为什么使用add2
方法的结果是错误的?Double.toString(d1)
为什么能保持double
的精度不丢失?
public static double add(double d1, double d2) {
BigDecimal bd1 = new BigDecimal(Double.toString(d1));
BigDecimal bd2 = new BigDecimal(Double.toString(d2));
return bd1.add(bd2).doubleValue();
}
public static double add2(double d1, double d2) {
BigDecimal bd1 = new BigDecimal(d1);
BigDecimal bd2 = new BigDecimal(d2);
return bd1.add(bd2).doubleValue();
}
public static void main(String[] args) {
double d1 = 1.0000002;
double d2 = 0.0000002;
System.out.println(Double.toString(d1));
System.out.println("d1 + d2 = " + (d1 + d2)); // 1.0000003999999998
System.out.println("d1 + d2 = " + add(d1, d2)); // 1.0000004
System.out.println("d1 + d2 = " + add2(d1, d2)); // 1.0000003999999998
}
PS:引出一个概念问题:
double d = XXXX.XXXXXX;
System.out.println(d); //这里总是不会丢失精度的,为什么?
ringa_lee2017-04-18 09:43:04
When BigDecimal uses double as input parameter, binary cannot accurately represent decimals. After the compiler reads the strings "0.0000002" and "1.0000002", it must convert it into an 8-byte double value, which is 1.0000001999999998947288304407265968620777130126953125
类似这种。
所以,运行的时候,实际传给BigDecimal构造函数的真正的数值是1.0000001999999998947288304407265968620777130126953125
.
BigDecimal can correctly convert the string into a truly accurate floating point number when using String as an input parameter.
System.out.println part, if the input parameter is string, it will be output directly. If the input parameter is other types, then the Object.toString method will be called to convert and then output. Double.toString will use a certain precision to round the double before outputting it.
The comment on the BigDecimal constructor writes this question:
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.
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.
Additional explanation: Double.toString
This method outputs a String
, and it will be rounded. Double.toString
这个方法输出的是一个String
,而且会进行四舍五入处理。new BigDecimal(Double.toString(d1))
这个入参在处理完毕之后是一个String
,调用的是BigDecimal(String val)
new BigDecimal(Double.toString(d1))
After processing, this input parameter is a String
, and the call is BigDecimal(String val)
This construction method.
The BigDecimal(String val) method in the source code will process val into a char[] array:
this(val.toCharArray(), 0, val.length());
Then call BigDecimal(char[] in)
this constructor.
And new BigDecimal(d1) calls BigDecimal(double val)
. The first thing after this method comes in is
long valBits = Double.doubleToLongBits(val);
Convert the input parameters into binary, so accuracy will be lost.
The loss of precision caused by double addition is the same as the above situation. It is first converted into bits and then calculated, and then the precision is lost. .