将浮点数转换为双精度浮点数,而不丢失精度

124

我有一个基本类型的浮点数,现在需要转换为基本类型的双精度浮点数。简单地将浮点数强制转换为双精度浮点数会产生奇怪的额外精度。例如:

float temp = 14009.35F;
System.out.println(Float.toString(temp)); // Prints 14009.35
System.out.println(Double.toString((double)temp)); // Prints 14009.349609375

如果不使用强制类型转换,而是将浮点数输出为字符串,然后将其解析为双精度浮点数,就可以得到想要的结果:

System.out.println(Double.toString(Double.parseDouble(Float.toString(temp))));
// Prints 14009.35

有没有比先转成字符串再转回来更好的方法?

11个回答

143

这并不是说你实际上获得了额外的精度 - 而是浮点数无法准确地表示最初的目标数字。双精度则可以准确地表示原始浮点数;toString 显示的是已经存在的 "额外" 数据。

例如(这些数字不正确,只是为了举例说明),假设你有:

float f = 0.1F;
double d = f;

那么f的值可能恰好为0.100000234523。d将具有完全相同的值,但是当您将其转换为字符串时,它将“信任”它的精度更高,因此不会像之前那样早期地四舍五入,并且您将看到已经存在但对您隐藏的“额外数字”。

当您将其转换为字符串并再次进行转换时,您将得到一个双精度浮点数,该数更接近于字符串值,而原始的浮点数则不然——但前提是您确信字符串值是您真正想要的。

您确定使用float/double而不是BigDecimal是适当的吗?如果您正在尝试使用具有精确小数值(例如货币)的数字,则在我看来,BigDecimal是更适当的类型。


53

我发现将其转换为二进制表示更容易理解这个问题。

float f = 0.27f;
double d2 = (double) f;
double d3 = 0.27d;

System.out.println(Integer.toBinaryString(Float.floatToRawIntBits(f)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d2)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d3)));

通过在末尾添加0,可以将float扩展为double类型,但0.27的double表示“更准确”,因此出现了问题。

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000

27

这是由于Float.toString(float)的合约所决定的,其中一部分规定:

小数部分必须打印多少位[...]? 必须至少有一位数字来表示小数部分,并且除此之外,只有那么多位但仅有那么多位更多的数字才能唯一区分类型为float的相邻值和参数值。也就是说,假设x是由此方法产生的十进制表示形式所代表的有限非零参数f的精确数学值。那么f必须是最接近x的浮点数值;或者如果两个浮点数值等距离地接近x,则f必须是它们中的一个,并且f的尾数的最低有效位必须为0。


17

今天我遇到了这个问题,由于项目非常庞大,所以无法使用重构功能转换为BigDecimal。不过我找到了解决方案。

Float result = new Float(5623.23)
Double doubleResult = new FloatingDecimal(result.floatValue()).doubleValue()

这个可以运行。

注意调用result.doubleValue()返回5623.22998046875。

但是调用doubleResult.doubleValue()会正确返回5623.23。

但我不确定这是否是正确的解决方案。


1
对我很有效,而且速度非常快。不确定为什么这没有被标记为答案。 - Sameer
这是我使用的方法,你不需要新的Float部分...只需使用原始类型创建FloatingDecimal即可。它会自动装箱...而且速度也很快...唯一的缺点是,FloatingDecimal是不可变的,所以你需要为每个单独的float创建一个...想象一下O(1e10)的计算代码!!! 当然BigDecimal也有同样的缺点... - Mostafa Zeinali
10
这个解决方案的缺点是sun.misc.FloatingDecimal是JVM的内部类,在Java 1.8中它的构造函数签名已经发生了变化。在实际应用中,任何人都不应该使用内部类。 - Igor Bljahhin

12

我找到了以下的解决方案:

public static Double getFloatAsDouble(Float fValue) {
    return Double.valueOf(fValue.toString());
}

如果您使用 floatdouble 而不是 FloatDouble,请使用以下内容:

public static double getFloatAsDouble(float value) {
    return Double.valueOf(Float.valueOf(value).toString()).doubleValue();
}

7
请使用BigDecimal代替float/double。有很多数字无法表示为二进制浮点数(例如0.1)。因此,您必须始终将结果四舍五入到已知精度或使用BigDecimal
有关更多信息,请参见http://en.wikipedia.org/wiki/Floating_point

假设您的缓存中有10m个浮点交易价格,您是否仍然考虑使用BigDecimal并实例化唯一的,例如~6m个对象(以折扣飞行权/不可变性)...还是还有其他更多的因素? - Sendi_t
1
@Sendi_t 请不要使用注释来提出复杂问题 :-) - Aaron Digulla
谢谢您的关注!我们现在使用浮点数,因为这是十年前达成的共识。我想了解您对此情况的看法。谢谢! - Sendi_t
@Sendi_t 误解了。我无法在512个字符内回答你的问题。请提出一个恰当的问题并发送给我一个链接。 - Aaron Digulla
2
@GKFX 在处理计算机上的小数时,没有“一刀切”的方法。但由于大多数人不理解,我会指向BigDecimal,因为它可以捕捉到大多数常见错误。如果速度太慢,他们需要学习更多知识并找到优化问题的方法。过早地进行优化是万恶之源 - D.E. Knuth。 - Aaron Digulla
显示剩余3条评论

2
一种简单有效的解决方案是从浮点数的字符串表示中解析出双精度数:
double val = Double.valueOf(String.valueOf(yourFloat));

不是特别高效,但它可以工作!

1

浮点数本质上是不精确的,总会有舍入问题。如果精度很重要,那么您可能需要重新设计应用程序以使用 Decimal 或 BigDecimal。

是的,由于处理器支持,浮点数的计算速度比十进制数更快。但是,您想要快还是准确?


1
十进制算术也是不精确的(例如,1/3*3==0.9999999999999999999999999999)。当然,它更适合表示像钱这样的精确的十进制数量,但对于物理测量来说并没有优势。 - dan04
2
但是1 == 0.9999999999999999999999999999 :) - Emmanuel Bourg

1

关于此信息,它属于《Effective Java第二版》中的第48项 - 当需要精确值时避免使用float和double。这本书充满了有用的内容,绝对值得一看。


0

这个可以工作吗?

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接