为什么C和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.
相关文章