Java中BigDecimal解析

来源:互联网 发布:淘宝活动报名平台 编辑:程序博客网 时间:2024/05/22 06:47
 

java.math
类 BigDecimal

java.lang.Object  继承者 java.lang.Number       继承者 java.math.BigDecimal
所有已实现的接口:
Serializable, Comparable<BigDecimal>

public classBigDecimal 
extends Number 
implements Comparable<BigDecimal> 
 
BigDecimal 是Java提供的一个不可变的、任意精度的有符号十进制数。BigDecimal 由任意精度的整数非标度值 和 32 位的整数标度 (scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负 scale 次幂。因此,BigDecimal 表示的数值是 (unscaledValue × 10-scale)

    BigDecimal 类提供以下操作:算术、标度操作、舍入、比较、哈希算法和格式转换。toString() 方法提供BigDecimal 的规范表示形式。

    BigDecimal 类使用户能完全控制舍入行为。如果未指定舍入模式,并且无法表示准确结果,则抛出一个异常;否则,通过向该操作提供适当的 MathContext 对象,可以对已选择的精度和舍入模式执行计算。在任何情况下,可以为舍入控制提供八种舍入模式。使用此类(例如,ROUND_HALF_UP)中的整数字段来表示舍入模式已过时;应改为使用 RoundingMode enum(例如,RoundingMode.HALF_UP)的枚举值。

    当为 MathContext 对象提供 0 的精度设置(例如,MathContext.UNLIMITED)时,算术运算是准确的,它们是不采用任何 MathContext 对象的算术方法。(这是第 5 版之前的版本支持的惟一行为。)为了计算准确结果,不使用附带 0 精度设置的 MathContext 对象的舍入模式设置,因此与该对象无关。在除法中,准确的商可能是一个无限长的十进制扩展;例如,1 除以 3 所得的商。如果商具有无穷的十进制扩展,但是指定了该操作返回准确结果,则抛出ArithmeticException。否则,像其他操作那样,返回除法运算的准确结果。

    当精度设置不为 0 时,BigDecimal 算法的规则完全符合 ANSI X3.274-1996 和 ANSI X3.274-1996/AM 1-2000( 7.4 节)中定义的算法的可选操作模式。与上述标准不同,BigDecimal 包括多种舍入模式,它们对于版本 5 以前的BigDecimal 版本中的除法是强制性的。这些 ANSI 标准和 BigDecimal 规范之间的任何冲突都按照有利于BigDecimal 的方式进行解决。

    由于同一数值可以有不同的表示形式(具有不同的标度),因此运算和舍入的规则必须同时指定数值结果和结果表示形式中所用的标度。

    一般情况下,当准确结果(在除法中,可能有无限多位)比返回的数值具有更多位数时,舍入模式和精度设置确定操作如何返回具有有限位数的结果。 首先,MathContext 的 precision 设置指定要返回的总位数;这确定了结果的精度。位数计数从准确结果的最左边的非零数字开始。舍入模式确定丢弃的尾部位数如何影响返回的结果。

    对于所有算术运算符,运算的执行方式是,首先计算准确的中间结果,然后,使用选择的舍入模式将其舍入为精度设置(如有必要)指定的位数。如果不返回准确结果,则将丢弃准确结果的某些数位。当舍入增加了返回结果的大小时,前导数字“9”的进位传播可能会创建新的数位。例如,将值 999.9 舍入为三位数字,则在数值上等于一千,表示为 100×101。在这种情况下,新的 "1" 是返回结果的前导数位。

除了逻辑的准确结果外,每种算术运算都有一个表示结果的首选标度。下表列出了每个运算的首选标度。
运算结果的首选标度Addmax(addend.scale(), augend.scale())Subtractmax(minuend.scale(), subtrahend.scale())Multiplymultiplier.scale() + multiplicand.scale()Dividedividend.scale() - divisor.scale()
这些标度是返回准确算术结果的方法使用的标度;准确相除可能必须使用较大的标度除外,因为准确的结果可能有较多的位数。例如,1/32 得到 0.03125

舍入之前,逻辑的准确中间结果的标度是该运算的首选标度。如果用 precision 位数无法表示准确的数值结果,则舍入会选择要返回的一组数字,并将该结果的标度从中间结果的标度减小到可以表示实际返回的 precision 位数的最小标度。如果准确结果可以使用最多 precision 个数字表示,则返回具有最接近首选标度的标度的结果表示形式。尤其是,通过移除结尾零并减少标度,可以用少于 precision 个数字来表示准确的可表示的商。例如,使用 floor 舍入模式将结果舍入为三个数字,
19/100 = 0.19 // integer=19, scale=2 
但是
21/110 = 0.190 // integer=190, scale=3
    注意,对于加、减和乘,标度的缩减量将等于丢弃的准确结果的数字位置数。如果舍入导致进位传播创建一个新的高位,则当未创建新的数位时,会丢弃该结果的附加数字。

    其他方法可能与舍入语义稍微不同。例如,使用指定的算法的 pow 方法得到的结果可能偶尔不同于舍入得到的算术结果,如最后一位有多个单位(ulp)。

    可以通过两种类型的操作来处理 BigDecimal 的标度:标度/舍入操作和小数点移动操作。标度/舍入操作(setScale和 round)返回 BigDecimal,其值近似地(或精确地)等于操作数的值,但是其标度或精度是指定的值;即:它们会增加或减少对其值具有最小影响的存储数的精度。小数点移动操作(movePointLeft 和 movePointRight)返回从操作数创建的 BigDecimal,创建的方法是按指定方向将小数点移动一个指定距离。

    为了简洁明了起见,整个 BigDecimal 方法的描述中都使用了伪代码。伪代码表达式 (i + j) 是“其值为BigDecimal i 加 BigDecimal j 的 BigDecimal”的简写。伪代码表达式 (i == j) 是“当且仅当 BigDecimal i 表示与 BigDecimal j 相同的值时,则为 true”的简写。可以类似地解释其他伪代码表达式。方括号用于表示特定的BigInteger 和定义 BigDecimal 值的标度对;例如,[19, 2] 表示 BigDecimal 在数值上等于 0.19,标度是 2。

    注:如果 BigDecimal 对象用作 SortedMap 中的键或 SortedSet 中的元素,则应特别小心,因为 BigDecimal 的自然排序与 equals 方法不一致。有关更多信息,请参见 ComparableSortedMap 或 SortedSet

    当为任何输入参数传递 null 对象引用时,此类的所有方法和构造方法都将抛出 NullPointerException

另请参见:
BigIntegerMathContextRoundingModeSortedMapSortedSet, 序列化表格


BigDecimal扩展:
    BigDecimal是Java提供的一个不变的、任意精度的有符号十进制数对象。它提供了多个个构造器,在这里我们不关心,我们重点看用double和String构造的两个构造器。
    BigDecimal(double)是把一个double类型十进制数构造为一个BigDecimal对象实例。
    BigDecimal(String)是把一个以String表示的BigDecimal对象构造为BigDecimal对象实例。
    习惯上,对于浮点数我们都会定义为double或float,但BigDecimal API文档中对于BigDecimal(double)有这么一段话:
    Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .10000000000000000555111512312578 27021181583404541015625. This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances notwithstanding.
    The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(".1") isexactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one

    下面对这段话做简单解释:
    注意:这个构造器的结果可能会有不可预知的结果。有人可能设想new BigDecimal(.1)等于.1是正确的,但它实际上是等于.1000000000000000055511151231257827021181583404541015625,这就是为什么.1不能用一个double精确表示的原因,因此,这个被放进构造器中的长值并不精确的等于.1,尽管外观看起来是相等的。
    然而(String)构造器,则完全可预知的,new BigDecimal(“.1”)如同期望的那样精确的等于.1,因此,(String)构造器是被优先推荐使用的。
    看下面的结果:
            System.out.println(new BigDecimal(123456789.02).toString());
            System.out.println(new BigDecimal("123456789.02").toString());
    输出为:
            123456789.01999999582767486572265625
            123456789.02
    现在我们知道,如果需要精确计算,非要用String来够造BigDecimal不可!


实现方案
    现在我们已经知道怎么解决这个问题了,原则上是使用BigDecimal(String)构造器,我们建议,在商业应用开发中,涉及金额等浮点数计算的数据,全部定义为String,数据库中可定义为字符型字段,在需要使用这些数据进行运算的时候,使用BigDecimal(String)构造BigDecimal对象进行运算,保证数据的精确计算。同时避免了科学记数法的出现。如果科学记数表示法在应用中不是一种负担的话,可以考虑定义为浮点类型。这里我们提供了一个工具类,定义浮点数的加、减、乘、除和四舍五入等运算方法。以供参考。

源文件MathExtend.java:

import java.math.BigDecimal;

public class MathExtend {
// 默认除法运算精度
private static final int DEFAULT_DIV_SCALE = 10;

public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}

public static String add(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).toString();
}

public static double subtract(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}

public static String subtract(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).toString();
}

public static double multiply(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}

public static String multiply(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).toString();
}

public static double divide(double v1, double v2) {
return divide(v1, v2, DEFAULT_DIV_SCALE);
}

public static double divide(double v1, double v2, int scale) {
return divide(v1, v2, scale, BigDecimal.ROUND_HALF_EVEN);
}

public static double divide(double v1, double v2, int scale, int round_mode) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, round_mode).doubleValue();
}

public static String divide(String v1, String v2) {
return divide(v1, v2, DEFAULT_DIV_SCALE);
}

public static String divide(String v1, String v2, int scale) {
return divide(v1, v2, DEFAULT_DIV_SCALE, BigDecimal.ROUND_HALF_EVEN);
}

public static String divide(String v1, String v2, int scale, int round_mode) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.divide(b2, scale, round_mode).toString();
}

public static double round(double v, int scale) {
return round(v, scale, BigDecimal.ROUND_HALF_EVEN);
}

public static double round(double v, int scale, int round_mode) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
return b.setScale(scale, round_mode).doubleValue();
}

public static String round(String v, int scale) {
return round(v, scale, BigDecimal.ROUND_HALF_EVEN);
}

public static String round(String v, int scale, int round_mode) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(v);
return b.setScale(scale, round_mode).toString();
}
}

原创粉丝点击