如何知道一个BigDecimal能否被精确转换为float或double?

12

BigDecimal类有一些有用的方法来保证不会出现精度丢失:

  • byteValueExact()
  • shortValueExact()
  • intValueExact()
  • longValueExact()

然而,方法floatValueExact()doubleValueExact()并不存在。

我查看了floatValue()doubleValue()的OpenJDK源代码。两者都回退到分别返回正无穷大或负无穷大的Float.parseFloat()Double.parseDouble()。例如,解析一个由10000个9组成的字符串将返回正无穷大。据我理解,BigDecimal没有内部概念是无穷大的。此外,将由100个9组成的字符串解析为double会得到1.0E100,它不是无穷大,但会丢失精度。

有什么合理的实现floatValueExact()doubleValueExact()的方法吗?

我曾考虑用BigDecimal.doubleValue()BigDecimal.toString()Double.parseDouble(String)Double.toString(double)相结合的方式来解决double的问题,但这看起来很混乱。我在这里提问,因为可能(一定!)有更简单的方法。

需要明确的是,我不需要高性能的解决方案。


8
我猜你可以将其转换为 double,然后再将 double 转换回 BigDecimal,看看是否得到相同的值。不过我不确定具体应用场景是什么。如果你使用 double,就已经接受了非精确值。如果你想要精确值,那就继续使用 BigDecimal。 - JB Nizet
2个回答

8
阅读 文档,所有它与numTypeValueExact变量所做的就是检查分数部分是否存在或值是否过大而抛出异常。
关于floatValue()doubleValue(),类似的溢出检查正在进行,但不是抛出异常,而是返回Double.POSITIVE_INFINITYDouble.NEGATIVE_INFINITY用于双精度浮点数,以及Float.POSITIVE_INFINITYFloat.NEGATIVE_INFINITY用于单精度浮点数。

因此,对于float和double的exact方法,最合理(也是最简单)的实现应该只检查转换是否返回POSITIVE_INFINITYNEGATIVE_INFINITY
此外,请记住,BigDecimal 旨在处理使用 floatdouble 处理大型无理数时出现的精度损失。因此,正如 @JB Nizet 评论 的那样,您可以添加另一个检查,即将 doublefloat 转换回 BigDecimal,以查看是否仍然获得相同的值。这应证明转换是正确的。

以下是 floatValueExact() 的方法示例:

public static float floatValueExact(BigDecimal decimal) {
    float result = decimal.floatValue();
    if (!(Float.isNaN(result) || Float.isInfinite(result))) {
        if (new BigDecimal(String.valueOf(result)).compareTo(decimal) == 0) {
            return result;
        }
    }
    throw new ArithmeticException(String.format("%s: Cannot be represented as float", decimal));
}

上面使用compareTo而不是equals是有意为之,以免检查过于严格。当两个BigDecimal对象具有相同的值和比例(小数部分的大小)时,equals仅会评估为true,而compareTo将在不重要时忽略此差异。例如2.02.00


非常巧妙。恰好是我需要的!注意:您也可以使用Float.isFinite()Float.isInfinite(),但这是可选的。 :) - kevinarpe
3
你应该更倾向于使用compareTo而不是equals,因为BigDecimal中的equals()方法会将2.0和2.00视为不同的值。 - JB Nizet
无法精确表示为浮点数的值将无法工作。例如:123.456f。我猜这是由于32位浮点数和64位双精度浮点数之间有效数字(尾数)大小的差异所致。在您上面的代码中,我使用以下代码可以获得更好的结果:if (new BigDecimal(result, MathContext.DECIMAL32).equals(decimal)) {。这是一个合理的更改吗...还是我错过了浮点数值的另一个特殊情况? - kevinarpe
1
@kevinarpe - 123.456f 的概念与您的 1.0E100 示例相同。我认为,如果您需要检查从 BigDecimal -> 二进制浮点数的转换是否精确,那么这种需求就是问题所在;即不应考虑进行转换。 - Stephen C
1
你必须使用isFinite(),因为还需要检查NaN。 - Eldar Agalarov
@EldarAgalarov 很好的发现。谢谢。 - smac89

0

这个答案的改进:

  1. BigDecimal.floatValue()不应该返回Float.NaN,因为没有BigDecimal.NaN
  2. String.valueOf(float) —或者更准确地说是Float.toString(float)—如果number < 10-3number ≥ 107,会切换到科学计数法,导致new BigDecimal(String)失败。
public static float floatValueExact(BigDecimal decimal) {
    float result = decimal.floatValue();
    if (Float.isInfinite(result) || new BigDecimal(result).compareTo(decimal) != 0) {
        throw new ArithmeticException(decimal + " cannot be represented as float");
    }
    return result;
}

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