如果小数不能用二进制精确表示,Double.toString() 如何工作?

How does Double.toString() work if a fraction number cannot be precisely represented in binary?

我无法理解 Double.toString() 在 Java/JVM 中的工作原理。 我的理解是,一般小数不能用 Double 和 Float 等浮点类型精确表示。例如,206.64 的二进制表示形式为 206.6399999999999863575794734060764312744140625。那怎么会 (206.64).toString() returns “206.64” 而不是 “206.6399999999999863575794734060764312744140625”?

Kotlin 中的测试代码。

@Test
fun testBigDecimalToString() {
    val value = 206.64
    val expected = "206.64"

    val bigDecimal = BigDecimal(value)

    assertEquals(expected, value.toString()) // success
    assertEquals(expected, bigDecimal.toString()) // failed. Actual: 206.6399999999999863575794734060764312744140625
}

我有点菜鸟,希望有经验的大佬解答的更透彻,不过我推测是这样的原因...

正在格式化

虽然这是针对 .NET 框架而不是专门针对 Java,但我想它们的工作方式类似:toString 方法使用 optional formatter input,并且很可能 Java 使用某些东西类似地,在 toString 方法中将 double 格式化为接近的近似值。 考虑到 Oracle 明确指出 toString should be concise and easy-to-read,很可能为 Double.toString().

实现了这样的方法

只需要区分的数字...

This is about as much documentation 正如我在 Double.toString() 方法的细节中找到的那样——注意最后一段:

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.

我很好奇"adjacent values of type double"(其他变量?)是什么意思,但它似乎也同意上述说法——toString 和其他方法可能只使用尽可能少的数字来唯一标识double,当数字足够接近时四舍五入,例如 23.675999999999 是 "close enough" 到 23.676 的情况。 或者我可能会严重误解文档。

您在打印 floatdouble 时看到的位数是 Java 对 float 和 [ 的默认转换规则的结果=11=] 到十进制。

Java 对 floating-point 数字的默认格式使用最少的有效小数位来区分该数字与附近的可表示数字。1

在您的示例中,源文本中的 206.64 被转换为 double 值 206.6399999999999863575794734060764312744140625,因为在 double 类型中可表示的所有值中,该值最接近206.64。下一个较低和下一个较高的值是 206.639999999999957935870043002068996429443359375 和 206.640000000000014779288903810083866119384765625.

When printing this value, Java only needs to print “206.64”, because that is enough that we can pick out the double value 206.6399999999999863575794734060764312744140625 from its neighbors 206.639999999999957935870043002068996429443359375 and 206.640000000000014779288903810083866119384765625。请注意,从 206.63999… 中的 9 结尾开始,第一个值与 206.64 相差 .1364…,而第三个值 206.64000… 相差 .1477…。 So, when Java prints “206.64”, it means the value of the double being printed is the nearest representable value, and that is the 206.6399999999999863575794734060764312744140625 value, not the farther 206.640000000000014779288903810083866119384765625 value.

脚注

1 Java SE 10 的规则可以在 java.lang.float 文档的 toString(float d) 部分找到。 double 文档类似。最相关部分以粗体显示的段落是:

Returns a string representation of the float argument. All characters mentioned below are ASCII characters.

  • If the argument is NaN, the result is the string "NaN".

  • Otherwise, the result is a string that represents the sign and magnitude (absolute value) of the argument. If the sign is negative, the first character of the result is '-' ('\u002D'); if the sign is positive, no sign character appears in the result. As for the magnitude m:

    • If m is infinity, it is represented by the characters "Infinity"; thus, positive infinity produces the result "Infinity" and negative infinity produces the result "-Infinity".

    • If m is zero, it is represented by the characters "0.0"; thus, negative zero produces the result "-0.0" and positive zero produces the result "0.0".

    • If m is greater than or equal to 10-3 but less than 107, then it is represented as the integer part of m, in decimal form with no leading zeroes, followed by '.' ('\u002E'), followed by one or more decimal digits representing the fractional part of m.

    • If m is less than 10-3 or greater than or equal to 107, then it is represented in so-called "computerized scientific notation." Let n be the unique integer such that 10nm < 10n+1; then let a be the mathematically exact quotient of m and 10n so that 1 ≤ a < 10. The magnitude is then represented as the integer part of a, as a single decimal digit, followed by '.' ('\u002E'), followed by decimal digits representing the fractional part of a, followed by the letter 'E' ('\u0045'), followed by a representation of n as a decimal integer, as produced by the method Integer.toString(int).

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 float. That is, suppose that x is the exact mathematical value represented by the decimal representation produced by this method for a finite nonzero argument f. Then f must be the float value nearest to x; or, if two float values are equally close to x, then f must be one of them and the least significant bit of the significand of f must be 0.