BigDecimal你真的会用吗?
1。BigDecimal1。1背景
一直从事金融相关项目,所以对BigDecimal再熟悉不过了,也曾看到很多时候因为不知道、不了解或使用不当导致资损事件发生。
所以,如果从事金融相关项目,或者在项目中涉及到金额的计算,那么你一定要花时间看看这篇文章,全面学习一下BigDecimal。1。2BigDecimal概述
Java在java。math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。
一般情况下,对于不需要准确计算精度的数字,可以直接使用Float和Double处理,但是Double。valueOf(String)和Float。valueOf(String)会丢失精度。所以如果需要精确计算的结果,则必须使用BigDecimal类来操作。
BigDecimal所创建的是对象,我们不能使用传统的、、、等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。1。3为什么使用BigDecimal
看如下代码:publicstaticvoidmain(String〔〕args)throwsException{System。out。println(0。20。1);System。out。println(0。30。1);System。out。println(0。20。1);System。out。println(0。30。1);}
打印结果:
为何会这样:
原因:不论是float还是double都是浮点数,而计算机是二进制的,浮点数会失去一定的精确度。
根本原因:十进制值通常没有完全相同的二进制表示形式;十进制数的二进制表示形式可能不精确。只能无限接近于那个值。
但是,在项目中,我们不可能让这种情况出现,特别是金融项目,因为涉及金额的计算都必须十分精确,否则,如果你的微信账户余额显示193。99999999999998,那是一种怎么样的体验?
float和double为什么不精确:
首先,计算机是只认识二进制的,即0和1,这个大家一定都知道。
那么,所有数字,包括整数和小数,想要在计算机中存储和展示,都需要转成二进制。
十进制整数转成二进制很简单,通常采用除2取余,逆序排列即可,如10的二进制为1010
但是,小数的二进制如何表示呢?
十进制小数转成二进制,一般采用乘2取整,顺序排列方法,如0。625转成二进制的表示为0。101。
具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的整数部分为零,或者整数部分为1,此时0或1为二进制的最后一位。或者达到所要求的精度为止
但是,并不是所有小数都能转成二进制,如0。1就不能直接用二进制表示,他的二进制是0。000110011001100一个无限循环小数
这里我们使用乘2取整,顺序排列的方法来计算一下0。120。2取整数部分00。220。4取整数部分00。420。8取整数部分00。821。6取整数部分10。621。2取整数部分10。220。2取整数部分0(从此处又开始新的一轮循环)所以0。1的二进制为0。0001100110011无限循
所以,计算机是没办法用二进制精确的表示0。1的。也就是说,在计算机中,很多小数没办法精确的使用二进制表示出来,这也就是为什么float和double的值并不是精确的原因了,其只是在一定精度范围内表示一个浮点数。1。4如何使用BigDecimal1。4。1初始化赋值
方法
类型
描述
publicBigDecimal(intval)
构造函数
int类型的值生成BigDecimal对象
publicBigDecimal(longval)
构造函数
long类型的值生成BigDecimal对象
publicBigDecimal(Stringval)
静态方法
String类型的值转换为BigDecimal类型
publicstaticBigDecimalvalueOf(doubleval)
静态方法
double类型的值转换为BigDecimal类型
publicstaticBigDecimalvalueOf(longval)
静态方法
long类型(包含int类型)的值转换为BigDecimal类型
通常建议优先使用String构造方法。当double必须用作BigDecimal的源时,请使用Double。toString(double)转成String,然后使用String构造方法,或使用BigDecimal的静态方法valueOf:BigDecimalbnewBigDecimal(12。3);BigDecimalcBigDecimal。valueOf(7。34325);BigDecimaldnewBigDecimal(Double。toString(7。34325));System。out。println(b);System。out。println(c);System。out。println(d);
打印结果:
1。4。2加减乘除运算
运算法则
对应方法
加法
publicBigDecimaladd(BigDecimalvalue)
减法
publicBigDecimalsubtract(BigDecimalvalue)
乘法
publicBigDecimalmultiply(BigDecimalvalue)
除法
publicBigDecimalpide(BigDecimalvalue)
代码示例:publicstaticvoidmain(String〔〕args)throwsException{System。out。println(计算加法:BigDecimal。valueOf(1。9)。add(BigDecimal。valueOf(0。2)));System。out。println(计算减法:BigDecimal。valueOf(1。9)。subtract(BigDecimal。valueOf(1。5)));System。out。println(计算乘法:BigDecimal。valueOf(1。9)。multiply(BigDecimal。valueOf(0。2)));System。out。println(计算除法:BigDecimal。valueOf(1。9)。pide(BigDecimal。valueOf(0。2)));}
打印结果:
注意点:BigDecimal的运算结果都是返回了一个新的BigDecimal对象,并不是在原有的对象上进行操作。使用pide除法函数除不尽,出现无线循环小数的时候,就需要使用另外精确的小数位数以及舍入模式,不然会出现报错。1。4。3保留小数及舍入模式publicBigDecimalsetScale(intnewScale,introundingMode)
用于格式化小数的方法,第一个值表示保留几位小数,第二个值表示格式化的类型。
8种类型如下:
格式化类型
描述
ROUNDDOWN
舍弃多余位数,如1。55会格式化为1。5,1。55会格式化为1。5
ROUNDUP
进位处理,如1。52会格式化为1。6,1。52会格式化为1。6
ROUNDHALFUP
四舍五入,如果舍弃部分。5,则进位
ROUNDHALFDOWN
五舍六入,如果舍弃部分。5,则进位
ROUNDCEILING
正无穷大方向舍入模式。如果值为正数,则与ROUNDUP模式相同;如果值为负数,则与ROUNDDOWN模式相同
ROUNDFLOOR
负无穷大方向舍入模式。如果值为正数,则与ROUNDDOWN模式相同;如果值为负数,则与ROUNDUP模式相同
ROUNDUNNECESSARY
确认值的小数位数是否与传入第一个参数(保留小数的位数)相等,如果符合则返回值,如果不符抛出异常
ROUNDHALFEVEN
如果舍弃部门左边的数字为奇数,则与ROUNDHALFUP模式相同,如果为偶数则与ROUNDHALFDOWN模式相同
代码示例:publicstaticvoidmain(String〔〕args){BigDecimalaBigDecimal。valueOf(5。445);System。out。println(5。445舍弃多余位数:a。setScale(2,BigDecimal。ROUNDDOWN));System。out。println(5。445进位处理:a。setScale(2,BigDecimal。ROUNDUP));System。out。println(5。445四舍五入(舍弃部分。5,进位):a。setScale(2,BigDecimal。ROUNDHALFUP));System。out。println(5。445四舍五入(舍弃部分未。5,舍弃):a。setScale(2,BigDecimal。ROUNDHALFDOWN));System。out。println(5。446四舍五入(舍弃部分。5,进位):BigDecimal。valueOf(5。446)。setScale(2,BigDecimal。ROUNDHALFDOWN));}
打印结果:
1。4。4比较大小publicintcompareTo(BigDecimalval)
BigDecimal类提供的比较值的方法,注意比较的两个值均不能为空。
a。compareTo(b)得到结果1,0,1。
比较结果
描述
1hra大于b
0hra等于b
1
a小于b
代码示例:publicstaticvoidmain(String〔〕args){BigDecimalaBigDecimal。valueOf(1);BigDecimalbBigDecimal。valueOf(2);BigDecimalcBigDecimal。valueOf(1);BigDecimaldBigDecimal。ZERO;System。out。println(1和2比较结果:a。compareTo(b));System。out。println(1和1比较结果:a。compareTo(c));System。out。println(1和0比较判断:(a。compareTo(d)0));}
打印结果:
注意:实际业务中,不会单纯的比较谁打谁小,而是类似于第三条去判断一个布尔值。1。4。5其他方法及常量
代码
类型
描述
BigDecimal。ZERO
常量
初始化一个为0的BigDecimal对象
BigDecimal。ONE
常量
初始化一个为1的BigDecimal对象
BigDecimal。TEN
常量
初始化一个为10的BigDecimal对象
publicBigDecimalabs()
方法
求绝对值,不管正数还是负数,都得到正数
publicBigDecimalnegate()
方法
求相反数,正变负,负变正
publicBigDecimalpow(intn)
方法
求乘方,如BigDecimal。valueOf(2)。pow(3)的值为8
publicBigDecimalmax(BigDecimalval)
方法
两值比较,返回最大值
publicBigDecimalmin(BigDecimalval)
方法
两值比较,返回最小值1。5BigDecimal的4个坑
在使用BigDecimal时,有4种使用场景下的坑,你一定要了解一下,如果使用不当,必定很惨。掌握这些案例,当别人写出有坑的代码,你也能够一眼识别出来,大牛就是这么练成的。1。5。1浮点类型的坑
在学习了解BigDecimal的坑之前,先来说一个老生常谈的问题:如果使用Float、Double等浮点类型进行计算时,有可能得到的是一个近似值,而不是精确的值。
比如下面的代码:Testpublicvoidtest0(){floata1;floatb0。9f;System。out。println(ab);}
结果是多少?0。1吗?不是,执行上面代码执行的结果是0。100000024。之所以产生这样的结果,是因为0。1的二进制表示是无限循环的。由于计算机的资源是有限的,所以是没办法用二进制精确的表示0。1,只能用近似值来表示,就是在有限的精度情况下,最大化接近0。1的二进制数,于是就会造成精度缺失的情况。
那么,BigDecimal就一定能避免上述的浮点问题吗?来看下面的示例:Testpublicvoidtest1(){BigDecimalanewBigDecimal(0。01);BigDecimalbBigDecimal。valueOf(0。01);System。out。println(aa);System。out。println(bb);}
结果:a0。01000000000000000020816681711721685132943093776702880859375b0。01
上面的实例说明,即便是使用BigDecimal,结果依旧会出现精度问题。这就涉及到创建BigDecimal对象时,如果有初始值,是采用newBigDecimal的形式,还是通过BigDecimalvalueOf方法了。
之所以会出现上述现象,是因为newBigDecimal时,传入的0。1已经是浮点类型了,鉴于上面说的这个值只是近似值,在使用newBigDecimal时就把这个近似值完整的保留下来了。
而BigDecimalvalueOf则不同,它的源码实现如下:publicstaticBigDecimalvalueOf(doubleval){Reminder:azerodoublereturns0。0,sowecannotfastpathtousetheconstantZERO。Thismightbeimportantenoughtojustifyafactoryapproach,acache,orafewprivateconstants,later。returnnewBigDecimal(Double。toString(val));}
在valueOf内部,使用DoubletoString方法,将浮点类型的值转换成了字符串,因此就不存在精度丢失问题了。
此时就得出一个基本的结论:第一,在使用BigDecimal构造函数时,尽量传递字符串而非浮点类型;第二,如果无法满足第一条,则可采用BigDecimalvalueOf方法来构造初始化值。1。5。2浮点精度的坑
如果比较两个BigDecimal的值是否相等,你会如何比较?使用equals方法还是compareTo方法呢?
先来看一个示例:Testpublicvoidtest2(){BigDecimalanewBigDecimal(0。01);BigDecimalbnewBigDecimal(0。010);System。out。println(a。equals(b));System。out。println(a。compareTo(b));}Overridepublicbooleanequals(Objectx){if(!(xinstanceofBigDecimal))returnfalse;BigDecimalxDec(BigDecimal)x;if(xthis)returntrue;if(scale!xDec。scale)returnfalse;longsthis。intCompact;longxsxDec。intCompact;if(s!INFLATED){if(xsINFLATED)xscompactValFor(xDec。intVal);returnxss;}elseif(xs!INFLATED)returnxscompactValFor(this。intVal);returnthis。inflated()。equals(xDec。inflated());}
equals方法不仅比较了值是否相等,还比较了精度是否相同。上述示例中,由于两者的精度不同,所以equals方法的结果当然是false了。而compareTo方法实现了Comparable接口,真正比较的是值的大小,返回的值为1(小于),0(等于),1(大于)。
基本结论:通常情况,如果比较两个BigDecimal值的大小,采用其实现的compareTo方法;如果严格限制精度的比较,那么则可考虑使用equals方法。1。5。3设置精度的坑
在项目中看到好多同学通过BigDecimal进行计算时不设置计算结果的精度和舍入模式,真是着急人,虽然大多数情况下不会出现什么问题。但下面的场景就不一定了:Testpublicvoidtest3(){BigDecimalanewBigDecimal(1。0);BigDecimalbnewBigDecimal(3。0);a。pide(b);}
执行上述代码的结果是什么?ArithmeticException异常!
总结:如果在除法(pide)运算过程中,如果商是一个无限小数(0。333),而操作的结果预期是一个精确的数字,那么将会抛出ArithmeticException异常。
此时,只需在使用pide方法时指定结果的精度即可:Testpublicvoidtest3(){BigDecimalanewBigDecimal(1。0);BigDecimalbnewBigDecimal(3。0);BigDecimalca。pide(b,2,RoundingMode。HALFUP);System。out。println(c);}
执行上述代码,输入结果为0。33。
基本结论:在使用BigDecimal进行(所有)运算时,一定要明确指定精度和舍入模式。1。5。4三种字符串输出的坑
当使用BigDecimal之后,需要转换成String类型,你是如何操作的?直接toString?
先来看看下面的代码:Testpublicvoidtest4(){BigDecimalaBigDecimal。valueOf(35634535255456719。22345634534124578902);System。out。println(a。toString());}
执行的结果是上述对应的值吗?并不是:3。563453525545672E16
也就是说,本来想打印字符串的,结果打印出来的是科学计数法的值。
这里我们需要了解BigDecimal转换字符串的三个方法toPlainString():不使用任何科学计数法;toString():在必要的时候使用科学计数法;toEngineeringString():在必要的时候使用工程计数法。类似于科学计数法,只不过指数的幂都是3的倍数,这样方便工程上的应用,因为在很多单位转换的时候都是103;
基本结论:根据数据结果展示格式不同,采用不同的字符串输出方法,通常使用比较多的方法为toPlainString()。1。5。5NumberFormat类
另外,NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。
使用示例如下:NumberFormatcurrencyNumberFormat。getCurrencyInstance();建立货币格式化引用NumberFormatpercentNumberFormat。getPercentInstance();建立百分比格式化引用percent。setMaximumFractionDigits(3);百分比小数点最多3位BigDecimalloanAmountnewBigDecimal(15000。48);金额BigDecimalinterestRatenewBigDecimal(0。008);利率BigDecimalinterestloanAmount。multiply(interestRate);相乘System。out。println(金额:currency。format(loanAmount));System。out。println(利率:percent。format(interestRate));System。out。println(利息:currency。format(interest));
输出结果如下:金额:15,000。48利率:0。8利息:120。00
科莫多巨蜥的毒液到底有多可怕?2009年,一名31岁的渔民安瓦尔,在印尼科莫多岛上采摘水果,结果不小心从水果树上掉了下来,刚好踩到了一只巨型蜥蜴,蜥蜴瞬间就朝他扑了过去,咬住了他的腿,之后又咬住了他的手臂,身体
武汉未来的房价会涨到100000元平米吗?这个问题的答案是肯定的!以目前的趋势,未来武汉房价必然达到10万的水平,只是时间长短的问题。2010年至2017年,短短七年间,武汉的房价已经翻了3番,目前,武汉部分高端楼盘如洞庭
兰州青白石片区,中央公园怎么样?兰州北拓的黄金区域,目前基础交通还跟不上建设需要,交通滞后可能会成为十四五期间兰州青白石片区发展的的最大障碍!不过就兰州地理位置和城区格局而言,青白石片区是离主城区最近的待开发区域
农民为什么不在国家统计失业范围之内?中国有四个儿子,大儿子叫工人,二儿子叫子弟兵,三儿子叫公务员,四儿子叫农民,所以四儿子就没有纳入统计失业包括养老金范围,因为四儿子有金山银山还有三分地。农民有土地,这是农民可以赖以
农村成立社区是什么意思?很多农村驻有村委会办公室,同时也驻有社区管理委员会办公室,特别是在城市郊区的农村和街道的农村都同时设立了村委会和社区管理委员会,很多人弄不明白是怎么回事。那农村成立社区是什么意思呢
农村里的剩男,为何一剩再剩呢?到底是什么原因?男女比例失调。计划生育只要一个孩子时,受封建思想影响,都拚命要男孩,等他们长大了,很难找到媳妇。一,农村姑娘远嫁,二,女孩见少,三,彩礼高,四,剩男挣钱少。我就一大龄剩男!个人亲身
抖音付费直播试水,看直播要给钱了?我们应该如何思考?使劲收,最好是家人们看的话,一分钟100块钱。毕竟粉丝听话的很。毕竟人设都设计好了打PK,卖货摆错价格,怒亏2个亿回馈粉丝。没事就怼工厂,怼员工反正就是赔钱回馈粉丝。赔完还得补交税
马上就要退休了,退休工资才3650元太少了,怎么办?3600不少了。我企业工龄32年,退休时退休金只有2200,涨了这么多年还不到3000。知足常乐吧!如果身体不好,这些钱也够生活了,如果身体还可以,就找一些力所能及的工作,打打工补
南宁五象新区未来的发展潜力很大吗?五象新区无法成为国家级新区!!!目前看来,五象新区的潜力也就这样了。我们对比一下贵阳的贵安新区,贵安新区的面积是1700平方公里,由贵阳市的郊区和安顺市合并得来。是全国第8个国家级
如果把三峡大坝加高10米,截留更多的洪水,可行吗?我国的三峡大坝,作为当今世界上最大的水利枢纽工程,位于湖北省宜昌市上游,距下游葛洲坝水电站38公里,三峡大坝全长2309米高185米,呈梯形形状,集发电旅游航运调控洪水于一身。三峡
大家有经历过亲人去世吗?是怎样走出痛苦和想念的?2018年9月1日下午3点8分,我的妻子在医院里停止了呼吸。她的眼睛没有闭上,我流着泪,帮她合上了双眼。我永远失去了我最亲近的爱人,孩子永远的失去了妈妈。一位好妻子,好女儿,好姑妈