我有一个问题是由另一个关于浮点数精度的问题引起的。
现在,我知道浮点数不能总是准确地表示,因此它们被存储为可以表示的最接近的浮点数。
我的问题实际上是关于float
和double
的表示形式的差异。
这个问题来自哪里?
假设我这样做:
System.out.println(.475d+.075d);
然后输出不是0.55
而是0.549999
(在我的机器上)
然而,当我这样做时:
System.out.println(.475f+.075f);
我得到了正确答案,即0.55
(对我来说有点意外)
到目前为止,我认为double
float
具有更高的精确度( double将更精确到更长的小数位数)precision
。因此,如果无法精确表示double,则其等效浮点表示也将被错误地存储。
然而,我得到的结果对我来说有点令人不安。我很困惑,如果:
float
的含义有不正确的理解?double
和{{1}}的表示方式不同?答案 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之间的东西”) ,从double
到float
的转换通常是准确的,而从float
到double
的转换通常不会。遗憾的是,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)。与float
到double
演员造成的可怕的不精确性相比,这种微小的不精确性是没有的。
顺便说一句,回到您正在使用的特定数字,当您将计算作为浮点数时,计算是:
0.075f = 20132660±½ / 268435456 0.475f = 31876710±½ / 67108864 Sum = 18454938±½ / 33554432
换句话说,总和表示介于大约0.54999999701和0.55000002682之间的数字。最自然的表示是0.55(因为实际值可能多于或少于那个,附加数字将毫无意义)。