java中Double类型运算精度丢失问题,(小数点多出99999999999999)x

 java 中 中 Double 类型的运算精度丢失的问题 ( 小数点多出99999999999999)

 在使用 Java,double 进行运算时,经常出现精度丢失的问题,总是在一个正确的结果左右偏 0.0000**1。

 特别在实际项目中,通过一个公式校验该值是否大于 0,如果大于 0 我们会做一件事情,小于 0 我们又处理其他事情。

 这样的情况通过double 计算出来的结果去和 0 比较大小,尤其是有小数点的时候,经常会因为精度丢失而导致程序处理流程出错。

 首先贴一个使用的代码:

 /**

  * 将 double 类型数据转为字符串(如将 18.4 转为 1840,如果需要 1840.0,把 int 强转去掉即可)

  * @param d

  * @return

  */

 public static String double2String(double d){

 BigDecimal bg = new BigDecimal(d * 100);

 double doubleValue = bg.setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();

 return

 String.valueOf((int)doubleValue);

 }

  BigDecimal 在《Effective Java》这本书中也提到这个原则,float 和 double 只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal。BigDecimal 一共有 4 个够造方法,我们不关心用 BigInteger 来够造的那两个,那么还有两个, 它们是:

 BigDecimal(double val)

 Translates a double into a BigDecimal.

 BigDecimal(String val)

 Translates the String repre sentation of a BigDecimal into a BigDecimal. 上面的 API 简要描述相当的明确,而且通常情况下,上面的那一个使用起来要方便一些。我们可能想都不想就用上了,会有什么问题呢?等到出了问题的时候,才发现上面哪个够造方法的详细说明中有这么一段:

 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 .1000000000000000055511151231257827021181583404541015625. 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 nonwithstanding.

 The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(".1") is exactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one. 原来我们如果需要精确计算,非要用 String 来够造 BigDecimal 不可!在《Effective Java》一书中的例子是用 String 来够造 BigDecimal 的,但是书上却没有强调这一点,这也许是一个小小的失误吧。

  解决方案 现在我们已经可以解决这个问题了,原则是使用 BigDecimal 并且一定要用 String来够造。

 但是想像一下吧,如果我们要做一个加法运算,需要先将两个浮点数转为 String,然后够造成 BigDecimal,在其中一个上调用 add 方法,传入另 一个作为参数,然后把运算的结果(BigDecimal)再转换为浮点数。你能够忍受这么烦琐的过程吗?下面我们提供一个工具类 Arith 来简化操作。它 提供以下静态方法,包括加减乘除和四舍五入:

 add(double v1,double v2) sub(double v1,double v2) mul(double v1,double v2) ) div(double v1,double v2,int scale) public static double round(double v,int scale)

  所以一般对 double 类型进行运算时,做好对结果进行处理,然后拿这个值去做其他事情。

 使用如下:

 /**

  对 double 数据进行取精度.

  value

 double 数据.

  scale

 精度位数(保留的小数位数).

  param roundingMode

 精度取值方式.

  * @return 精度计算后的数据.

  */

 public static double round(double value, int scale,

 int roundingMode) {

  BigDecimal bd = new BigDecimal(value);

  bd = bd.setScale(scale, roundingMode);

  double d = bd.doubleValue();

  bd = null;

  return d;

 }

 /**

  double 相加

  1

  param d2

  * @return

  */

 public double sum(double d1,double d2){

 1 1

  BigDecimal bd2 = new BigDecimal(Double.toString(d2));

 return bd1.add(bd2).doubleValue();

 }

 /**

  double 相减

  1

  param d2

  * @return

  */

 public double sub(double d1,double d2){

 1 1

  BigDecimal bd2 = new BigDecimal(Double.toString(d2));

 return bd1.subtract(bd2).doubleValue();

 }

  /**

  double 乘法

  1

  param d2

  * @return

  */

 public double mul(double d1,double d2){

 1 1

  BigDecimal bd2 = new BigDecimal(Double.toString(d2));

 return bd1.multiply(bd2).doubleValue();

 }

 /**

  double 除法

  1

  * @param d2

  param scale 四舍五入 小数点位数

  * @return

  */

 public double div(double d1,double d2,int scale){

 当然在此之前,你要判断分母是否为 0,

  //

 为 0 你可以根据实际需求做相应的处理

  1 1

  BigDecimal bd2 = new BigDecimal(Double.toString(d2));

 return bd1.divide

 (bd2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();

 }

 这样,计算 double 类型的数据计算问题就可以处理了。

 另外补充一下 JavaScript 四舍五入的方法:

 小数点问题

  Math.round(totalAmount*100)/100 (保留 2 位)

  function formatFloat(src, pos)

 {

 return Math.round(src*Math.pow(10, pos))/Math.pow(10, pos);

  }

  四舍五入是我们小学的数学问题,这个问题对于我们程序猿来说就类似于 1 到 10的加减乘除那么简单了。在讲解之间我们先看如下一个经典的案例:

 [java] view plaincopyprint? 1 public static void maiString[] args) {

  2

  12.5 的四舍五入值:" + Math.round(12.5));

  3

  System.out.println("-12.5 的四舍五入值:" + Math.round(-12.5));

  4

  }

  5 Output:

  6 12.5 的四舍五入值:13

  7. -12.5 的四舍五入值:-12

 这是四舍五入的经典案例,也是我们参加校招时候经常会遇到的(貌似我参加笔试的时候遇到过好多次)。从这儿结果中我们发现这两个绝对值相同的数字,为何近似值会不同呢?其实这与 Math.round 采用的四舍五入规则来决定。

  四舍五入其实在金融方面运用的非常多,尤其是银行的利息。我们都知道银行的盈利渠道主要是利息差,它从储户手里收集资金,然后放贷出去,期间产生的利息差就是银行所获得的利润。如果我们采用平常四舍五入的规则话,这里采用每10 笔存款利息计算作为模型,如下:

  四舍:0.000、0.001、0.002、0.003、0.004。这些舍的都是银行赚的钱。

  五入:0.005、0.006、0.007、0.008、0.009。这些入的都是银行亏的钱,分别为:0.005、0.004、.003、0.002、0.001。

  所以对于银行来说它的盈利应该是 0.000 + 0.001 + 0.002 + 0.003 + 0.004 - 0.005 - 0.004 - 0.003 - 0.002 - 0.001 = -0.005。从结果中可以看出每 10 笔的利息银行可能就会损失 0.005 元,千万别小看这个数字,这对于银行来说就是一笔非常大的损失。面对这个问题就产生了如下的银行家涉入法了。该算法是由美国银行家提出了,主要用于修正采用上面四舍五入规则而产生的误差。如下:

  舍去位的数值小于 5 时,直接舍去。

  舍去位的数值大于 5 时,进位后舍去。

  当舍去位的数值等于 5 时,若 5 后面还有其他非 0 数值,则进位后舍去,若 5后面是 0 时,则根据 5 前一位数的奇偶性来判断,奇数进位,偶数舍去。

  对于上面的规则我们举例说明

 11.556 = 11.56 ------六入

 11.554 = 11.55 -----四舍

 11.5551 = 11.56 -----五后有数进位

 11.545 = 11.54 -----五后无数,若前位为偶数应舍去

 11.555 = 11.56 -----五后无数,若前位为奇数应进位

  下面实例是使用银行家舍入法:

 [java] view plaincopyprint? 1 public static void main(String[] args) {

  2

  d = new BigDecimal(100000);

 //存款

  3.

  BigDecimal r = new BigDecimal(0.001875*3);

  //利息

 4.

  BigDecimal i = d.multiply(r).setScale(2,RoundingMode.HALF_EVEN);

  //使用银行家算法

 5

 6

  System.out.println("季利息是:"+i);

  7

  }

  8 Output:

  9. 季利息是:562.50

  在上面简单地介绍了银行家舍入法,目前 java 支持 7 中舍入法:

 1、 ROUND_UP:远离零方向舍入。向绝对值最大的方向舍入,只要舍弃位非0 即进位。

 2、 ROUND_DOWN:趋向零方向舍入。向绝对值最小的方向输入,所有的位都要舍弃,不存在进位情况。

 3、 ROUND_CEILING:向正无穷方向舍入。向正最大方向靠拢。若是正数,舍入行为类似于 ROUND_UP,若为负数,舍入行为类似于 ROUND_DOWN。Math.round()方法就是使用的此模式。

 4、 ROUND_FLOOR:向负无穷方向舍入。向负无穷方向靠拢。若是正数,舍入行为类似于 ROUND_DOWN;若为负数,舍入行为类似于 ROUND_UP。

 5、 HALF_UP:最近数字舍入(5 进)。这是我们最经典的四舍五入。

 6、 HALF_DOWN:最近数字舍入(5 舍)。在这里 5 是要舍弃的。

 7、 HAIL_EVEN:银行家舍入法。

  提到四舍五入那么保留位就必不可少了,在 java 运算中我们可以使用多种方式来实现保留位。

  保留位

  方法一:四舍五入 方法一:四舍五入

 [java] view plaincopyprint? 1 double

  f

  =

  111231.5585;

  2 BigDecimal

  b

  =

  new

  BigDecimal(f);

  3. double

  f1

  =

  b.setScale(2,

  RoundingMode.HALF_UP).doubleValue();

 在这里使用 BigDecimal ,并且采用 setScale 方法来设置精确度,同时使用RoundingMode.HALF_UP 表示使用最近数字舍入法则来近似计算。在这里我们可以看出 BigDecimal 和四舍五入是绝妙的搭配。

  方式二:

 方式二:

 [java] view plaincopyprint? 1 java.text.DecimalFormat

  df

  =new

  java.text.DecimalFormat(”#.00″);

  2. df.format(你要格式化的数字);

  例:new java.text.DecimalFormat(”#.00″).format(3.1415926)

  #.00 表示两位小数 #.0000 四位小数 以此类推…

  方式三:

 方式三:

 [java] view plaincopyprint? 1 double d = 3.1415926;

  2

 3 String result = String .format(”%.2f”);

  4

 5. %.2f %. 表示 小数点前任意位数

  2 表示两位小数 格式后的结果为 f 表示浮点型。

  方式四:

 方式四:

  此外如果使用 struts 标签做输出的话,有个 format 属性,设置为 format="0.00"就是保留两位小数

  例如:

 [java] view plaincopyprint? 1 <bean:write name="entity" property="dkhAFSumPl"

 format="0.00" />

  2

 3 或者

  4

 5. <fmt:formatNumber type="number" value="${10000.22/100}" maxFractionDigits="0"/>

  6

 7. maxFractionDigits 表示保留的位数

  BigDecimal.setScale 处理 java 小数点 BigDecimal.setScale()方法用于格式化小数点 setScale(1)表示保留一位小数,默认用四舍五入方式

 DOWN)直接删除多余的小数位,如 2.35 会变成 2.3

 UP)进位处理,2.35 变成 2.4

 (1,BigDecimal.ROUND_HALF_UP)四舍五入,2.35 变成 2.4 setScaler(1,BigDecimal.ROUND_HALF_DOWN)四舍五入,2.35 变成 2.3,如果是 5则向下舍

  注释:

 注释:

 1:

 ale 指的是你小数点后的位数。比如 123.456 则 score 就是 3. score()就是 BigDecimal 类中的方法啊。

 比如:BigDecimal b = new BigDecimal("123.456"); b.scale(),返回的就是 3.

 2:

 :

 roundingMode 是小数的保留模式。它们都是 BigDecimal 中的常量字段,有很多种。

 比如:BigDecimal.ROUND_HALF_UP 表示的就是 4 舍 5 入。

 3:

 pubilc BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) 的意思是说:我用一个 BigDecimal 对象除以 divisor 后的结果,并且要求这个结果保留有 scale 个小数位,roundingMode 表示的就是保留模式是什么,是四舍五入啊还是其它的,你可以自己选!

 4:对于一般 :对于一般 add 、subtract 、multiply 方法的小数位格式化如下:

 方法的小数位格式化如下:

 BigDecimal mData = new BigDecimal("9.655").setScale(2, BigDecimal.ROUND_HALF_UP);

  System.out.println("mData=" + mData);

  ----结果:

 结果:----- mData=9.66

推荐访问:小数点 多出 运算