Java 是否有可能一个精确表示为float的数字不能精确表示为double?
我有一个问题是由另一个关于浮点数精度的问题引起的 现在,我知道浮点不能总是准确地表示,因此它们被存储为可以表示的最接近的浮点数 我的问题实际上是关于Java 是否有可能一个精确表示为float的数字不能精确表示为double?,java,floating-point,double,Java,Floating Point,Double,我有一个问题是由另一个关于浮点数精度的问题引起的 现在,我知道浮点不能总是准确地表示,因此它们被存储为可以表示的最接近的浮点数 我的问题实际上是关于float和double表示的差异 这个问题从何而来? 假设我这样做: System.out.println(.475d+.075d); 然后输出将不是0.55,而是0.549999(在我的机器上) 然而,当我这样做时: System.out.println(.475f+.075f); 我得到了正确的答案,即0.55(对我来说有点意外) 到目前为
float
和double
表示的差异
这个问题从何而来?
假设我这样做:
System.out.println(.475d+.075d);
然后输出将不是0.55
,而是0.549999
(在我的机器上)
然而,当我这样做时:
System.out.println(.475f+.075f);
我得到了正确的答案,即0.55
(对我来说有点意外)
到目前为止,我的印象是,double
比float
更精确(double将更精确,小数位数更长)。因此,如果不能精确地表示double,那么它的等效浮点表示也将被不准确地存储
然而,我得到的结果让我有点不安。如果:
精度的含义理解不正确
float
和double
的表示方式不同,除了double有更多位这一事实之外精确只意味着更多的比特。不能表示为
浮点
的数字可能精确表示为双精度
,但这些情况的数量相对于可能情况的总数而言是无限小的
对于像0.1
这样的简单情况,无论可用的位数是多少,它都不能表示为固定长度的浮点数。这等于说,无论允许使用多少位数(只要位数是有限的),分数(如1/7)都不能用十进制精确表示。您可以将其近似为0.142857142857142857。。。一次又一次地重复,但无论你坚持多久,你都无法准确地写出它
相反,如果一个数字可以精确地表示为浮点
,那么它也可以精确地表示为双精度
。double具有更大的指数范围和更多的尾数位
例如,造成明显差异的原因是,在float
中,0.475与其float表示之间的差异是在“正确”的方向上,因此当发生截断时,它按照您的预期进行。当增加可用的精度时,表示“更接近”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小一点点。这可以在双重表示中看到。然而,第一个“错误”位出现在最右边,当被截断以适应
浮点值时,恰好计算为0.475。这纯粹是一个意外。一个可以表示为浮点数的数字也可以表示为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
如果人们认为浮点类型实际上代表值的范围,而不是离散值(例如,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
值之间的边界上的值。将这些值转换为浮点值需要系统选择一个或另一个范围;如果当double
实际表示低于其范围中心的数字时系统向上取整,或者反之亦然,float
的范围不会完全包含double
的范围。但实际上,这不是一个问题,因为这意味着从表示类似(13421772.5/134217728到13421773.5/134217728)的范围(13421772.499999/134217728到13421773.5000001/134217728)的double
转换而来的float
将表示类似(13421772.499999/134217728到13421773.5000001/134217728)的范围。与由float
到double
转换产生的可怕的不精确性相比,这种微小的不精确性算不了什么
顺便说一句,回到您使用的特定数字,当您以浮点形式进行计算时,计算结果如下:
0.075f = 20132660±½ / 268435456
0.475f = 31876710±½ / 67108864
Sum = 18454938±½ / 33554432
0.075f=20132660±½/268435456
0.475f=31876710±½/67108864
总和=18454938±½/33554432
换句话说,总和代表一个数字