BigDecimal详解和精度问题

一、背景

在实际开发中,对于 不需要任何准确计算精度的属性可以直接使用float或double,但是如果需要精确计算结果,则必须使用BigDecimal,例如价格、质量。

为什么这么说,主要有两点

1、double计算会有精度丢失问题

2、在除法运算时,BigDecimal提供了丰富的取舍规则。(double虽然可以通过NumberFormat进行四舍五入,但是NumberFormat是线程不安全的)

对于精度问题我们可以看下实际的例子

  public static void main(String[] args) {
//正常 3.3
System.out.println("加法结果:"+(1.1+2.2));
//正常 -7.9
System.out.println("减法结果:"+(2.2-10.1));
//正常 2.42
System.out.println("乘法结果:"+(1.1*2.2));
//正常 0.44
System.out.println("除法结果:"+(4.4/10));
}

实际控制台输出

为什么会这样原因?

在于我们的计算机是二进制的。浮点数没有办法是用二进制进行精确表示。我们的CPU表示浮点数由两个部分组成:指数和尾数,这样的表示方法一般都会

失去一定的精确度,有些浮点数运算也会产生一定的误差。如:2.4的二进制表示并非就是精确的2.4。反而最为接近的二进制表示是 2.3999999999999999。

浮点数的值实际上是由一个特定的数学公式计算得到的。

二、BigDecimal构造函数

1、四种构造函数

BigDecimal(int)     //创建一个具有参数所指定整数值的对象。
BigDecimal(double) //创建一个具有参数所指定双精度值的对象。
BigDecimal(long) //创建一个具有参数所指定长整数值的对象。
BigDecimal(String) //创建一个具有参数所指定以字符串表示的数值的对象。

这几个都是常用的构造器,他们返回的对象都是BigDecimal对象。换而言之,将BigDecimal对象转换为其他类型的对象,我们通过以下几种。

toString()          //将BigDecimal对象的数值转换成字符串。
doubleValue() //将BigDecimal对象中的值以双精度数返回。
floatValue() //将BigDecimal对象中的值以单精度数返回。
longValue() //将BigDecimal对象中的值以长整数返回。
intValue() //将BigDecimal对象中的值以整数返回。

这里需要非常注意BigDecimal(double)的构造函数,也是会存在精度丢失的问题,其它的不会,这里也可以举例说明

public static void main(String[] args) {
BigDecimal intDecimal = new BigDecimal(10);
BigDecimal doubleDecimal = new BigDecimal(4.3);
BigDecimal longDecimal = new BigDecimal(10L);
BigDecimal stringDecimal = new BigDecimal("4.3");
System.out.println("intDecimal=" + intDecimal);
System.out.println("doubleDecimal=" + doubleDecimal);
System.out.println("longDecimal=" + longDecimal);
System.out.println("stringDecimal=" + stringDecimal);
}

控制台实际输出

从图中很明显可以看出,对于double的构造函数是会存在精度丢失的可能的

2、为什么会出现这种情况

这个在new BigDecimal(double)类型的构造函数上的注解有解释说明。

这个构造函数的结果可能有些不可预测。 可以假设在Java中写入new BigDecimal(0.1)创建一个BigDecimal ,它完全等于0.1(非标尺值为1,比例为1),但实际上等于

0.1000000000000000055511151231257827021181583404541015625。 这是因为0.1不能像double (或者作为任何有限长度的二进制分数)精确地表示。

因此,正在被传递给构造的值不是正好等于0.1。

3、如何解决

有两种常用的解决办法。

1、是将double 通过Double.toString(double)先转为String,然后放入BigDecimal的String构造函数中。

2、不通过BigDecimal的构造函数,而是通过它的静态方法BigDecimal.valueOf(double),也同样不会丢失精度。

示例

 public static void main(String[] args) {
String string = Double.toString(4.3);
BigDecimal stringBigDecimal = new BigDecimal(string);
BigDecimal bigDecimal = BigDecimal.valueOf(4.3);
System.out.println("stringBigDecimal = " + stringBigDecimal);
System.out.println("bigDecimal = " + bigDecimal);
}

运行结果

这样也能保证,对与double而言,转BigDecimal不会出现精度丢失的情况。

三、常用方法

1、常用方法

示例

public static void main(String[] args) {
BigDecimal a = new BigDecimal("4.5");
BigDecimal b = new BigDecimal("1.5");
BigDecimal c = new BigDecimal("-10.5"); BigDecimal add_result = a.add(b);
BigDecimal subtract_result = a.subtract(b);
BigDecimal multiply_result = a.multiply(b);
BigDecimal divide_result = a.divide(b);
BigDecimal remainder_result = a.remainder(b);
BigDecimal max_result = a.max(b);
BigDecimal min_result = a.min(b);
BigDecimal abs_result = c.abs();
BigDecimal negate_result = a.negate(); System.out.println("4.5+1.5=" + add_result);
System.out.println("4.5-1.5=" + subtract_result);
System.out.println("4.5*1.5=" + multiply_result);
System.out.println("4.5/1.5=" + divide_result);
System.out.println("4.5/1.5余数=" + remainder_result);
System.out.println("4.5和1.5最大数=" + max_result);
System.out.println("4.5和1.5最小数=" + min_result);
System.out.println("-10.5的绝对值=" + abs_result);
System.out.println("4.5的相反数=" + negate_result);
}

运行结果

4.5+1.5=6.0
4.5-1.5=3.0
4.5*1.5=6.75
4.5/1.5=3
4.5/1.5余数=0.0
4.5和1.5最大数=4.5
4.5和1.5最小数=1.5
-10.5的绝对值=10.5
4.5的相反数=-4.5

这里把除法单独再讲一下,因为除法操作的时候会有除不尽的情况,,比如 3,5/3,这时会报错java.lang.ArithmeticException: Non-terminating decimal expansion;

no exact representable decimal result。所以这里要考虑除不尽的情况下,保留几位小数,取舍规则。(除法如果可能存在除不进,那就用下面方法)

BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) 第一参数表示除数,第二个参数表示小数点后保留位数,第三个参数表示取舍规则。

2、取舍规则

ROUND_UP          //不管保留数字后面是大是小(0除外)都会进1
ROUND_DOWN //保留设置数字,后面所有直接去除
ROUND_HALF_UP //常用的四舍五入
ROUND_HALF_DOWN //五舍六入
ROUND_CEILING //向正无穷方向舍入
ROUND_FLOOR //向负无穷方向舍入
ROUND_HALF_EVEN //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,如果保留位数是奇数,使用ROUND_HALF_UP,如果是偶数,使用ROUND_HALF_DOWN
ROUND_UNNECESSARY //计算结果是精确的,不需要舍入模式

注意 我们最常用的应该是 ROUND_HALF_UP(四舍五入)

上面这样解释还是有点模糊,具体可以看这篇文章,示例非常清楚 BigDecimal的四舍五入的RoundingMode 选择

这里举几个常用的取舍规则

 public static void main(String[] args) {

        BigDecimal a = new BigDecimal("1.15");
BigDecimal b = new BigDecimal("1"); //不管保留数字后面是大是小(0除外)都会进1 所以这里输出为1.2
BigDecimal divide_1 = a.divide(b,1,BigDecimal.ROUND_UP);
//保留设置数字,后面所有直接去除 所以这里输出为1.1
BigDecimal divide_2 = a.divide(b,1,BigDecimal.ROUND_DOWN);
//常用的四舍五入 所以这里输出1.2
BigDecimal divide_3 = a.divide(b,1,BigDecimal.ROUND_HALF_UP);
//这个可以理解成五舍六入 所以这里输出1.1
BigDecimal divide_4 = a.divide(b,1,BigDecimal.ROUND_HALF_DOWN);
//这里将1.15改成1.16
BigDecimal c = new BigDecimal("1.16");
//那么这里就符合六入了 所以输出变为1.2
BigDecimal divide_5 = c.divide(b,1,BigDecimal.ROUND_HALF_DOWN);
System.out.println("divide_1 = " + divide_1);
System.out.println("divide_2 = " + divide_2);
System.out.println("divide_3 = " + divide_3);
System.out.println("divide_4 = " + divide_4);
System.out.println("divide_5 = " + divide_5); }

运行结果

divide_1 = 1.2
divide_2 = 1.1
divide_3 = 1.2
divide_4 = 1.1
divide_5 = 1.2

四、格式化

由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。

以利用BigDecimal对货币和百分比格式化为例。首先,创建BigDecimal对象,进行BigDecimal的算术运算后,分别建立对货币和百分比格式化的引用,最后利用

BigDecimal对象作为format()方法的参数,输出其格式化的货币值和百分比。

示例

    public static void main(String[] args) {
//建立货币格式化引用
NumberFormat currency = NumberFormat.getCurrencyInstance();
//建立百分比格式化引用
NumberFormat percent = NumberFormat.getPercentInstance();
//百分比小数点最多3位
percent.setMaximumFractionDigits(3);
//取整
NumberFormat integerInstance = NumberFormat.getIntegerInstance();
////金额
BigDecimal loanAmount = new BigDecimal("188.555");
////利率
BigDecimal interestRate = new BigDecimal("0.018555555");
//没有指定保留位数的情况下 默认保留2位
System.out.println("金额: " + currency.format(loanAmount));
//货币(百分比)格式化 指定默认的取舍规则是四舍五入
System.out.println("利率: " + percent.format(interestRate));
//取整还有点不一样 188.555取整为189, 188.51也是189 但是189.5确实188,所以它不是真正意义上的四舍五入
System.out.println("取整: " + integerInstance.format(loanAmount));
}

运行结果

金额: ¥188.56
利率: 1.856%
取整: 189

这里有几点在说明下

1、格式化的时候没有指定保留位数的情况下 默认保留2位

2、货币(百分比)格式化 指定默认的取舍规则是四舍五入

3、取整还有点不一样 188.555取整为189, 188.51也是189 但是189.5确实188,所以它不是真正意义上的四舍五入

有关NumberFormat详细介绍可以参考文章:NumberFormat--数字格式化类

别人骂我胖,我会生气,因为我心里承认了我胖。别人说我矮,我就会觉得好笑,因为我心里知道我不可能矮。这就是我们为什么会对别人的攻击生气。
攻我盾者,乃我内心之矛(28)

【java提高】(19)---BigDecimal详解和精度问题的更多相关文章

  1. java提高篇之详解内部类

    可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一个非常有用的特性但又比较难理解使用的特性(鄙人到现在都没有怎么使用过内部类,对内部类也只是略知一二). 第一次见面 内部类我们从外面 ...

  2. java提高篇(十)-----详解匿名内部类

    在java提高篇-----详解内部类中对匿名内部类做了一个简单的介绍,但是内部类还存在很多其他细节问题,所以就衍生出这篇博客.在这篇博客中你可以了解到匿名内部类的使用.匿名内部类要注意的事项.如何初始 ...

  3. java提高篇(八)----详解内部类

    可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一个非常有用的特性但又比较难理解使用的特性(鄙人到现在都没有怎么使用过内部类,对内部类也只是略知一二). 第一次见面 内部类我们从外面 ...

  4. 【转】java提高篇(十)-----详解匿名内部类

    原文网址:http://www.cnblogs.com/chenssy/p/3390871.html 在java提高篇-----详解内部类中对匿名内部类做了一个简单的介绍,但是内部类还存在很多其他细节 ...

  5. java提高篇(十)-----详解匿名内部类 ,形参为什么要用final

    在java提高篇-----详解内部类中对匿名内部类做了一个简单的介绍,但是内部类还存在很多其他细节问题,所以就衍生出这篇博客.在这篇博客中你可以了解到匿名内部类的使用.匿名内部类要注意的事项.如何初始 ...

  6. Java 8 Stream API详解--转

    原文地址:http://blog.csdn.net/chszs/article/details/47038607 Java 8 Stream API详解 一.Stream API介绍 Java8引入了 ...

  7. java和javascript日期详解

    ** java,js日期转换:** <Excerpt in index | 首页摘要> java的各种日期转换 <The rest of contents | 余下全文> 日期 ...

  8. java反射机制深入详解

    java反射机制深入详解  转自:http://www.cnblogs.com/hxsyl/archive/2013/03/23/2977593.html 一.概念 反射就是把Java的各种成分映射成 ...

  9. java之AbstractStringBuilder类详解

    目录 AbstractStringBuilder类 字段 构造器 方法   public abstract String toString() 扩充容量 void  expandCapacity(in ...

随机推荐

  1. 面向对象之多态(Java实现)

    本文借鉴于csdn,博客园,b站等各大知识分享平台 之前学习了封装与继承,封装就是数据的封装性(大致理解),继承就是一个类继承另一个类的属性,称为父子类 多态 多态是面向对象的第三大特性(共三大特性) ...

  2. Halcon一维测量1D Measuring解析

    一维测量(也叫一维计量或卡尺)的概念非常直观.沿着一个预定的区域(主要是垂直于RIO感兴趣区域的方向) 边缘的位置.这里的边缘为从暗到亮或从亮到暗的过渡. 基于提取的边缘,可以测量零件的尺寸.例如,可 ...

  3. 基于html5拖拽api实现列表的拖拽排序

    基于html5拖拽api实现列表的拖拽排序 html代码: <ul ondrop="drop_handler(event);" ondragover="dragov ...

  4. 最佳开发工具大全,GitHub Star 6.2k+

    一位曾经的谷歌工程师,花费两年时间,辛苦整理了一份清单.本文转自量子位,作者晓查.栗子.方驭洋,如有侵,可删! 这个名为 "xg2xg" 的清单,原本是这位前谷歌工程师(ex-Go ...

  5. 开会时CPU 飙升100%同事们都手忙脚乱记一次应急处理过程

    告警 正在开会,突然钉钉告警声响个不停,同时市场人员反馈客户在投诉系统登不进了,报504错误.查看钉钉上的告警信息,几台业务服务器节点全部报CPU超过告警阈值,达100%. 赶紧从会上下来,SSH登录 ...

  6. 状态模式(c++实现)

    状态模式 目录 状态模式 模式定义 模式动机 UML类图 源码实现 优点 缺点 模式定义 状态模式(state),当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类. 模式动机 状 ...

  7. CentOS开机启动不了修复

    1,如果启动时进度条,先修改为日志启动 启动后快速按任何键(Enter除外)进入如下界面 在按e进入 选择第二个选项卡 在按e进入将红色部分 rhgb quiet 删除,然后按Enter,在按b重启 ...

  8. Python Ethical Hacking - BACKDOORS(4)

    REVERSE_BACKDOOR - cd command Access file system: cd command changes current working directory. It h ...

  9. QQ音乐Android客户端Web页面通用性能优化实践

    QQ音乐 Android 客户端的 Web 页面日均 PV 达到千万量级,然而页面的打开耗时与 Native 页面相距甚远,需要系统性优化.本文将介绍 QQ 音乐 Android 客户端在进行 Web ...

  10. 微信小程序实战:app主页面保存page页面实例

    先上代码. app.js //app.js App({ onLaunch: function () { // 登录 wx.login({ success: res => { if (this.g ...