是否有可能一个完全表示为float的数字不能完全表示为double?

时间:2012-06-08 22:22:16

标签: java floating-point double

我有一个问题是由另一个关于浮点数精度的问题引起的。

现在,我知道浮点数不能总是准确地表示,因此它们被存储为可以表示的最接近的浮点数。

我的问题实际上是关于floatdouble的表示形式的差异。

这个问题来自哪里?

假设我这样做:

System.out.println(.475d+.075d);

然后输出不是0.55而是0.549999(在我的机器上)

然而,当我这样做时:

System.out.println(.475f+.075f);

我得到了正确答案,即0.55(对我来说有点意外)

到目前为止,我认为double float具有更高的精确度( double将更精确到更长的小数位数precision。因此,如果无法精确表示double,则其等效浮点表示也将被错误地存储。

然而,我得到的结果对我来说有点令人不安。我很困惑,如果:

  1. 我对float的含义有不正确的理解?
  2. 除了double有更多位之外,
  3. double和{{1}}的表示方式不同?

3 个答案:

答案 0 :(得分:8)

可以表示为float的数字也可以表示为double

您所阅读的只是格式化的输出,您不会读取实际的二进制表示。

System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(.475d + .075d)));
// 11111111100001100110011001100110011001100110011001100110011001
System.out.println(Integer.toBinaryString(Float.floatToRawIntBits(.475f + .075f)));
// 111111000011001100110011001101

double d = .475d + .075d;
System.out.println(d);
// 0.5499999999999999
System.out.println((float)d);
// 0.55 (as expected)
System.out.println((double)(float)d);
// 0.550000011920929

System.out.println( .475f + .075f == 0.550000011920929d);
// true

答案 1 :(得分:5)

精确只意味着更多的比特。无法表示为float 的数字可能具有double的精确表示形式,但相对于可能的总数,这些数字的数量无限小例。

对于像0.1这样的简单情况,无论可用的位数是多少,都不能表示为固定长度的浮点数。这与使用1/7这样的分数无法用十进制精确表示相同,无论您允许使用多少位数(只要数字位数是有限的)。您可以将其近似为0.142857142857142857 ...反复重复,但无论您持续多久,您都无法完全写出来。

相反,如果一个数字可以完全表示为float,那么它也可以完全表示为double。 double具有更大的指数范围和更多的尾数位。

对于您的示例,明显差异的原因是在float中,0.475与其浮点表示之间的差异处于“正确”方向,因此当截断发生时,它达到了您的预期。当增加可用的精度时,表示“更接近”到0.475但现在在相反的一侧。作为一个很好的例子,假设最接近的浮点数为0.475006,但在最接近的可能值为0.474999时。这会给你看到的结果。

编辑:以下是快速实验的结果:

public class Test {

    public static void main(String[] args)
    {
        float  f = 0.475f;
        double d = 0.475d;

        System.out.printf("%20.16f", f);
        System.out.printf("%20.16f", d);
    }
}

输出:

  0.4749999940395355  0.4750000000000000

这意味着数字0.475的浮点表示,如果你有大量的位,将只有一点点小于0.475。这在双重表示中看到。但是,第一个'错误'位出现在右边,当截断到适合float时,它恰好会达到0.475。这纯属意外。

答案 2 :(得分:1)

如果有人认为浮点类型实际上代表值的范围,而不是离散值(例如0.1f不代表13421773/134217728,而是“13421772.5 / 134217728和13421773.5 / 134217728之间的东西”) ,从doublefloat的转换通常是准确的,而从floatdouble的转换通常不会。遗憾的是,Java允许隐式执行通常不准确的转换,同时需要在通常精确的方向上进行类型转换。

对于float类型的每个值,都存在double类型的值,其范围以float范围的中心为中心。这并不意味着double是浮点数值的准确表示。例如,将0.1f转换为double会产生一个值,意思是“13421772.9999999 / 134217728和13421773.0000001 / 134217728之间的某个值”,这个值超出隐含容差的一百万倍。

对于double类型的几乎所有值,都存在float类型的值,其范围完全包含double隐含的范围。唯一的例外是值,其范围精确地集中在两个float值之间的边界上。将这些值转换为float将要求系统选择一个范围或另一个范围;如果系统在double实际表示低于其范围中心的数字时向上舍入,反之亦然,则float的范围不会完全包含double的范围。但实际上,这是一个非问题,因为它意味着代表范围的float而不是double强制转换(13421772.5 / 134217728到13421773.5 / 134217728),它代表一个范围如(13421772.4999999 / 134217728至13421773.5000001 / 134217728)。与floatdouble演员造成的可怕的不精确性相比,这种微小的不精确性是没有的。

顺便说一句,回到您正在使用的特定数字,当您将计算作为浮点数时,计算是:

0.075f = 20132660±½ / 268435456
0.475f = 31876710±½ /  67108864
Sum    = 18454938±½ /  33554432

换句话说,总和表示介于大约0.54999999701和0.55000002682之间的数字。最自然的表示是0.55(因为实际值可能多于或少于那个,附加数字将毫无意义)。