前言我们都知道浮点型变量在计算的时候会丢失精度。下面这段代码:System.out.println(0.05+0.01);System.out.println(1.0-0.42);System.out.println(4.015*100);System.out.println(123.3/100);输出:0.060000000000000050.580000000000001401.49999999999941.2329999999999999可以看到在Java中进行浮点运算时,会出现精度丢失的问题。那么如果我们计算商品价格,就会出现问题。很可能我们手里有0.06元,但是我们买不到0.05元的商品和0.01元的商品。因为如上图,他们两个的和是0.060000000000000005。这无疑是一个非常严重的问题,尤其是当电子商务网站的并发量增加时,出现的问题将是巨大的。可能会导致无法下单,或者对账出现问题。那么我们就可以使用Java中的BigDecimal类来解决这类问题。普及一下:Java中float的精度是6-7位有效数字。double的精度为15-16位。API构造函数:构造函数说明BigDecimal(int)创建一个具有参数指定的整数值的对象。BigDecimal(double)使用参数指定的双精度值创建一个对象。BigDecimal(long)使用参数指定的长整数值创建一个对象。BigDecimal(String)创建一个对象,其数值由参数指定的字符串表示。功能:方法说明add(BigDecimal)将BigDecimal对象中的值相加,然后返回该对象。subtract(BigDecimal)减去BigDecimal对象中的值并返回此对象。multiply(BigDecimal)将BigDecimal对象中的值相乘并返回此对象。divide(BigDecimal)将BigDecimal对象中的值相除并返回此对象。toString()将BigDecimal对象的数值转换为字符串。doubleValue()将BigDecimal对象中的值作为双精度数返回。floatValue()将BigDecimal对象中的值作为单精度数字返回。longValue()以长整数形式返回BigDecimal对象中的值。intValue()以整数形式返回BigDecimal对象中的值。由于是一般的数值类型,比如double,不能准确表示16位以上的数字。BigDecimal精度也丢失了。当我们使用BigDecimal时,只有使用它的BigDecimal(String)构造函数来创建对象才有意义。其他的比如BigDecimalb=newBigDecimal(1),还是会出现丢精度的问题。以下代码:BigDecimal=newBigDecimal(1.01);BigDecimalb=newBigDecimal(1.02);BigDecimalc=newBigDecimal("1.01");BigDecimald=newBigDecimal("1.02");System.out.println(a.add(b));系统.out.println(c.add(d));Output:2.03000000000000002664535259100375697016716003417968752.03可见BigDecimal的精度损失更是过分。但是,使用Bigdecimal的BigDecimal(String)构造函数对变量进行运算时,不会出现此问题。原因在于计算机的组成原理,它们的编码决定了这样的结果。long正好可以存储19位,而double只准备存储16位。由于exp位,double可以存储超过16位的数字,但代价是低位不准确。如果需要精确存储大于19的数字,就必须使用BigInteger来保存,当然会牺牲一些性能。因此,我们在一般使用BigDecimal来解决业务操作中丢失精度的问题时,在声明BigDecimal对象时,必须使用其参数为String的构造函数。同时EffectiveJavaandMySQL中也提到了这个原则。float和double只能用于科学和工程计算。我们在业务运营中使用BigDecimal。并且我们也从源码的注释中给出了官方的解释。以下是BigDecimal类double类型参数的构造函数的部分注释:Theresultsofthisconstructorcanbesomewhatunpredictable。*0.1(anunscaledvalueof1,withascaleof1),butitis*actuallyequalto*0.1000000000000000055511151231257827021181583404541015625.*Thisisbecause0.1cannotberepresentedexactlyasa*{@codedouble}(or,forthatmatter,asabinaryfractionof*anyfinitelength).Thus,thevaluethatisbeingpassed*intotheconstructorisnotexactlyequalto0.1,*外观与站立无关。...*当{@codedouble}必须用作*{@codeBigDecimal}的源代码时,不是这个构造函数提供*精确的转换;它不会给出相同的结果*使用*{@linkDouble#toString(double)}方法将{@codedouble}转换为{@codeString},然后使用*{@linkDouble#toString(double)}方法然后使用*{@linkBigDecimal(String)}构造函数。为了得到结果,*使用{@codestatic}{@link#valueOf(double)}方法。*publicBigDecimal(doubleval){this(val,MathContext.UNLIMITED);}第一段也说的很清楚,只能计算无限接近这个数,不能精确到这个数。第二段说如果你想准确的计算This值,那么你需要将double类型的参数转换成String类型。并使用BigDecimal(String)构造方法来构造。得到结果。BigDecimal的正确使用另外,BigDecimal创建的是一个对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,必须调用其对应的方法。方法中的参数也必须是BigDecimal对象,从我们刚刚列出的API中可以看出。在一般的开发过程中,我们数据库中存储的数据都是float和double类型的。进行计算时继续转换非常不方便。这里我写了一个实用类:/***@author:JiYongGuang.*@date:19:502017/12/14.*/publicclassBigDecimalUtil{privateBigDecimalUtil(){}publicstaticBigDecimaladd(doublev1,doublev2){//v1+v2BigDecimalb1=newBigDecimal(Double.toString(v1));BigDecimalb2=newBigDecimal(Double.toString(v2));returnb1.add(b2);}publicstaticBigDecimalsub(doublev1,doublev2){BigDecimalb1=newBigDecimal(Double.toString(v1));BigDecimalb2=newBigDecimal(Double.toString(v2));returnb1.subtract(b2);}publicstaticBigDecimalmul(doublev1,doublev2){BigDecimalb1=newBigDecimal(Double.toString(v1));BigDecimalb2=newBigDecimal(Double.toString(v2));returnb1.multiply(b2);}publicstaticBigDecimaldiv(doublev1,doublev2){BigDecimalb1=newBigDecimal(Double.toString(v1));BigDecimalb2=newBigDecimal(Double.toString(v2));//2=保留两位小数ROUND_HALF_UP=Roundingreturnb1.divide(b2,2,BigDecimal.ROUND_HALF_UP);//不可分割情况的应对}}该工具类提供了double类型的基本加减乘除运算。直接调用即可。
