为什么C和Java循环浮动不同?
考虑浮点数0.644696875。让我们使用Java和C将其转换为八位小数的字符串: JAVA为什么C和Java循环浮动不同?,java,c,floating-point,printf,Java,C,Floating Point,Printf,考虑浮点数0.644696875。让我们使用Java和C将其转换为八位小数的字符串: JAVA import java.lang.Math; 公共类圆形示例{ 公共静态void main(字符串[]args){ System.out.println(String.format(“%10.8f”,0.644696875)); } } 结果:0.64469688 你自己试试吧: C #包括 int main() { printf(“%10.8f”,0.644696875);//双精度转换为字符串
import java.lang.Math;
公共类圆形示例{
公共静态void main(字符串[]args){
System.out.println(String.format(“%10.8f”,0.644696875));
}
}
结果:0.64469688
你自己试试吧:
C
#包括
int main()
{
printf(“%10.8f”,0.644696875);//双精度转换为字符串
返回0;
}
结果:0.64469687
你自己试试吧:
问题
为什么最后一个数字不同
背景
数字0.644696875不能准确表示为机器编号。它表示为分数2903456606016923/4503599627370496,其值为0.64469687499999
这无疑是一个边缘案例。但我真的很好奇这种差异的来源
相关:这里可能发生的情况是,他们使用稍微不同的方法将数字转换为字符串,这会引入舍入错误。在编译期间将字符串转换为浮点值的方法在它们之间也可能不同,这同样会由于舍入而给出稍有不同的值 但是请记住,float的分数有24位精度,精确到~7.22个十进制数字[log10(2)*24],前7位在它们之间是一致的,所以最后几个最低有效位是不同的 欢迎来到有趣的浮点数学世界,2+2并不总是等于4。 在这种情况下,Java规范需要麻烦的双舍入。数字0.64469687499999947064566185872536126012420654296875首先转换为0.644696875,然后四舍五入为0.64469688 相比之下,C实现只是将0.6446968749999947064566185872536126012420654296875直接舍入到八位数字,生成0.64469687 预备赛 对于
Double
,Java使用IEEE-754基本64位二进制浮点。在此格式中,与源文本中的数字0.644696875最接近的值是0.6446968749999947064566185872536126012420654296875,我相信这是使用String.format(“%10.8f”,0.644696875)
格式化的实际值
Java规范所说的
说:
…如果精度小于分别由Float.toString(Float)
或Double.toString(Double)
返回的字符串中小数点后出现的位数,则将使用向上舍入算法对值进行舍入。否则,可以添加零以达到精度
让我们考虑“…返回的字符串<代码> double totostring(double)< /代码>。对于数字0.64469687499999947064566185872536126012420654296875,此字符串为“0.644696875”。这是因为Java规范说明了这一点,“0.644696875”在本例中只有足够的数字。2
这个数字在小数点后有九位,而“%10.8f”
要求八位,所以上面引用的段落说“值”是四舍五入的。它是指格式的实际操作数,即0.6446968749999947064566185872536126012420654296875,还是它提到的字符串“0.644696875”?由于后者不是数值,我希望“值”是指前者。但是,第二句说“否则[即,如果请求更多的数字],可能会追加零…”如果我们使用的是格式的实际操作数,我们将显示其数字,而不是使用零。但是,如果我们将字符串作为一个数值,那么它的十进制表示形式在显示的数字后面只有零。因此,这似乎是我们想要的解释,Java实现似乎也符合这一点
因此,要使用“%10.8f”
格式化此数字,我们首先将其转换为0.644696875,然后使用四舍五入规则对其进行四舍五入,从而生成0.64469688
这是一个糟糕的规范,因为:
- 它需要两个舍入,这会增加误差
- 环形交叉发生在难以预测和控制的地方。某些值将在小数点后两位舍入。有些将在13后四舍五入。一个程序不能很容易地预测或调整它
(还有,他们写了“可以”附加的零,这是一件很遗憾的事。为什么不“否则,零附加以达到精度”?“可以”似乎给了实现一个选择,尽管我怀疑他们的意思是“可以”是基于是否需要零来达到精度,而不是基于实现者是否选择附加它们。)
脚注
1当源文本中的0.644696875
转换为Double
时,我相信结果应该是Double
格式中可表示的最接近的值。(我没有在Java文档中找到这一点,但它符合要求实现行为相同的Java哲学,我怀疑转换是根据Double.valueOf(String s)
完成的。)与0.644696875最接近的Double
是0.6446968749999999470645661858725326012420654296875
2如果数字较少,则七位数字0.64469687不足,因为最接近它的双精度值为0.6446968699999997745192104048328474164009423828125。因此,需要8位数字来唯一区分0.64469687499999947064566185872536126012420654296875,请注意,问题可能与二进制表示无关,但与每种语言中的printf
函数的工作方式有关。默认舍入行为是一种有点随意的选择。Java,GNULIBC。重点是:Jav
#include <stdio.h>
int main()
{
printf("%10.8f", 0.644696875); //double to string
return 0;
}