在 Java 中使用 Double vs BigDecimal 解析双精度值

2022-09-03 07:48:04

我是一个有经验的开发人员,但不是数学专家。我对IEEE浮点规范有足够的了解,以至于害怕对解析,打印和比较它们做出假设。

我知道我可以从使用Double.parseDouble(String s)来解析a。我知道我也可以把同一个字符串解析成一个使用新的BigDecimal(String s),然后要求使用BigDecimal.doubleValue()doubleStringBigDecimalBigDecimaldouble

我瞥了一眼这两种技术的API和代码,似乎有很多不同的解析和转换选项。BigDecimal

对于所有字符串输入,这两种技术(和)是否保证产生完全相同的基元值,前提是该值不在加号或减号的范围之外?Double.parseDouble(s)new BigDecimal(s).doubleValue()doubleDouble.MAX_DOUBLE


答案 1

对于大多数输入值,这两种技术应生成相同的值。虽然他们仍然有可能不这样做,但似乎不太可能。

BigDecimal(String) 构造函数 Javadocs 指出:

接口说明:

对于 NaN 和 ±Infinity 以外的值,此构造函数与 和 返回的值兼容。floatdoubleFloat.toString(float)Double.toString(double)

但是,Double.parseDouble(String) 方法指出:

返回一个新的初始化为指定值,如类 的方法执行。doubleStringvalueOfDouble

这继续描述该方法接受的格式。

让我们来测试一下吧!

让我们测试一些值。详尽地测试这一点似乎是一项令人难以置信的巨大努力,但让我们测试一些字符串值,这些值表示已知会产生浮点错误或不精确的表示的值。

public static void main(String[] args)
{
    String[] values = {"0", "0.1", "0.33333333333333333333", "-0", "-3.14159265", "10.1e100",
            "0.00000000000000000000000000000000000000000000000000142857142857",
            "10000000000.000000000000000001", "2.718281828459",
            "-1.23456789e-123", "9.87654321e+71", "66666666.66666667",
            "1.7976931348623157E308", "2.2250738585072014E-308", "4.9E-324",
            "3.4028234663852886E38", "1.1754943508222875E-38", "1.401298464324817E-45",
            String.valueOf(Math.E), String.valueOf(Math.PI), String.valueOf(Math.sqrt(2))
    };
    for (String value : values) {
        System.out.println(isDoubleEqual(value));
    }
}
// Test if the representations yield the same exact double value.
public static boolean isDoubleEqual(String s) {
    double d1 = Double.parseDouble(s);
    double d2 = new BigDecimal(s).doubleValue();
    return d1 == d2;
}

对于这些值,我得到所有 s。这绝不是详尽无遗的,因此很难证明它适用于所有可能的值。只需要一个反例。但是,这似乎是一些证据,表明它适用于所有法律字符串陈述。truedoublefalsedouble

我还尝试了前导空格,例如.构造函数抛出了一个,但正确修剪了输入。" 4"BigDecimal(String)NumberFormatExceptionDouble.parseDouble

构造函数不会接受 或 ,但你只询问了正常的有限范围。该方法接受十六进制浮点表示形式,但不接受。BigDecimal(String)InfinityNaNDouble.parseDoubleBigDecimal(String)

如果包括这些边缘情况,则一种方法可能会引发异常,而另一种方法则不会。如果你正在寻找范围内有限值的正常 base-10 字符串,答案是“似乎很有可能”。


答案 2

对于所有字符串输入,这两种技术(和)是否都保证产生完全相同的双基元值,前提是该值不在加号或减号的范围之外?Double.parseDouble(s)new BigDecimal(s).doubleValue()Double.MAX_DOUBLE

不,当然不是。

首先,有一些字符串输入支持但不支持(例如,十六进制文本)。Double.parseDouble(s)new BigDecimal(s)

另一方面,产生零,而产生零(因为没有符号零的概念)。Double.parseDouble("-0")new BigDecimal("-0").doubleValue()BigDecimal

虽然与您的问题没有直接关系,但我被要求指出,为了其他支持NaN和无穷大的读者的利益,而事实并非如此。Double.parseDouble(s)BigDecimal