Java使用BigDecimal进行高精度计算的示例代码

所属分类: 软件编程 / java 阅读数: 19
收藏 0 赞 0 分享

首先看如下代码示例:

System.out.println(0.05 + 0.01);
System.out.println(0.05 - 0.03);
System.out.println(1.025 * 100);
System.out.println(305.1 / 1000);

输出结果为:

0.060000000000000005
0.020000000000000004
102.49999999999999
0.30510000000000004

Java语言支持两种基本的浮点类型:float和double,以及与它们对应的包装类Float和Double。它们都依据IEEE 754 标准,该标准为 32 位浮点和 64 位双精度浮点二进制小数定义了二进制标准。

IEEE 754 用科学记数法以底数为 2 的小数来表示浮点数。IEEE 浮点数用 1 位表示数字的符号,用 8 位来表示指数,用 23 位来表示尾数,即小数部分,作为有符号整数的指数可以有正负之分,小数部分用二进制(底数 2)小数来表示

不要用浮点值表示精确值

一些非整数值(如几美元和几美分这样的小数)需要很精确。浮点数不是精确值,所以使用它们会导致舍入误差。因此,使用浮点数来试图表示象货币量这样的精确数量不是一个好的想法。使用浮点数来进行美元和美分计算会得到灾难性的后果。浮点数最好用来表示象测量值这类数值,这类值从一开始就不怎么精确。

使用BigDecimal

从 JDK 1.3 起,Java 开发人员就有了另一种数值表示法来表示非整数: BigDecimal 。 BigDecimal 是标准的类,在编译器中不需要特殊支持,它可以表示任意精度的小数,并对它们进行计算。

用于加、减、乘和除的方法给 BigDecimal 值提供了算术运算。由于 BigDecimal 对象是不可变的,这些方法中的每一个都会产生新的 BigDecimal 对象。因此,因为创建对象的开销, BigDecimal 不适合于大量的数学计算,但设计它的目的是用来精确地表示小数。如果您正在寻找一种能精确表示如货币量这样的数值,则 BigDecimal 可以很好地胜任该任务。

构造 BigDecimal 数

对于 BigDecimal ,有几个可用的构造函数。其中一个构造函数以双精度浮点数作为输入,另一个以整数和换算因子作为输入,还有一个以小数的 String 表示作为输入。要小心使用 BigDecimal(double) 构造函数,因为如果不了解它,会在计算过程中产生舍入误差。请使用基于整数或 String 的构造函数

public class Test {
  public static void main(String[] args) {
    // 以双精度浮点数进行构造
    BigDecimal bd1 = new BigDecimal(0.5);
    BigDecimal bd2 = new BigDecimal(0.1);
    System.out.println(bd1.add(bd2));

    // 以String类型进行构造
    BigDecimal bd3 = new BigDecimal("0.5");
    BigDecimal bd4 = new BigDecimal("0.1");
    System.out.println(bd3.add(bd4));
  }
}

输出结果为:

0.6000000000000000055511151231257827021181583404541015625
0.6

上面代码分别以

BigDecimal(double val)
BigDecimal(String val)

不同的方式进行构造 BigDecimal 数,输出的结果是不一样的。

回到最开始的示例,提供工具类进行精确的浮点数运算,包括加减乘除和四舍五入。

import java.math.BigDecimal;

public class ArithUtil {
  private static final int DEF_DIV_SCALE = 6; // 默认除法运算精度

  /**
   * 提供精确的加法运算。
   *
   * @param v1 被加数
   * @param v2 加数
   * @return 两个参数的和
   */
  public static double add(double v1, double v2) {
    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));

    return b1.add(b2).doubleValue();
  }

  /**
   * 提供精确的减法运算。
   *
   * @param v1 被减数
   * @param v2 减数
   * @return 两个参数的差
   */
  public static double sub(double v1, double v2) {
    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));

    return b1.subtract(b2).doubleValue();
  }

  /**
   * 提供精确的乘法运算。
   *
   * @param v1 被乘数
   * @param v2 乘数
   * @return 两个参数的积
   */
  public static double mul(double v1, double v2) {
    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));

    return b1.multiply(b2).doubleValue();
  }

  /**
   * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 小数点以后10位,以后的数字四舍五入。
   *
   * @param v1 被除数
   * @param v2 除数
   * @return 两个参数的商
   */
  public static double div(double v1, double v2) {
    return div(v1, v2, DEF_DIV_SCALE);
  }

  /**
   * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 定精度,以后的数字四舍五入。
   *
   * @param v1 被除数
   * @param v2 除数
   * @param scale 表示表示需要精确到小数点以后几位。
   * @return 两个参数的商
   */
  public static double div(double v1, double v2, int scale) {
    if (scale < 0) {
      throw new IllegalArgumentException(
        "The scale must be a positive integer or zero");
    }

    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));

    return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
  }

  /**
   * 提供精确的小数位四舍五入处理。
   *
   * @param v 需要四舍五入的数字
   * @param scale 小数点后保留几位
   * @return 四舍五入后的结果
   */
  public static double round(double v, int scale) {
    if (scale < 0) {
      throw new IllegalArgumentException(
        "The scale must be a positive integer or zero");
    }

    BigDecimal b = new BigDecimal(Double.toString(v));
    BigDecimal one = new BigDecimal("1");

    return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
  }
}

结束语:

在 Java 程序中使用浮点数和小数充满着陷阱。浮点数和小数不象整数一样“循规蹈矩”,不能假定浮点计算一定产生整型或精确的结果,虽然它们的确“应该”那样做。最好将浮点运算保留用作计算本来就不精确的数值,譬如测量。如果需要表示定点数(譬如,几美元和几美分),则使用 BigDecimal 。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

更多精彩内容其他人还在看

Java数据类型的规则

这篇文章主要介绍了Java数据类型的规则的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Spring整合TimerTask实现定时任务调度

这篇文章主要介绍了Spring整合TimerTask实现定时任务调度的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

详解SpringMVC使用MultipartFile实现文件的上传

本篇文章主要介绍了SpringMVC使用MultipartFile实现文件的上传,本地的文件上传到资源服务器上,比较好的办法就是通过ftp上传。这里是结合SpringMVC+ftp的形式上传的,有兴趣的可以了解一下。
收藏 0 赞 0 分享

SpringMVC上传文件的三种实现方式

本篇文章主要介绍了SpringMVC上传文件的三种实现方式,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
收藏 0 赞 0 分享

微信公众帐号开发-自定义菜单的创建及菜单事件响应的实例

本篇文章主要介绍了微信公众帐号开发-自定义菜单的创建及菜单事件响应的实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
收藏 0 赞 0 分享

浅析Java中的继承与组合

本文将介绍组合和继承的概念及区别,并从多方面分析在写代码时如何进行选择。文中通过示例代码介绍的很详细,有需要的朋友可以参考借鉴,下面来一起看看吧。
收藏 0 赞 0 分享

利用反射获取Java类中的静态变量名及变量值的简单实例

下面小编就为大家带来一篇利用反射获取Java类中的静态变量名及变量值的简单实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
收藏 0 赞 0 分享

java启动线程的3种方式对比分析

这篇文章主要为大家对比分析了java启动线程的3种方式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

SpringMVC上传和解析Excel方法

这篇文章主要介绍了SpringMVC上传和解析Excel方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

JAVA中String类与StringBuffer类的区别

这篇文章主要为大家详细介绍了JAVA中String类与StringBuffer类的区别,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享
查看更多