通常商业计算涉及到小数的,我们都会使用 BigDecimal 来完成加减乘除运算。但是利用 BigDecimal 进行浮点数精确运算时,需要注意使用正确的方法。如果方法选择不当,依旧会发生错误。
发现问题
public class Test {public static void main(String[] args) {BigDecimal multiply = BigDecimal.valueOf(9.9f).multiply(BigDecimal.valueOf(100f));System.out.println(multiply.toString());BigDecimal multiply2 = new BigDecimal(Float.toString(9.9f)).multiply(BigDecimal.valueOf(100f));System.out.println(multiply2.toString());}}//result989.9999618530273000990.00
测试 BigDecimal 的两种构造方法,发现浮点运算的结果不同。利用 BigDecimal.valueOf 方法构造对象的方法,获得的浮点数发生了精度异常。利用 new BigDecimal(String val) 方法,运算正确。
分析原因
查看 BigDecimal.valueOf 源码,我们可以发现问题。BigDecimal.valueOf(9.9f) 实际调用的方法是 java.math.BigDecimal#valueOf(double)。如下图所示:
1 /**2 * Translates a {@code double} into a {@code BigDecimal}, using3 * the {@code double}'s canonical string representation provided4 * by the {@link Double#toString(double)} method.5 *6 * <p><b>Note:</b> This is generally the preferred way to convert7 * a {@code double} (or {@code float}) into a8 * {@code BigDecimal}, as the value returned is equal to that9 * resulting from constructing a {@code BigDecimal} from the10 * result of using {@link Double#toString(double)}.11 *12 * @param val {@code double} to convert to a {@code BigDecimal}.13 * @return a {@code BigDecimal} whose value is equal to or approximately14 * equal to the value of {@code val}.15 * @throws NumberFormatException if {@code val} is infinite or NaN.16 * @since 1.517 */18 public static BigDecimal valueOf(double val) {19 // Reminder: a zero double returns '0.0', so we cannot fastpath20 // to use the constant ZERO. This might be important enough to21 // justify a factory approach, a cache, or a few private22 // constants, later.23 return new BigDecimal(Double.toString(val));24 }
比我们想象的多了一步强制类型转换的动作。9.9f 会被强制转换成 double 类型。测试这个强制转换,我们可以发现问题,其实出在强制转换这里。强制转换结果如下图所示。
public class Test {public static void main(String[] args) {double v = 9.9f;System.out.println(v);double v2 = Double.valueOf(9.9f);System.out.println(v2);}}//result9.8999996185302739.899999618530273
拓展
假如参数是 double 类型,我们使用 BigDecimal.valueOf(…) 可以吗?
public class Test {public static void main(String[] args) {BigDecimal multiply3 = BigDecimal.valueOf(9.9d).multiply(BigDecimal.valueOf(100d));System.out.println(multiply3.toString());}}//result990.00
看结果是没问题的。但是,注意 BigDecimal.valueOf(…) 的源码,其内部依然是把入参转换了 double 数据对应的字符串。假如直接使用 new BigDecimal(double val) 构造函数来进行运算,则会发现计算结果发生来精度异常。如下图所示。
public class Test {public static void main(String[] args) {BigDecimal multiply4 = new BigDecimal(9.9d).multiply(BigDecimal.valueOf(100d));System.out.println(multiply4.toString());}}//result990.00000000000003552713678800500929355621337890625000
可以看到 jdk 内部的注释,已经说明,new BigDecimal(double val) 方法得到的结果是不可预知的。推荐使用入参类型为 String 的构造函数来进行浮点数的精确计算。
1 /**2 * Translates a {@code double} into a {@code BigDecimal} which3 * is the exact decimal representation of the {@code double}'s4 * binary floating-point value. The scale of the returned5 * {@code BigDecimal} is the smallest value such that6 * <tt>(10<sup>scale</sup> × val)</tt> is an integer.7 * <p>8 * <b>Notes:</b>9 * <ol>10 * <li>11 * The results of this constructor can be somewhat unpredictable.12 * One might assume that writing {@code new BigDecimal(0.1)} in13 * Java creates a {@code BigDecimal} which is exactly equal to14 * 0.1 (an unscaled value of 1, with a scale of 1), but it is15 * actually equal to16 * 0.1000000000000000055511151231257827021181583404541015625.17 * This is because 0.1 cannot be represented exactly as a18 * {@code double} (or, for that matter, as a binary fraction of19 * any finite length). Thus, the value that is being passed20 * <i>in</i> to the constructor is not exactly equal to 0.1,21 * appearances notwithstanding.22 *23 * <li>24 * The {@code String} constructor, on the other hand, is25 * perfectly predictable: writing {@code new BigDecimal("0.1")}26 * creates a {@code BigDecimal} which is <i>exactly</i> equal to27 * 0.1, as one would expect. Therefore, it is generally28 * recommended that the {@linkplain #BigDecimal(String)29 * <tt>String</tt> constructor} be used in preference to this one.30 *31 * <li>32 * When a {@code double} must be used as a source for a33 * {@code BigDecimal}, note that this constructor provides an34 * exact conversion; it does not give the same result as35 * converting the {@code double} to a {@code String} using the36 * {@link Double#toString(double)} method and then using the37 * {@link #BigDecimal(String)} constructor. To get that result,38 * use the {@code static} {@link #valueOf(double)} method.39 * </ol>40 *41 * @param val {@code double} value to be converted to42 * {@code BigDecimal}.43 * @throws NumberFormatException if {@code val} is infinite or NaN.44 */45 public BigDecimal(double val) {46 this(val,MathContext.UNLIMITED);47 }
总结
在使用 BigDecimal 进行科学运算的时候,我们需要清楚,BigDecimal.valueOf(…) 和 new BigDecimal(…) 的使用效果不同。当 BigDecimal.valueOf(…) 的入参是 float 类型时,BigDecimal 会把入参强制转换成 double 类型。
使用 new BigDecimal(String val) 来获得浮点数对应的 BigDecimal 对象,进而完成浮点数精确运算。
