为什么C和Java回合的浮动不同?

gde*_*ino 17 c java floating-point printf

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

爪哇

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

结果:0.6446968 8

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

C

#include <stdio.h>

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

结果:0.6446968 7

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

问题

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

背景

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

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

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

Eri*_*hil 16

结论

在这种情况下,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规范怎么说

使用Double类型和f格式进行格式化的文档说:

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

让我们考虑“……返回的字符串Double.toString(double)”。对于数字0.6446968749999999470645661858725361526012420654296875,此字符串为“ 0.644696875”。这是因为Java规范说toString产生的十进制数字足以唯一地区Double分值集中的数字,而在这种情况下,“ 0.644696875”具有足够的数字。2

该数字在小数点后有9位数字,并"%10.8f"要求输入8位,因此上面引用的段落表示“值”是四舍五入的。它的意思是哪个值-的实际操作数format为0.6446968749999999470645661858725361526012420654296875,或它提到的字符串“ 0.644696875”?由于后者不是数字值,因此我希望“值”表示前者。但是,第二句话说:“否则[即,如果需要更多数字,则可以附加零...”。如果我们使用的实际操作数format,则将显示其数字,而不使用零。但是,如果我们将字符串作为数字值,则其十进制表示形式将仅在显示的数字之后为零。因此,这似乎是预期的解释,并且Java实现似乎与此相符。

因此,要使用格式化该数字"%10.8f",我们首先将其转换为0.644696875,然后使用舍入上舍入规则将其舍入,这将产生0.64469688。

这是一个糟糕的规范,因为:

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

(而且,他们写了零的耻辱“可以”追加。为什么不“否则,零附加以达到精准”?以“可能”,好像他们给执行情况的选择,但我怀疑他们表示“可能”取决于是否需要零才能达到精度,而不取决于实现者是否选择附加它们。)

脚注

10.644696875源文本中的转换为时Double,我相信结果应该是Double格式中可以表示的最接近的值。(我没有在Java文档中找到它,但是它符合要求实现具有相同行为的Java哲学,并且我怀疑转换是根据Double.valueOf(String s),而这确实需Double要这样做。)最接近0.644696875的是0.6446968749999999470645661858725361526012420654296875。

2用较少的数字表示,七位数的0.64469687是不够的,因为Double最接近它的值为0.6446968 699999999774519210210832832416416400909423828125。因此需要八位数字来唯一区分0.6446968 749999999470645661858725361526012420654296875


Jac*_* FW 5

可能发生的情况是,他们使用略有不同的方法将数字转换为字符串,从而引入了舍入误差。编译期间将字符串转换为浮点数的方法之间也可能有所不同,由于舍入,其给出的值也可能略有不同。

不过请记住,float的分数精度为24位,大约为7.22个十进制数字[log10(2)* 24],并且前7位数字彼此一致,因此,这只是最后几个最低有效位不同。

欢迎来到浮点数学的有趣世界,其中2 + 2并不总是等于4。

  • @ JL2210用浮点术语表示的非常不准确的表示,它将在〜2.2个不同的值之间波动,而不是正常的10个值,换句话说,它的准确度仅为22%,而不是100%。 (3认同)
  • 不涉及浮点数(32位二进制浮点数)。如果存在,则值为0.644696891307830810546875,我们将看到“ 0.64469689” —以9结尾,而不是8或7。 (2认同)