为什么C和Java的循环浮动不同?

2022-02-25 00:00:00 floating-point c printf java

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

Java

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

结果:0.64469688

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

C

#include <stdio.h>

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

结果:0.64469687

亲自尝试: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表示:

…如果精度小于分别由Float.toString(float)Double.toString(double)返回的字符串中小数点后的位数,则将使用四舍五入算法对该值进行四舍五入。否则,可以追加零以达到精度…

让我们考虑一下"…返回的字符串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",似乎他们给了实现一个选择,尽管我怀疑他们的意思是"May"是基于是否需要零来达到精度,而不是实现者是否选择追加它们。)

脚注

1将源文本中的0.644696875转换为Double时,我认为结果应该是Double格式中可以表示的最接近的值。(我没有在Java文档中找到这一点,但它符合要求实现行为相同的Java哲学,我怀疑转换是根据Double.valueOf(String s)完成的,does require this。)最接近0.644696875的Double是0.6446968749999999470645661858725361526012420654296875.

2使用较少的数字时,7位0.64469687是不够的,因为最接近它的Double值是0.6446968699999999774519210404832847416400909423828125.因此需要8位数字来唯一区分0.6446968749999999470645661858725361526012420654296875.

相关文章