一、BigDecimal.valueOf(double val):
这个方法是专门为浮点数设计的,它试图为给定的 double 值提供一个精确的 BigDecimal 表示。
当你传递一个 double 值给 valueOf 方法时,它并不直接使用该 double 的二进制表示。相反,它首先检查这个 double 是否能精确地表示为长整数(即没有小数部分),如果是,则使用长整数来构建 BigDecimal。
如果 double 值包含小数部分,则 valueOf 方法会使用一个更精确的算法来将 double 转换为 BigDecimal,这通常涉及将 double 分解为它的各个部分(符号、指数、尾数等),并据此构造 BigDecimal。
因此,对于 BigDecimal.valueOf(0.01),它通常会给出精确的结果,因为该值可以相对容易地以精确的方式转换为 BigDecimal。
二、new BigDecimal(double val):
当你使用这种构造函数时,BigDecimal 直接使用传入的 double 值的二进制表示来构建 BigDecimal。
但是,由于许多 double 值(尤其是小数)在二进制中不能精确表示(就像十进制中的 1/3 不能精确表示为有限小数一样),因此这种转换可能会导致精度损失。
这就是为什么直接使用 new BigDecimal(0.01) 可能会得到一个有轻微精度损失的结果的原因。
总之,为了避免在将浮点数转换为 BigDecimal 时出现精度问题,最好使用 BigDecimal.valueOf(double val) 方法,而不是直接使用 new BigDecimal(double val) 构造函数。
三、new BigDecimal(“0.01”):
这个构造函数直接接受一个字符串参数。
字符串 “0.01” 在内存中是以字符数组的形式存储的,每个字符都有一个明确的ASCII值。
BigDecimal 的这个构造函数会解析这个字符串,按照十进制数制规则来构建内部的数值表示。
因为字符串精确地表示了数字0.01,所以这种方式没有精度损失。
四、new BigDecimal(“0.01”)与BigDecimal.valueof(0.01)创建时底层有什么区别吗
在比较 new BigDecimal(“0.01”) 和 BigDecimal.valueOf(0.01) 创建 BigDecimal 对象时底层的区别时,实际上有一点需要澄清:BigDecimal.valueOf(0.01) 并不直接接受一个 double 类型的字面量作为参数,而是接受一个 double 类型的值作为参数,但这个值通常是通过字面量或其他方式(如计算)得到的。
不过,为了回答你的问题,我们可以从它们如何处理参数和内部实现的角度来讨论:
new BigDecimal(“0.01”):
这个构造函数直接接受一个字符串参数。
字符串 “0.01” 在内存中是以字符数组的形式存储的,每个字符都有一个明确的ASCII值。
BigDecimal 的这个构造函数会解析这个字符串,按照十进制数制规则来构建内部的数值表示。
因为字符串精确地表示了数字0.01,所以这种方式没有精度损失。
BigDecimal.valueOf(0.01):
这个静态方法接受一个 double 类型的参数(尽管这里你写的是一个 double 字面量0.01,但它在被传递给方法之前已经被转换成了 double 类型的值)。
这个 double 值在内存中是以IEEE 754浮点数格式存储的,可能不是0.01的精确表示。
但是,BigDecimal.valueOf 方法并没有直接使用这个 double 值的二进制表示来构建 BigDecimal。相反,它使用了一种更精确的方式来处理这个 double 值。
具体来说,valueOf 方法会检查这个 double 值是否可以精确地表示为一个长整数(即没有小数部分)。如果可以,它就直接使用长整数来构建 BigDecimal。如果不能,它会将 double 值分解为它的各个部分(符号、指数、尾数等),并据此精确地构建 BigDecimal。
因此,虽然你传递的是一个 double 值给 valueOf 方法,但该方法会尽量避免由于 double 不精确表示导致的精度损失。
总结来说,虽然 new BigDecimal(“0.01”) 和 BigDecimal.valueOf(0.01) 在表面上看起来都接受了一个表示0.01的参数,但它们在底层处理这个参数的方式是不同的。new BigDecimal(“0.01”) 直接解析字符串来构建 BigDecimal,而 BigDecimal.valueOf(0.01) 则使用了一种更精确的方式来处理 double 值,以避免精度损失。
五、BigDecimal.valueof(0.01)为啥没有精度问题,而new BigDecimal(0.01)有精度问题
BigDecimal是Java中用于解决浮点数运算精度问题的一个类,它提供了精确的数值运算能力。在处理涉及精度要求较高的场合,如金融计算时,BigDecimal的使用尤为重要。关于BigDecimal.valueOf(0.01)
和new BigDecimal(0.01)
在精度问题上的差异,可以从以下几个方面进行分析:
-
计算机存储机制
- 二进制表示:计算机内部使用二进制系统来表示和存储所有的数据,包括十进制的浮点数。由于二进制无法精确表示某些十进制小数,因此在转换过程中可能会产生精度损失[1]。
- 浮点数表示:计算机中的浮点数由指数和尾数两部分组成,这种表示方法可能会导致一些浮点数运算产生误差[3]。
-
构造函数差异
BigDecimal.valueOf(double val)
:这个静态方法在底层实际上是将传入的double值先转换为字符串,然后再创建BigDecimal对象。这样做的好处是避免了直接使用double值可能带来的精度损失[4]。new BigDecimal(double val)
:使用这个构造函数会直接根据传入的double值创建一个BigDecimal对象,但由于double值本身的精度问题,创建的BigDecimal对象可能会包含不精确的值[3]。
-
精度丢失风险
- 直接使用double:当直接使用double类型的字面量作为参数时,可能会因为二进制表示的限制而引入精度误差。
- 字符串转换:通过字符串创建BigDecimal可以避免这种精度丢失,因为字符串中的每个数字都是精确的。
-
推荐做法
- 优先使用
BigDecimal.valueOf()
:为了避免精度丢失,建议使用BigDecimal.valueOf()
方法来创建BigDecimal对象[2][4]。 - 使用字符串构造函数:或者使用
new BigDecimal(String val)
构造函数,将需要精确表示的浮点数先转换为字符串,再创建BigDecimal对象。
- 优先使用
-
阿里巴巴Java开发手册建议
- 避免使用基本数据类型:《阿里巴巴 Java 开发手册》中提到,浮点数之间的等值判断不应该使用基本数据类型的
==
来比较,因为这可能会因为精度问题导致错误的比较结果[1]。 - 使用BigDecimal进行精确计算:手册中还提到,涉及到钱等需要精确计算结果的场景应该使用BigDecimal来进行运算。
- 避免使用基本数据类型:《阿里巴巴 Java 开发手册》中提到,浮点数之间的等值判断不应该使用基本数据类型的
-
实际案例
- 金融计算:在金融领域,比如货币的加减乘除,精确到分的计算是必须的,这时候使用BigDecimal可以保证计算的准确性。
- 科学计算:在科学计算中,对精度的要求也非常高,使用BigDecimal可以避免因为浮点数的精度问题导致的计算错误。
-
注意事项
- 除法运算:在使用BigDecimal进行除法运算时,应该注意使用带有三个参数的divide方法,并选择合适的舍入模式,以避免无法除尽时出现的异常[1]。
- 大小比较:使用compareTo方法进行大小比较,而不是使用
==
来判断两个BigDecimal对象是否相等。
-
其他方法
- add、subtract、multiply、divide:这些方法分别用于执行加法、减法、乘法和除法运算,它们保证了运算的精确性[1]。
- toString、doubleValue、floatValue、longValue、intValue:这些方法用于将BigDecimal对象转换为其他类型的对象,但需要注意的是转换后的对象可能无法保持原有的精度。
综上所述,BigDecimal.valueOf(0.01)
没有精度问题是因为它通过将double值转换为字符串的方式来创建BigDecimal对象,从而避免了直接使用double值可能带来的精度损失。而new BigDecimal(0.01)
有精度问题的原因在于它直接使用了可能存在精度误差的double值来创建BigDecimal对象。因此,在实际开发中,为了确保精度,应优先考虑使用BigDecimal.valueOf()
方法或通过字符串构造函数来创建BigDecimal对象。