我们在项目中经常会用到小数,那就来看看常用到的double和BigDecimal有没有用对它俩。
先看代码:
public static void main(String[] args) {
double d1 = 0.02;
double d2 = 0.03;
System.out.println(d2-d1);
}
以你的经验看,会输出什么?0.01?
实际则是:
0.009999999999999998
为什么呢?大概率也都知道:精度问题嘛!
赶个潮流吧,我们让文心一言给解释一下:
OK!在内存中实际上是以最新近的二进制浮点数形式存储的,那么d2-d1也就自然会是一个有误差的结果,是一个近似值。明白了!
再看,它给出了建议方法,用BigDecimal,说BigDecimal适用于高精度计算的场合,恩,这个和我印象中的是一样的,那我们用它来试试看,应该没问题的:)
public static void main(String[] args) {
BigDecimal b1 = new BigDecimal(0.02);
BigDecimal b2 = new BigDecimal(0.03);
System.out.println(b2.subtract(b1));
}
经常这么用吧?用上BigDecimal了,这回应该可以正常的输出0.01了吧?
0.0099999999999999984734433411404097569175064563751220703125
这。是什么操作?怎么还不对?
好吧,稳住,看下new BigDecimal的源代码。
/**
* Translates a {@code double} into a {@code BigDecimal} which
* is the exact decimal representation of the {@code double}'s
* binary floating-point value. The scale of the returned
* {@code BigDecimal} is the smallest value such that
* <tt>(10<sup>scale</sup> × val)</tt> is an integer.
* <p>
* <b>Notes:</b>
* <ol>
* <li>
* The results of this constructor can be somewhat unpredictable.
* One might assume that writing {@code new BigDecimal(0.1)} in
* Java creates a {@code BigDecimal} which is exactly equal to
* 0.1 (an unscaled value of 1, with a scale of 1), but it is
* actually equal to
* 0.1000000000000000055511151231257827021181583404541015625.
* This is because 0.1 cannot be represented exactly as a
* {@code double} (or, for that matter, as a binary fraction of
* any finite length). Thus, the value that is being passed
* <i>in</i> to the constructor is not exactly equal to 0.1,
* appearances notwithstanding.
*
* <li>
* The {@code String} constructor, on the other hand, is
* perfectly predictable: writing {@code new BigDecimal("0.1")}
* creates a {@code BigDecimal} which is <i>exactly</i> equal to
* 0.1, as one would expect. Therefore, it is generally
* recommended that the {@linkplain #BigDecimal(String)
* <tt>String</tt> constructor} be used in preference to this one.
*
* <li>
* When a {@code double} must be used as a source for a
* {@code BigDecimal}, note that this constructor provides an
* exact conversion; it does not give the same result as
* converting the {@code double} to a {@code String} using the
* {@link Double#toString(double)} method and then using the
* {@link #BigDecimal(String)} constructor. To get that result,
* use the {@code static} {@link #valueOf(double)} method.
* </ol>
*
* @param val {@code double} value to be converted to
* {@code BigDecimal}.
* @throws NumberFormatException if {@code val} is infinite or NaN.
*/
public BigDecimal(double val) {
this(val,MathContext.UNLIMITED);
}
英文太多,翻译一下:
将一个double类型的值转换为BigDecimal对象,返回的同样是一个近似值!!!
不过,人家也给出了可预测的方法说明:
建议用string参数的构造方法:
public BigDecimal(String val) {
this(val.toCharArray(), 0, val.length());
}
好的,这个好理解,肯定不是近似值了,我们再来试试:
public static void main(String[] args) {
BigDecimal b1 = new BigDecimal("0.02");
BigDecimal b2 = new BigDecimal("0.03");
System.out.println(b2.subtract(b1));
}
输出:
终于是对了!
所以,如果想要使用BigDecimal来用在对精度要求更高的场景,一定要充分了解api才行,并不是用上了就一定是高精度的实现了。
另外一个,关于BigDecimal.valueOf(double val)的使用,也需要注意,主要是0和0.0的区别,这个看看源码中的注解也就清楚了,特别是如果以BigDecimal作为key的场景时,0和0.0重点关注