为什么在java运算中有些双精度除法是正确的?

Why some double precision divisions are correct in java operation?

我了解二进制数的理论,所以double数的运算并不精确。但是,在java中,我不知道为什么“(double)65 / 100”是0.65,这在十进制数中是完全正确的,除了0.6500000000004.

double a = 5;
double b = 4.35;
int c = 65;
int d = 100;
System.out.println(a -  b); // 0.6500000000000004
System.out.println((double) c /  d); // 0.65

Java 完全搞砸了有它自己的处理floating-point二进制到十进制转换的方法。

一个简单的 C 程序(用 gcc 编译)给出了结果:

printf("1: %.20f\n", 5.0 - 4.35);         // 0.65000000000000035527
printf("2: %.20f\n", 65./100);            // 0.65000000000000002220

而 Java 给出了结果(注意你只需要 17 位数字就可以看到它,但我试图让它更清楚):

System.out.printf("%.20f\n", 5.0 - 4.35); // 0.65000000000000040000
System.out.printf("%.20f\n", 65./100);    // 0.65000000000000000000

但是当使用 %a 格式说明符时,两种语言 printf 底层十六进制(正确)值:0x1.4ccccccccccd00000000p-1.

因此,Java 在代码中的某个位置执行了一些非法舍入。 这里明显的问题是 Java 有一套不同的规则来将二进制转换为十进制,与 Java specification:

The number of digits in the result for the fractional part of m or a is equal to the precision. If the precision is not specified then the default value is 6. If the precision is less than the number of digits which would appear after the decimal point in the string returned by Float.toString(float) or Double.toString(double) respectively, then the value will be rounded using the round half up algorithm. Otherwise, zeros may be appended to reach the precision. For a canonical representation of the value, use Float.toString(float) or Double.toString(double) as appropriate. (emphasis mine)

并且在 toString specification:

How many digits must be printed for the fractional part of m or a? There must be at least one digit to represent the fractional part, and beyond that as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type double. That is, suppose that x is the exact mathematical value represented by the decimal representation produced by this method for a finite nonzero argument d. Then d must be the double value nearest to x; or if two double values are equally close to x, then d must be one of them and the least significant bit of the significand of d must be 0. (emphasis mine)

因此,Java 确实执行了与 C 不同的二进制到十进制的转换,但它仍然更接近真正的二进制值而不是任何其他值,因此规范保证可以通过以下方式恢复二进制值十进制到二进制的转换。

William Kahan 教授警告了本文中的一些 Java floating-point 问题:

How Java’s Floating-Point Hurts Everyone Everywhere

但这种转换行为似乎是IEEE-complaint。

编辑:我在评论中包含了@MarkDickinson 提供的信息,以报告此 Java 行为虽然与 C 不同,但已记录在案,并且是 IEEE-compliant。这已经解释过了 here, here, and .