Java-BigDecimal学习

1. BigDecimal简介

  Java中,如果涉及到大的整数或者大的小数的计算的时候,比如long整数与long整数的相乘,金额的精确计算等操作,会用到BigIntegerBigDecimal这两种类型。其中BigInteger是针对任意整数的运算,而BigDecimal则是针对任意浮点数的运算。本篇文章我们来简单学习下它们的用法。

2. BigDecimal用法
2.1 BigDecimal继承结构及构造方法
public class BigDecimal extends Number implements Comparable<BigDecimal> {

可以看到,BigDecimal继承了Number类,并且实现了Comparable接口,说明该对象是支持排序的。至于Number类,我们来简单看下:

public abstract class Number implements java.io.Serializable {
  
    public abstract int intValue();
    public abstract long longValue();
    public abstract float floatValue();
    public abstract double doubleValue();

    public byte byteValue() {
        return (byte)intValue();
    }

    public short shortValue() {
        return (short)intValue();
    }
}

根据Api文档上介绍,该类作为数值类型的类的基类,主要是提供了将包装类拆箱成基本类型的方法,其中数值相关的类基本都实现了该抽象类,比如DoubleInteger等类。

再来看下BigDecimal的构造方法:

public BigDecimal(String val)
public BigDecimal(char[] in)
public BigDecimal(double val)
public BigDecimal(BigInteger val)
public BigDecimal(int val) 
public BigDecimal(long val)

  由于BigDecimal的构造方法特别多,这里就不一一介绍了,这里只说一点,就是不建议使用参数是double类型的构造方法了,因为double本身就不是精确的,转换成BigDecimal之后同样也不是精确的。如果非要使用double类型的话,建议先通过Double.toString(double)转成String或者通过BigDecimal的静态方法valueof来进行操作。

2.2 BigDecimal变量

这里列举下主要用到的变量:

// 维护了一个BigInteger变量,当有效位数超过18位的时候会有值
private final BigInteger intVal;
// 小数位数
private final int scale;
// 精度,该数字的长度
private transient int precision;
// 数值0
public static final BigDecimal ZERO = zeroThroughTen[0];
// 数值1
public static final BigDecimal ONE = zeroThroughTen[1];
// 数值10
public static final BigDecimal TEN = zeroThroughTen[10];

这里主要说两个变量,scale表示小数点右边的位数,而precision表示精度,也就是有效数字的长度,这两个数值和符号位都没关系。比如1.234,该数的scale是3,而precision是4。

2.3 Rounding mode 舍入规则或者舍入模式

对于小数,我们在进行操作的时候一般都会涉及到舍入规则,比如我们所了解的四舍五入法,进一法等。在BigDecimal中定义了8中舍入规则,我们来简单看下:

public final static int ROUND_UP =           0;

public final static int ROUND_DOWN =         1;

public final static int ROUND_CEILING =      2;

public final static int ROUND_FLOOR =        3;

public final static int ROUND_HALF_UP =      4;

public final static int ROUND_HALF_DOWN =    5;

public final static int ROUND_HALF_EVEN =    6;

public final static int ROUND_UNNECESSARY =  7;

关于舍入模式,直接看下RoundingMode的文档所介绍的:

Input Number UP DOWN CEILING FLOOR HALF_UP HALF_DOWN HALF_EVEN UNNECESSARY
5.5 6 5 6 5 6 5 6 throw ArithmeticException
2.5 3 2 3 2 3 2 2 throw ArithmeticException
1.6 2 1 2 1 2 2 2 throw ArithmeticException
1.1 2 1 2 1 1 1 1 throw ArithmeticException
1.0 1 1 1 1 1 1 1 1
-1.0 -1 -1 -1 -1 -1 -1 -1 -1
-1.1 -2 -1 -1 -2 -1 -1 -1 throw ArithmeticException
-1.6 -2 -1 -1 -2 -2 -2 -2 throw ArithmeticException
-2.5 -3 -2 -2 -3 -3 -2 -2 throw ArithmeticException
-5.5 -6 -5 -5 -6 -6 -5 -6 throw ArithmeticException

这个表格基本上描述了这几种舍入模式的区别,我们再来简单说下这几种模式,不过先说一个小问题,先猜测下如下代码的输出:

BigDecimal bd = new BigDecimal("6.45007");
bd = bd.setScale(3, BigDecimal.ROUND_UP);
System.out.println(bd);

没错,你猜的很正确,就是打印:6.451,至于原因么,我猜测应该是在ROUND_UP的文档上:

Rounding mode to round away from zero. Always increments the digit prior to a nonzero discarded fraction. Note that this rounding mode never decreases the magnitude of the calculated value.

也就是说四舍五入的时候,总是以最后一个非0的数来计算舍入。由于我英文不咋地,这里就不翻译了,来简单了解下这几项吧:

  1. ROUND_UP,向远离零的方向舍入。非标准的四舍五入,这个和ROUND_HALF_UP有点像,不过对负数的处理是不同的;
  2. ROUND_DOWN,向接近零的方向舍入。也就是将指定位数之后的全部舍掉,相当于截取操作;
  3. ROUND_CEILING, 向正无穷大的方向舍入。其实也就是进一法,对于指定位数之后的非0的全部加1;或者另一种说法,如果 BigDecimal 为正,则舍入行为与ROUND_UP 相同,如果为负,则舍入行为与 ROUND_DOWN 相同;
  4. ROUND_FLOOR,如果 BigDecimal 为正,则舍入行为与 ROUND_DOWN 相同,如果为负,则舍入行为与 ROUND_UP 相同;
  5. ROUND_HALF_UP,标准的四舍五入,官方文档还特地说明了下:Note that this is the rounding mode that most of us were taught in grade school.(注意一下,这是我们大多数人在小学时候所学的舍入模式)
  6. ROUND_UNNECESSARY,该模式表示该数值是精确的,如果某一个数值指定了该模式,并且不是精确的,则会抛出异常;
  1. ROUND_HALF_DOWN,其实这种有时候也被称为五舍六入,也就是大于5的时候舍入,小于等于5的时候舍弃。比如1.64250,保留3位小数的话,该舍入模式的值就是1.642,因为0.50 = 0.5;而如果1.64251,因为0.51 > 0.5,那么就是舍入,就是1.643;该模式和ROUND_HALF_UP模式不同的地方就是对5的操作。
  1. ROUND_HALF_EVEN,这个有点意思,该模式被称为银行家舍入(至于为什么叫银行家舍入,原因好像是美国的一个银行家提出的该算法),四舍六入,如果是5的话,要看5后面一位是否是0,如果不是0,舍入的时候进1;如果是0,然后看5之前的数是奇书还是偶数,奇书同样是进一,偶数的话就舍去。简单的说,就是:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。如要了解更多,可以使用Google百度一下:银行家舍入。

看个简单例子:

public static void main(String[] args) {
    // 使用 1.64250  1.64251   1.64350  1.64351进行测试
    BigDecimal bd1 = new BigDecimal("1.64250");
    BigDecimal bd2 = new BigDecimal("1.64251");
    BigDecimal bd3 = new BigDecimal("1.64350");
    BigDecimal bd4 = new BigDecimal("1.64351");
    // output:  1.642
    System.out.println(bd1.setScale(3, RoundingMode.HALF_EVEN));
    // output:  1.643
    System.out.println(bd2.setScale(3, RoundingMode.HALF_EVEN));
    // output:  1.644
    System.out.println(bd3.setScale(3, RoundingMode.HALF_EVEN));
    // output:  1.644
    System.out.println(bd4.setScale(3, RoundingMode.HALF_EVEN));
}
2.4 一些常用的方法

BigDecimal提供了一些常用的方法,我们来了解下:

  1. valueof方法,将对应类型转为BigDecimal,前面也说过,如果需要将double类型转为BigDecimal的话,可以使用该方法,而不用构造方法,不过该方法内部还是通过将字符串作为构造方法的参数的形式来实现的。
public static BigDecimal valueOf(double val) {
    // Reminder: a zero double returns '0.0', so we cannot fastpath
    // to use the constant ZERO.  This might be important enough to
    // justify a factory approach, a cache, or a few private
    // constants, later.
    return new BigDecimal(Double.toString(val));
}
  1. add方法,表示两个数值类型的加法运算操作;
  2. subtract方法,表示两个数值类型的减法运算操作;
  3. multiply方法,表示两个数值类型的乘法操作;
  4. divide方法,表示两个数值类型的除法操作;
  1. divideToIntegralValue方法,返回两个数值进行除法操作之后,也就是商的整数部分,而该返回值BigDecimal的scale,也就是小数位数 = (this.scale() - divisor.scale()),也就是除数的位数 - 被除数的位数,比如 12.345 除于 3.00,结果是4.0;
  2. remainder方法,计算两个数值的余数,该值有可能是负值,比如12.345 与3.00的余数,结果是0.345,计算方式是通过this.subtract(this.divideToIntegralValue(divisor).multiply(divisor))得到的;
  3. divideAndRemainder方法,返回一个数组,包含了两个数值除法之后得到的商的整数和余数。注意如果同时需要整数商和余数,这个方法比单独使用divideToIntegralValue和余数方法要快,因为除法只需要执行一次。
  4. pow(n)方法,精确的计算幂的操作,也就是thisn,参数n的范围是0到999999999,如计算3.00的3次方,结果是27.000000;
  5. abs方法,计算绝对值操作;negate方法,取反操作;max方法,获取最大值;min方法,获取最小值;
  1. longValueExact方法,除了常规的longValue,intValue之外,该类还提供了一种精确转换的方法,该方法表示如果要转换的BigDecimal有一个非零小数部分或者长度超出了long类型的范围,就会抛出一个异常,而不会像longValue方法那样进行舍弃。
  2. scale方法,获取小数位数;precision,获取精度;
  3. unscaledValue方法,获取小数值,比如对0.1234调用该方法,获取到的结果是1234,计算方式:this * 10this.scale()
  4. setScale方法,设置小数位数,注意下就是由于BigDecimal的不可变性,该方法和常规setX设置字段有些不同,会返回一个新的对象;
  5. signum方法,返回该数值的正负号,正数,0,负数分别对应返回结果:1,0,-1;
  6. movePointLeft,小数点左移,movePointRight,小数点右移;
  7. scaleByPowerOfTen,返回对象与10的幂的乘积,计算方式:this * 10n
  8. stripTrailingZeros方法,去除末尾多余的0,用科学记数法表示;
  9. compareTo方法,用于比较大小,同样返回1,0,-1;
  1. toStringtoPlainStringtoEngineeringString方法,这三个方法都是用于BigDecimal的字符串表示,不同的是toString有可能会使用科学记数法,toPlainString只展示数值,不使用科学记数法,toEngineeringString工程计数法,与科学技术法类似,但要求10的幂必须是3的倍数;
toPlainString toString toEngineeringString
1000 1 * 103 1 * 103
10000 1 * 104 10 * 103
100000 1 * 105 100 * 103
1000000 1 * 106 1 * 106

测试代码:

public static void main(String[] args) {
    BigDecimal bigDecimal = new BigDecimal("1000000");
    System.out.println(bigDecimal.stripTrailingZeros().toPlainString());
    System.out.println(bigDecimal.stripTrailingZeros().toString());
    System.out.println(bigDecimal.stripTrailingZeros().toEngineeringString());
}
  1. toBigInteger方法, 转成BigInteger对象;
  2. ulp方法,返回BigDecimal最后一位单位的大小,整数和0的话都是1,而小数的话,则取决于对应的位数,比如43.3,返回0.1,43.33返回0.01;

另外两个方法,plus和round,不太清楚具体含义。

2.5 注意事项

  由于BigDecimal和String一样具有不可变性,所以我们在进行操作之后都会得到一个新的对象,所以要记得保存下:

BigDecimal b = new BigDecimal("3.012");
// wrong
b.negate();
// right 
b = b.negate();
System.out.println(b);
3. RoundingMode枚举类

关于该枚举类的用法,先看一段官方文档:

This enum is intended to replace the integer-based enumeration of rounding mode constants in BigDecimal (BigDecimal.ROUND_UP, BigDecimal.ROUND_DOWN, etc. ).

很明显,该枚举类是为了取代BigDecimal中所定义的静态常量,所以说,以后我们如果有需要的话,尽量使用RoundingMode枚举类。由于该枚举中的类型和BigDecimal中的类型是一一对应的,这里就不多说了。

4. MathContext类

  如果我们使用BigDecimal的时候,需要同时指定精度和舍入模式的话,可以使用MathContext类。该对象是对BigDecimal中精度和舍入模式的一个封装,也就是可以指定有效位数和具体的舍入模式,使用起来比较简单:

public static void main(String[] args) {
    // output: 12.35
    System.out.println(new BigDecimal("12.345", new MathContext(4, RoundingMode.HALF_UP)));
}
5. BigInteger类

BigInteger类是对大整数进行操作的类,这里面有一些方法和BigDecimal类的一些方法是很像的,这里我们只简单介绍下BigInteger独有的方法。

1.gcd 求两个数的最大公约数;

  1. mod 取模运算,结果不会为负数 this mod m;
  2. modPow 取模运算,结果可以是负数 thisexponent mod m
  3. modInverse 计算 (this-1 mod m)
  4. isProbablePrime 判断是否是质数
  5. bitCount 返回此 该BigInteger 的二进制补码表示形式中与符号位不同的位的数量,比如8,二进制1000,则该方法返回1;再比如-8,二进制 1 1000,第一个是符号位,计算补码1 1000,则该方法返回 3;
  6. bitLength 返回此 BigInteger 的最小的二进制补码表示形式的位数,不包括 符号位
  7. flipBit 返回按指定位进行反转之后的值,计算方式:this ^ (1<<n)
    clearBit 返回清除指定位之后的值,计算方式:this & ~(1<<n)
    setBit 返回设置指定位之后的值,计算方式:this | (1<<n)
    testBit 当且仅当指定的位被设置之后,返回true,计算方式:(this & (1<<n)) != 0
  8. shiftLeftshiftRight 左移和右移操作;
  9. and,执行 this & val;
    or 执行 this | val;
    xor 执行 this ^ val;
    not 执行 ~this;
    andNot 执行 this & ~val;
  10. nextProbablePrime 大于该值的下一个可能是质数的整数;

参考:JDK 8.0 官方Api

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容