欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java开发笔记(三十)大小数BigDecimal

程序员文章站 2022-05-14 10:24:35
前面介绍的BigInteger只能表达任意整数,但不能表达小数,要想表达任意小数,还需专门的大小数类型BigDecimal。如果说设计BigInteger的目的是替代int和long类型,那么设计BigDecimal的目的便是替代浮点型float和双精度型double了。正如它的兄弟BigInteg ......

前面介绍的biginteger只能表达任意整数,但不能表达小数,要想表达任意小数,还需专门的大小数类型bigdecimal。如果说设计biginteger的目的是替代int和long类型,那么设计bigdecimal的目的便是替代浮点型float和双精度型double了。正如它的兄弟biginteger一般,bigdecimal不存在什么数值范围限制,无论是整数部分还是小数部分,只要你能写得出来,bigdecimal就能表达出来,从此不必担心基本数字类型的精度问题了。
既然同为大数字家族,bigdecimal的绝大部分用法就与biginteger保持一致,像add方法、subtract方法、abs方法、pow方法等等直接拿来便是,这里不再重复啰嗦了,且看下面bigdecimal的方法调用代码:

		// 生成一个指定数值的大小数变量
		bigdecimal sevenandhalf = bigdecimal.valueof(7.5);
		bigdecimal three = bigdecimal.valueof(3);
		// add方法用来替代加法运算符“+”
		bigdecimal sum = sevenandhalf.add(three);
		system.out.println("sum="+sum);
		// subtract方法用来替代减法运算符“-”
		bigdecimal sub = sevenandhalf.subtract(three);
		system.out.println("sub="+sub);
		// multiply方法用来替代乘法运算符“*”
		bigdecimal mul = sevenandhalf.multiply(three);
		system.out.println("mul="+mul);
		// divide方法用来替代除法运算符“/”
		bigdecimal div = sevenandhalf.divide(three);
		system.out.println("div="+div);
		// remainder方法用来替代取余数运算符“%”
		bigdecimal remainder = sevenandhalf.remainder(three);
		system.out.println("remainder="+remainder);
		// negate方法用来替代负号运算符“-”
		bigdecimal neg = sevenandhalf.negate();
		system.out.println("neg="+neg);
		// abs方法用来替代数学库函数math.abs
		bigdecimal abs = sevenandhalf.abs();
		system.out.println("abs="+abs);
		// pow方法用来替代数学库函数math.pow
		bigdecimal pow = sevenandhalf.pow(2);
		system.out.println("pow="+pow);

 

哇噻,难道这么容易就学会使用bigdecimal了吗?仔细看上面的例子代码,被除数是7.5,除数是3,二者相除得到的商为2.5。注意这是除得尽的情况,倘若换个除不尽的情况,例如把除数改成7,7.5除以7结果理应得到一个无限循环小数。可要是运行以下的测试代码,没想到程序竟然运行异常,未能打印那个值为无限循环小数的商。

		// 只有一个输入参数的divide方法,要求被除数能够被除数除得尽。
		// 倘若除不尽,也就是商为无限循环小数,则程序会异常退出,
		// 报错“non-terminating decimal expansion; no exact representable decimal result.”
		bigdecimal seven = bigdecimal.valueof(7);
		bigdecimal divtest = sevenandhalf.divide(seven);
		system.out.println("divtest="+divtest);

 

虽说大小数能够表示任意范围的小数,但必须是个有限的范围,而不能是无限的范围。由于内存容量是有限的,一个无限循环小数写出来都写不完,要是放到内存就需要无限大小的内存,因此为了让内存能够放得下无限循环小数,只好给该小数指定需要保留的小数位数,也就意味着bigdecimal表示无限循环小数时还是有精度要求的。
除了规定小数部分的保留位数,还需明确多余部分的数字是直接舍弃还是四舍五入?这样对于无限循环小数来说,除法运算的divide方法需要三个输入参数,包括除数、需要保留的小数位数、多余数字的舍入规则。bigdecimal提供的数字舍入规则主要有下列几种:
round_ceiling:往数值较小的方向取整,类似于math库的ceiling函数。
round_floor:往数值较大的方向取整,类似于math库的floor函数。
round_half_up:四舍五入取整,若多余的数字等于.5,则前一位进1,类似于math库的round函数。
round_half_down:类似四舍五入取整,区别在于:若多余的数字等于.5,则直接舍弃。
round_half_even:如果保留位数的末尾为奇数,则按照round_half_up方式取整。如果保留位数的末尾为偶数,则按照round_half_down方式取整。
由上述规则可知,通常情况下的四舍五入应当采取round_half_up方式。于是重新指定了小数精度和舍入规则,改写后大小数的除法运算代码示例如下:

		bigdecimal one = bigdecimal.valueof(100);
		bigdecimal three = bigdecimal.valueof(3);
		// 大小数的除法运算,小数点后面保留64位,其中最后一位做四舍五入
		bigdecimal div = one.divide(three, 64, bigdecimal.round_half_up);
		system.out.println("div="+div);

 

运行修改后的除法代码,控制台打印的日志结果见下:

div=33.3333333333333333333333333333333333333333333333333333333333333333

 

可见此时除法计算正常工作,并且结果值的小数部分确实保留到了64位。
上述带三个输入参数的divide方法固然实现了符合精度的除法运算,但若代码存在多处调用divide方法,便意味着该方法后面的精度规则“64, bigdecimal.round_half_up”在每处调用的地方都会出现,这样不但造成代码重复,而且要是变更精度规则还得改动多处。为此java又提供了工具mathcontext,利用该工具可事先指定包含小数精度和舍入规则在内的精度规则,然后把设置好的工具对象传给divide方法就好了。下面是使用mathcontext工具辅助除法运算的代码例子:

		// 利用工具mathcontext,可以把divide方法的输入参数减少为两个
		mathcontext mc = new mathcontext(64, roundingmode.half_up);
		bigdecimal divbymc = one.divide(three, mc);
		system.out.println("divbymc="+divbymc);

 

在大小数的除法中引入工具mathcontext,至少有两个好处,其一为:只要定义一次,即可多处使用;其二为:若要变更精度规则,只需修改一个地方。



更多java技术文章参见《java开发笔记(序)章节目录