为什么 String.format 0.1d double 值在 java 中精确为 0.1?

why String.format 0.1d double value as exact 0.1 in java?

IEEE 754 浮点数是离散的。

public class MyTest2 {
  public static void main(String[] args) {
    //about 1.00000001490116119384765625E-1 in IEEE-754
    float f = 0.1f;
    //about 1.00000000000000005551115123126E-1 in IEEE-754
    double d = 0.1d;
    System.out.println(String.format("double 0.1= %.30f", d));
    System.out.println(String.format("float 0.1 = %.15f", f));
    System.out.println(d+"");
  }
}

看到这个 code run live at IdeOne.com。 运行在JDK8中,输出为

double 0.1= 0.100000000000000000000000000000
float 0.1 = 0.100000001490116
0.1

浮点值按预期打印。 我希望双精度值 0.1d 打印成类似 1.000000000000000055511151231260 的内容。 为什么打印 小数部分全为零?

如果我将双精度变量 d 转换为字符串,它会打印 0.1。

System.out.println(d+"");

java 如何将最接近的浮点值 0.1d(它被表示为大约 1.00000001490116119384765625E-1)转换为精确的 0.1?

如果您看到 java 源代码,您会发现

System.out.println(d+"");

将使用这样的东西:

Double.toString(d)

反过来会调用

FloatingDecimal.toJavaFormatString(d)

String.format 使用其他机制。这就是您看到这种行为的原因。

参考文献:

Java 规范要求这种不完美的值显示。 f 格式只生成与 Double.toString(double) 方法一样多的有效数字,然后盲目地附加零以达到要求的精度。

Per the documentation,对于 f 格式,如果精度超过 Double.toString(double) 会产生的小数点后的位数,则“可能会附加零以达到精度。”这并没有说明这些零附加到什么。据推测,它们被附加到 Double.toString(double) 会产生的字符串。

The documentation for Double.toString(double) says it produces “as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type double.” I discuss that further 。对于 0.1000000000000000055511151231257827021181583404541015625,Double.toString(double) 产生“0.1”。 (相邻值,0.09999999999999999167332731531132594682276248931884765625 和 0.10000000000000001942890293094023945741355419158935546875, are both further from .1 than 0.1000000000000000055511151231257827021181583404541015625 is, and they are formatted as “0.09999999999999999” and “0.10000000000000002”, so “0.1” serves to uniquely distinguish 0.1000000000000000055511151231257827021181583404541015625 from its neighbors.)

因此,System.out.println(String.format("double 0.1= %.30f", d))Double.toString(double) 的“0.1”开始,并附加 29 个零。

同样,如果您将 d 更改为 0.099999999999999999167332731531132594682276248931884765625,String.format 会生成“0.09999999999999990000000000000”,并在结果后附加零 [=22] - 它采用了零对于 0.10000000000000001942890293094023945741355419158935546875,它产生“0.100000000000000020000000000000”。

这符合规范。指定的行为无法正确呈现真实值,因此我认为该规范有缺陷。

顺便说一句,无论请求的精度是大于还是小于 Double.toString(double) 将产生的位数,Java 规范都很麻烦。在请求权限较少的情况下, 会增加错误。