【课程管理系统 源码】【投票 小程序源码】【windows坦克大战源码】Bigdicmal 源码

1.不是我说,不掌握这些坑,你敢用BigDecimal吗?
2.BigDecimal的精度与刻度
3.BigInteger与BigDecimal
4.Java为什么不能使用 BigDecimal 的 equals 方法做等值比较
5.关于java中DecimalFormat的问题。

Bigdicmal 源码

不是我说,不掌握这些坑,你敢用BigDecimal吗?

       一直从事金融相关项目,课程管理系统 源码对BigDecimal了如指掌,也见证了许多因为对其不了解或使用不当导致的资损事件。

       如果你从事金融相关项目,或者你的项目中涉及到金额的计算,务必花时间阅读这篇文章,全面了解BigDecimal。

       Java的java.math包提供了BigDecimal API类,用于对超过位有效位的数进行精确的运算。双精度浮点型变量double可以处理位有效数,但在实际应用中,可能需要对更大或更小的数进行运算和处理。

       一般情况下,对于不需要精确计算精度的数字,可以直接使用Float和Double处理,但Double.valueOf(String)和Float.valueOf(String)会丢失精度。因此,如果需要精确计算的结果,则必须使用BigDecimal类来操作。

       BigDecimal对象提供了传统的+、-、*、/等算术运算符对应的投票 小程序源码方法,通过这些方法进行相应的操作。BigDecimal都是不可变的(immutable)的,在进行每一次四则运算时,都会产生一个新的对象,所以在做加减乘除运算时要记得保存操作后的值。

       在使用BigDecimal时,有4种使用场景下的坑,你一定要了解一下,如果使用不当,必定很惨。掌握这些案例,当别人写出有坑的代码,你也能够一眼识别出来,大牛就是这么练成的。

       第一:浮点类型的坑

       在使用Float、Double等浮点类型进行计算时,有可能得到的是一个近似值,而不是精确的值。

       比如下面的代码:结果是多少?0.1吗?不是,执行上面代码执行的结果是0.。之所以产生这样的结果,是因为0.1的二进制表示是无限循环的。由于计算机的资源是有限的,所以是没办法用二进制精确地表示0.1,只能用「近似值」来表示,就是在有限的精度情况下,最大化接近0.1的windows坦克大战源码二进制数,于是就会造成精度缺失的情况。

       关于上述的现象大家都知道,不再详细展开。同时,还会得出结论在科学计数法时可考虑使用浮点类型,但如果是涉及到金额计算要使用BigDecimal来计算。

       那么,BigDecimal就一定能避免上述的浮点问题吗?来看下面的示例:

       上述单元测试中的代码,a和b结果分别是什么?

       上面的实例说明,即便是使用BigDecimal,结果依旧会出现精度问题。这就涉及到创建BigDecimal对象时,如果有初始值,是采用new BigDecimal的形式,还是通过BigDecimal#valueOf方法了。

       之所以会出现上述现象,是因为new BigDecimal时,传入的0.1已经是浮点类型了,鉴于上面说的这个值只是近似值,在使用new BigDecimal时就把这个近似值完整地保留下来了。

       而BigDecimal#valueOf则不同,它的源码实现如下:

       在valueOf内部,使用Double#toString方法,将浮点类型的值转换成了字符串,因此就不存在精度丢失问题了。

       此时就得出一个基本的第一,在使用BigDecimal构造函数时,注册登录php源码尽量传递字符串而非浮点类型;第二,如果无法满足第一条,则可采用BigDecimal#valueOf方法来构造初始化值。

       这里延伸一下,BigDecimal常见的构造方法有如下几种:

       其中涉及到参数类型为double的构造方法,会出现上述的问题,使用时需特别留意。

       第二:浮点精度的坑

       如果比较两个BigDecimal的值是否相等,你会如何比较?使用equals方法还是compareTo方法呢?

       先来看一个示例:

       乍一看感觉可能相等,但实际上它们的本质并不相同。

       equals方法是基于BigDecimal实现的equals方法来进行比较的,直观印象就是比较两个对象是否相同,那么代码是如何实现的呢?

       仔细阅读代码可以看出,equals方法不仅比较了值是否相等,还比较了精度是否相同。上述示例中,由于两者的精度不同,所以equals方法的结果当然是false了。而compareTo方法实现了Comparable接口,真正比较的是值的大小,返回的值为-1(小于),0(等于),1(大于)。

       基本通常情况,如果比较两个BigDecimal值的大小,采用其实现的compareTo方法;如果严格限制精度的比较,那么则可考虑使用equals方法。免小号登录源码

       另外,这种场景在比较0值的时候比较常见,比如比较BigDecimal("0")、BigDecimal("0.0")、BigDecimal("0."),此时一定要使用compareTo方法进行比较。

       第三:设置精度的坑

       在项目中看到好多同学通过BigDecimal进行计算时不设置计算结果的精度和舍入模式,真是着急人,虽然大多数情况下不会出现什么问题。但下面的场景就不一定了:

       执行上述代码的结果是什么?ArithmeticException异常!

       这个异常的发生在官方文档中也有说明:

       If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations.

       总结一下就是,如果在除法(divide)运算过程中,如果商是一个无限小数(0.…),而操作的结果预期是一个精确的数字,那么将会抛出ArithmeticException异常。

       此时,只需在使用divide方法时指定结果的精度即可:

       执行上述代码,输入结果为0.。

       基本在使用BigDecimal进行(所有)运算时,一定要明确指定精度和舍入模式。

       拓展一下,舍入模式定义在RoundingMode枚举类中,共有8种:

       通常我们使用的四舍五入即RoundingMode.HALF_UP。

       第四:三种字符串输出的坑

       当使用BigDecimal之后,需要转换成String类型,你是如何操作的?直接toString?

       先来看看下面的代码:

       执行的结果是上述对应的值吗?并不是:

       也就是说,本来想打印字符串的,结果打印出来的是科学计数法的值。

       这里我们需要了解BigDecimal转换字符串的三个方法

       三种方法展示结果示例如下:

       计算法

       基本**根据数据结果展示格式不同,采用不同的字符串输出方法,通常使用比较多的方法为toPlainString()**。

       另外,NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出位有效数字的货币值,百分值,以及一般数值进行格式化控制。

       使用示例如下:

       输出结果如下:

       小结

       本篇文章介绍了BigDecimal使用中场景的坑,以及基于这些坑我们得出的“最佳实践”。虽然某些场景下推荐使用BigDecimal,它能够达到更好的精度,但性能相较于double和float,还是有一定的损失的,特别在处理庞大、复杂的运算时尤为明显。故一般精度的计算没必要使用BigDecimal。而必须使用时,一定要规避上述的坑。

BigDecimal的精度与刻度

       BigDecimal在处理高精度运算时,能有效避免Double类型的精度损失问题,尤其在金融计算等领域尤为重要。了解BigDecimal的精度与刻度概念对于正确使用这个类至关重要。精度指最多可表示的数字位数,而刻度指小数点后能保留的位数。

       例如,DECIMAL(7, 2) 表示最多可存储7位数字,其中2位为小数点后,5位为整数部分。在BigDecimal中,精度和刻度可通过相关方法获取。

       执行除法运算时,需要指定刻度以控制保留的小数位数,并选择进位模式。模式包括UP、DOWN、CEILING、FLOOR、HALF_UP、HALF_DOWN、HALF_EVEN 和 UNNECESSARY。不指定刻度时,结果能除尽则正常处理,否则会抛出异常。源码中的计算逻辑旨在确保结果的精度。

       使用字符串构造BigDecimal是最佳实践,避免因直接使用数值导致的精度损失。尝试构造一个刻度为的BigDecimal时,结果出乎意料,这是由于二进制无法精确表示某些十进制数,尤其是那些在二进制中有无限循环小数位的数。

       例如,0.1和0.2在二进制中无法精确表示,转换后会变成无限循环小数。由于计算机存储空间有限,这些小数只能被截断,导致精度损失。直接使用Double构造BigDecimal与使用字符串构造的实现不同,原因在于避免直接使用数值时可能出现的精度问题。

BigInteger与BigDecimal

       BigInteger - 廖雪峰的网站

        在Java中,由CPU原生提供的整型最大范围是位 long 型整数。使用 long 型整数可以直接通过CPU指令进行计算,速度非常快。

        如果我们使用的整数范围超过了 long 型怎么办?这个时候,就只能用软件来模拟一个大整数。 java.math.BigInteger 就是用来表示任意大小的整数。 BigInteger 内部用一个 int[] 数组来模拟一个非常大的整数:

        对 BigInteger 做运算的时候,只能使用实例方法,例如,加法运算:

        和 long 型整数运算比, BigInteger 不会有范围限制,但缺点是速度比较慢。

        也可以把 BigInteger 转换成 long 型:

        使用 longValueExact() 方法时,如果超出了 long 型的范围,会抛出 ArithmeticException 。

        BigInteger 和 Integer 、 Long 一样,也是不可变类,并且也继承自 Number 类。因为 Number 定义了转换为基本类型的几个方法:

        因此,通过上述方法,可以把 BigInteger 转换成基本类型。如果 BigInteger 表示的范围超过了基本类型的范围,转换时将丢失高位信息,即结果不一定是准确的。如果需要准确地转换成基本类型,可以使用 intValueExact() 、 longValueExact() 等方法,在转换时如果超出范围,将直接抛出 ArithmeticException 异常。

        如果 BigInteger 的值甚至超过了 float 的最大范围,那么返回的 float 是什么呢?

        和 BigInteger 类似, BigDecimal 可以表示一个任意大小且精度完全准确的浮点数。

        通过 BigDecimal 的 stripTrailingZeros() 方法,可以将一个 BigDecimal 格式化为一个相等的,但去掉了末尾0的 BigDecimal :

        如果一个 BigDecimal 的 scale() 返回负数,例如, -2 ,表示这个数是个整数,并且末尾有2个0。

        可以对一个 BigDecimal 设置它的 scale ,如果精度比原始值低,那么按照指定的方法进行四舍五入或者直接截断:

        对 BigDecimal 做加、减、乘时,精度不会丢失,但是做除法时,存在无法除尽的情况,这时,就必须指定精度以及如何进行截断:

        还可以对 BigDecimal 做除法的同时求余数:

        调用 divideAndRemainder() 方法时,返回的数组包含两个 BigDecimal ,分别是商和余数,其中商总是整数,余数不会大于除数。我们可以利用这个方法判断两个 BigDecimal 是否是整数倍数:

        在比较两个 BigDecimal 的值是否相等时,要特别注意,使用 equals() 方法不但要求两个 BigDecimal 的值相等,还要求它们的 scale() 相等:

        必须使用 compareTo() 方法来比较,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于。

        总是使用 compareTo() 比较两个 BigDecimal 的值,不要使用 equals() !

        如果查看 BigDecimal 的源码,可以发现,实际上一个 BigDecimal 是通过一个 BigInteger 和一个 scale 来表示的,即 BigInteger 表示一个完整的整数,而 scale 表示小数位数:

        BigDecimal 也是从 Number 继承的,也是不可变对象。

Java为什么不能使用 BigDecimal 的 equals 方法做等值比较

       前言

       在Java编程中,`BigDecimal`是数学包`java.math`提供的一种可以进行精确运算的类型。它在支付、电商等业务中使用广泛,并内置了加、减、乘、除等运算方法。然而,在使用`BigDecimal`进行数值比较时,`equals`方法的使用常常引发疑惑。本文将深入探讨为什么不能直接使用`equals`方法进行等值比较,并解释背后的原理。

       等值比较的问题

       根据《阿里巴巴Java开发手册》中的要求,直接使用`==`进行`BigDecimal`对象比较是不正确的。直接使用`equals`方法进行等值判断呢?让我们通过代码运行来验证。

       使用`equals`方法比较`BigDecimal`时,结果会根据值和标度的不同而变化。在某些情况下,使用`int`、`double`定义的`BigDecimal`进行`equals`比较会得到正确的结果,而使用`String`定义的`BigDecimal`则不会。这背后的原因在于`equals`方法不仅比较值,还会比较标度。

       理解`equals`方法内部逻辑

       `equals`方法的源码揭示了其比较逻辑。它不仅比较两个`BigDecimal`对象的值,还会比较它们的标度。因此,即便两个`BigDecimal`对象的值相同,如果它们的标度不同,`equals`方法也会返回`false`。

       代码调试揭示标度差异

       通过代码调试,我们可以看到`bigDecimal5`和`bigDecimal6`的值相同,但标度不同。`bigDecimal5`的标度为0,而`bigDecimal6`的标度为1,导致它们的比较结果为`false`。

       标度差异的来源

       `BigDecimal`的构造方法揭示了标度差异的成因。`BigDecimal`有四种构造方法,其中`BigDecimal(int)`和`BigDecimal(long)`构造的`BigDecimal`对象的标度为0。当使用`BigDecimal(double)`构造方法时,如`new BigDecimal(0.1)`,实际上创建的`BigDecimal`对象的值更精确,因此标度较高。通过调试发现,`new BigDecimal(0.1)`的标度值为。同样地,`new BigDecimal(1.0)`和`new BigDecimal(1.)`形式的`BigDecimal`对象,虽然包含更多数字位,但它们本质上是整数,标度仍为0。

       使用`compareTo`方法进行值比较

       `compareTo`方法提供了只比较两个`BigDecimal`对象值的功能,不考虑标度。在比较`bigDecimal5`和`bigDecimal6`时,使用`compareTo`方法后得到的结果为0,表明这两个`BigDecimal`对象的值相等。

       总结

       总之,当使用`BigDecimal`进行等值比较时,应避免直接使用`equals`方法,而应使用`compareTo`方法进行值比较。这样可以准确地判断两个`BigDecimal`对象是否相等,避免因标度差异造成的混淆。

关于java中DecimalFormat的问题。

       把newSalary转为double型,然后再format就好了,看源码就会知道,String类型是不被允许的

public final StringBuffer format(Object number,

                                            StringBuffer toAppendTo,

                                            FieldPosition pos) {

               if (number instanceof Long || number instanceof Integer ||

                          number instanceof Short || number instanceof Byte ||

                          number instanceof AtomicInteger ||

                          number instanceof AtomicLong ||

                          (number instanceof BigInteger &&

                           ((BigInteger)number).bitLength () < )) {

                   return format(((Number)number).longValue(), toAppendTo, pos);

               } else if (number instanceof BigDecimal) {

                   return format((BigDecimal)number, toAppendTo, pos);

               } else if (number instanceof BigInteger) {

                   return format((BigInteger)number, toAppendTo, pos);

               } else if (number instanceof Number) {

                   return format(((Number)number).doubleValue(), toAppendTo, pos);

               } else {

                   throw new IllegalArgumentException("Cannot format given Object as a Number");

               }

           }

更多内容请点击【知识】专栏

精彩资讯