考虑浮点数0.644696875。让我们使用Java和C将其转换为带有八个小数的字符串:

java

import java.lang.Math;
public class RoundExample{
     public static void main(String[] args){
        System.out.println(String.format("%10.8f",0.644696875));
     }
}

结果:0.6446968 8

自己尝试:http://tpcg.io/oszC0w

C
#include <stdio.h>

int main()
{
    printf("%10.8f", 0.644696875); //double to string
    return 0;
}

结果:0.6446968 7

自己尝试:http://tpcg.io/fQqSRF

问题

为什么最后一位数字不同?

背景

数字0.644696875无法完全表示为机器编号。它表示为分数2903456606016923/4503599627370496,其值为0.6446968749999999

诚然,这是一个极端情况。但是我真的很好奇差异的根源。

相关:https://mathematica.stackexchange.com/questions/204359/is-numberform-double-rounding-numbers

最佳答案

结论
在这种情况下,Java规范需要麻烦的双舍入。数字0.6446968749999999470645661858725361526012420654296875首先转换为0.644696875,然后四舍五入为0.64469688。
相反,C实现仅将0.6446968749999999470645661858725361526012420654296875直接舍入为八位数字,从而产生0.64469687。
初赛
对于Double,Java使用IEEE-754基本的64位二进制浮点数。在此格式下,最接近源文本中数字0.644696875的值为0.6446968749999999470645661858725361526012420654296875,我相信这是将使用String.format("%10.8f",0.644696875) .1格式化的实际值。
Java规范怎么说
The documentation for formatting with the Double type and f format说:

让我们考虑“…Double.toString(double)返回的字符串”。对于数字0.6446968749999999470645661858725361526012420654296875,此字符串为“0.644696875”。这是因为Java规范说 toString produces just enough decimal digits to uniquely distinguish the number within the set of Double values和“0.644696875”在这种情况下仅具有足够的数字。2
该数字在小数点后有9位数字,而"%10.8f"请求有8位数字,因此上面引用的段落表示“值”是四舍五入的。它是什么意思— format的实际操作数为0.6446968749999999470645661858725361526012420654296875,或它提到的字符串“0.644696875”?由于后者不是数字值,因此我希望“值”表示前者。但是,第二句话说:“否则[即,如果需要更多位数],可以附加零...”。如果我们使用的是format的实际操作数,我们将显示其位数,而不使用零。但是,如果我们将字符串作为数字值,则其十进制表示形式将仅在显示的数字之后为零。因此,这似乎是预期的解释,并且Java实现似乎与此相符。
因此,要使用"%10.8f"格式化此数字,我们首先将其转换为0.644696875,然后使用舍入上舍入规则将其舍入,这将产生0.64469688。
这是一个糟糕的规范,因为:

  • 它需要两个舍入,这会增加错误。
  • 四舍五入发生在难以预测和难以控制的地方。一些值将四舍五入到小数点后两位。有些将在13之后四舍五入。程序无法轻松预测或调整此值。

  • (此外,他们在“可能”之后添加了零,这是很可惜的。为什么不另外添加“零来达到精度”?为什么带有“may”,他们似乎给了实现一个选择,尽管我怀疑它们的意思是“可能”是基于是否需要零才能达到精度,而不是取决于实现者是否选择附加它们。)
    脚注
    1当源文本中的0.644696875转换为Double时,我相信结果应该是Double格式可表示的最接近的值。 (我没有在Java文档中找到它,但是它符合要求实现必须具有相同行为的Java哲学,并且我怀疑转换是按照Double.valueOf(String s)进行的,即does require this。)最接近0.644696875的Double是0.6446968749999999470645661858725361526012420654296875。
    2用较少的数字表示,七位数的0.64469687是不够的,因为最接近它的Double值为0.6446968 699999999774519210404832847416400909423828125 。因此,需要八位数字来唯一区分0.6446968 749999999470645661858725361526012420654296875

    09-11 17:54