java - 速度比较 - 为什么c++效率高




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

考虑浮点数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 : 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 : http://tpcg.io/fQqSRF

问题

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

背景

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

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

相关: https://mathematica.stackexchange.com/questions/204359/is-numberform-double-rounding-numbers : 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规范怎么说

使用 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之后四舍五入。程序无法轻易预测或调整它。

(而且,他们在“可能”之后添加了零,这是很可惜的。为什么不另外添加“零 以达到精度”?使用“可能”,似乎他们给了实现一个选择,尽管我怀疑他们表示“可能”取决于是否需要零才能达到精度,而不取决于实现者是否选择附加它们。)

脚注

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

2如果 数字较少,则7位数0.64469687不足,因为最接近它的 Double 值为0.6446968 699999999774519210210832832416416400909423828125 。 因此需要八位数字来唯一区分0.6446968 749999999470645661858725361526012420654296875


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

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

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





printf