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规范怎么说
…如果精度小于分别由
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。