当前位置: 首页 > 知识库问答 >
问题:

为什么C和Java的round floats不一样?

牟焱
2023-03-14

考虑浮点数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));
     }
}

结果:0.64469688

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

#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.64668799999

诚然,这是一个边缘案例。但我真的很好奇差异的来源。

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

共有2个答案

左丘嘉木
2023-03-14

这里可能发生的情况是,他们使用稍微不同的方法将数字转换为字符串,这引入了舍入误差。在编译过程中,字符串转换为浮点的方法也可能不同,由于舍入,它们的值也可能略有不同。

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

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

澹台星光
2023-03-14

在这种情况下,Java 规范需要麻烦的双舍五入。数字 0.6446968749999999470645661858725361526012420654296875 首先转换为 0.644696875,然后舍入为 0.64469688。

相比之下,C实现简单地将0.64469687499947064566185872536156012420654296875直接四舍五入到8位,产生0.6446 9687。

对于双精度型,Java 使用 IEEE-754 基本 64 位二进制浮点数。在此格式中,最接近源文本中数字的值 0.644696875 是 0.64469696874999999470645661858725361526012420654296875,我相信这是使用 String.format(“.8f”,0.644696875) 格式化的实际值。1

使用< code>Double类型和< code>f格式进行格式化的文档说明如下:

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

让我们考虑“由…< code > double . tostring(double)返回的字符串”。对于数字0.6446968749999999470645661858725361526012420654296875,此字符串为“0.644696875”。这是因为Java规范规定< code>toString产生的十进制数字刚好足以唯一区分< code>Double值集合中的数字,在这种情况下,“0.644696875”刚好有足够的数字。第2页

该数字在小数点后有九位数字,“.8f”请求八位,因此上面引用的段落说“值”是四舍五入的。它指的是哪个值 - 格式的实际操作数,即 0.64469696874999999470645661858725361526012420654296875,还是它提到的字符串“0.644696875”?由于后者不是数值,我本来以为“值”是指前者。但是,第二句话说“否则[即,如果请求更多数字],可能会附加零......”如果我们使用格式的实际操作数,我们将显示其数字,而不是使用零。但是,如果我们将字符串作为数值,则其十进制表示形式在中显示的数字后面将只有零。因此,这似乎是预期的解释,而Java实现似乎符合这一点。

因此,要使用".8f"格式化这个数字,我们首先将其转换为0.644696875,然后使用舍入半向上规则对其进行舍入,从而产生0.64469688。

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

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

(另外,很遗憾他们写了零“可能”附加。为什么不“否则,附加零以达到精度”?对于“may”,似乎他们给了实现一个选择,尽管我怀疑他们的意思是“可能”是基于是否需要零来达到精度,而不是基于实现者是否选择附加它们。

1当源代码中的0.644696875转换为Double时,我相信结果应该是Double格式中可表示的最接近值。(我没有在Java留档中找到它,但它符合要求实现行为相同的Java哲学,我怀疑转换是根据Double.valueOf(String s)完成的,它确实需要这个。)最接近0.644696875的Double是0.64469687499999470645661858725361526012420654296875。

2如果数字较少,七位数0.64469687是不够的,因为最接近它的Double值为0.6446968699999999774519210404832847416400909423828125。所以需要八位数字来唯一区分0.64469687499999470645661858725361526012420654296875。

 类似资料:
  • 问题内容: Ada,Pascal和许多其他语言都支持范围,这是对整数进行子类型的一种方式。范围是一个有符号整数值,范围从一个值(第一个)到另一个值(最后一个)。实现一个在OOP中执行相同操作的类很容易,但是我认为本机支持该功能可以使编译器进行其他静态检查。 我知道无法静态地验证范围内定义的变量不会“溢出”运行时(即由于输入错误),但是我认为可以做些什么。我考虑了按合同设计方法(Eiffel)和Sp

  • 问题内容: 多年前,当我开始面向对象编程时,给人的印象是变量(如果是正确的词)是“原始”(int,double等)或一流对象(String,JPane等)。最近关于Java和C#中的基元的答案对此予以加强(@DanielPryden:Java和C#中的基元类型是否不同?。但是,不知道C#ValueTypes是基元,对象还是其他野兽(例如第二类对象)。我看到SO只能使用标签的一种,因此也许它不再是一

  • 为什么mysql中IS TRUE和=True的结果不一样? 我有一张user表,结构如下: 表中数据如下: 我尝试查询sql: 结果为: 查询sql: 结果为: 我用true或者false作为查询条件是因为:在java中tinyint能被转成Boolean,所以在查询的时候能直接传true或者false 有没有大佬知道为什么结果不一样呢?

  • 问题内容: 我们注意到,用C#(或Java)开发的软件中的许多错误都导致NullReferenceException。 为什么在语言中甚至包含了“ null”? 毕竟,如果没有“ null”,那么我就不会有错误,对吧? 换句话说,如果没有null,该语言的什么功能将无法正常工作? 问题答案: “ C#父亲” Anders Hejlsberg在他的《计算机世界》采访中谈到了这一点: 例如,在类型系统

  • 我编写了一个名为“dateutils”的静态类,这里是一个名为“parsedate(String)”的静态方法,它将使用一些模式将字符串转换为日期。默认时区为亚洲/上海(我在中国) 并将其传递给方法timeZone.setDefault(timeZone);并使用SimpleDateFormat来协调字符串。匹配的模式应该是“EEE,dd MMM yyyy HH:MM:SS ZZZ”,这里有三个测

  • 问题内容: 我知道前缀和posfix操作… i和i 之间的区别等等。 但是我想我在这里想不到的东西。在下面可以找到代码: 因此输出为: 我在C中尝试了相同的代码: 输出为: 为什么在C中相同的代码增加值时却不增加? 问题答案: 爪哇 在Java中,表达式具有明确定义的含义。复合赋值运算符的规范说: 形式为 E1 op = E2 的复合赋值表达式等效于 E1 =(T)((E1)op(E2)) ,其中