double类型精度丢失问题:

(1)加法运算。

public static void main(String[] args) {double number1 = 1;double number2 = 20.2;double number3 = 300.03;double result = number1 + number2 + number3;System.out.println("使用double运算结果: "+result);
}

打印结果如下:

        使用double运算结果: 321.22999999999996。。。

 (2)减法运算。

double d1 = 2.11;
double d2 = 2.10;
System.out.println( d1 - d2);

口算结果是0.01。可是用程序执行出来的结果却出乎意料,执行结果为0.009999999999999787。

(3)乘法运算。

public static void main(String[] args) {double a = 152.70;System.out.println(a);System.out.println(a * 100);//15280.000000000002System.out.println(Math.round(a * 100));//还凑合double b = 152.70;System.out.println(b);System.out.println(b * 100);//15269.999999999998System.out.println((int)(b * 100));//15269 错误!!!不是想要的
}

然后把15280.000000000002 - 15280 = 1.8189894035458565E-12这个结果返回给了前端,

前端拿去和0比较, 结果就炸了

原因:就是double的精度问题, 单个double数可能就有损失精度,,多个double数运算(减法和乘法都输入加法)可能导致损失的精度更多。。。

为什么double类型会出现精度丢失问题?

我们知道,计算机发展了如此长的一段时间,但它始终只能识别0和1(即二进制)。无论我们使用哪种编程语言,在哪种编译环境下工作,都要先把源代码翻译成二进制的机器码才能被计算机所识别。

举个简单的例子,在源程序里面2.4,是十进制的,但计算机不能直接识别,要先编译成二进制。

那么问题来了,2.4的二进制并非是精确的2.4,反而是最为接近的二进制表示是2.39999999996。

原因在于浮点数由两部分组成:指数和尾数。如果知道怎么进行浮点数的二进制和十进制转换,应该不难理解。

如果在这个转换过程中,浮点数参与了计算,那么在转换的过程中就会变得不可预知,并且变得不可逆。我们有理由相信,就是在这个过程中,发生了精度的丢失。

而至于为什么有些浮点计算会得到准确的结果,应该也是碰巧那个计算二进制和十进制之间能够准确转换。

而当输出单个浮点型数据时,可以正确输出,如:

Double num3 = 2.4;
System.out.println(num3);

输出的结果是2.4,而不是2.39999999996。也就是说,不进行浮点计算的时候,在十进制里浮点数能正确显示。

这正印证了上面的说法,即如果浮点数参与了计算,那么浮点数二进制与十进制的转换过程就会变得不可预知,并且变得不可逆。

结论就是,在Java中,浮点数不精确,因为计算机内部无法用二进制的小数来精确的表达。

经典问题:浮点数精度丢失

精度丢失的问题是在其他计算机语言中也都会出现,float和double类型的数据在执行二进制浮点运算的时候,并没有提供完全精确的结果。

产生误差不在于数的大小,而是因为数的精度。

关于浮点数存储精度丢失的问题,话题过于庞大,感兴趣的同学可以自行搜索一下:【解惑】剖析float型的内存存储和精度丢失问题

这里简单讨论一下十进制数转二进制为什么会出现精度丢失的现象,十进制数分为整数部分和小数部分,我们分开来看看就知道原因为何:

十进制整数如何转化为二进制整数?
将被除数每次都除以2,只要除到商为0就可以停止这个过程。

5 / 2 = 2 余 1

2 / 2 = 1 余 0

1 / 2 = 0 余 1

// 结果为 101

这个算法永远都不会无限循环,整数永远都可以使用二进制数精确表示,但小数呢?

十进制小数如何转化为二进制数?
每次将小数部分乘2,取出整数部分,如果小数部分为0,就可以停止这个过程

0.1 * 2 = 0.2 取整数部分0

0.2 * 2 = 0.4 取整数部分0

0.4 * 2 = 0.8 取整数部分0

0.8 * 2 = 1.6 取整数部分1

0.6 * 2 = 1.2 取整数部分1

0.2 * 2 = 0.4 取整数部分0

//... 我想写到这就不必再写了,你应该也已经发现,上面的过程已经开始循环,小数部分永远不能为0

这个算法有一定概率会存在无限循环,即无法用有限长度的二进制数表示十进制的小数,这就是精度丢失问题产生的原因。

浮点数并不适合用于精确计算,而适合进行科学计算。

float和double型用来表示带有小数点的数,那为什么我们不称他们为小数或实数,要叫浮点数呢?是因为这些数都以科学计数法的形式存储。

当一个数如50.534,转换成科学计数法的形式为5.053e1,它 的小数点移动到了一个新的位置(即浮动了)。

可见,浮点数本来就是用于科学计算的,用来进行精确计算实在太不合适了。

如何用BigDecimal解决double精度问题?

我们已经明白为什么精度会存在丢失现象,那么我们就应该知道,当某个业务场景对double数据的精度要求非常高时,就必须采取某种手段来处理这个问题,

这也是BigDecimal为什么会被广泛应用于金额支付场景中的原因啦。

BigDecimal类位于java.math包下,用于对超过16位有效位的数进行精确的运算。

一般来说,double类型的变量可以处理16位有效数,

但实际应用中,如果超过16位,就需要BigDecimal类来操作。

new BigDecimal(double val)
        该方法是不可预测的,以0.1为例,你以为你传了一个double类型的0.1,最后会返回一个值为0.1的BigDecimal吗?不会的,原因在于,0.1无法用有限长度的二进制数表示,无法精确地表示为双精度数,最后的结果会是0.100000xxx。

new BigDecimal(String val)
        该方法是完全可预测的,也就是说你传入一个字符串"0.1",他就会给你返回一个值完全为0,1的BigDecimal,官方也表示,能用这个构造函数就用这个构造函数叭。

BigDecimal.valueOf(double val)
        第二种构造方式已经足够优秀,可你还是想传入一个double值,怎么办呢?官方其实提供给你思路并且实现了它,可以使用Double.toString(double val)先将double值转为String,再调用第二种构造方式,你可以直接使用静态方法:valueOf(double val)。

总结:将double转为BigDecimal的时候,需要先把double转换为字符串,然后再作为BigDecimal(String val)构造函数的参数,这样才能避免出现精度问题。

​​​​​​​

以下是提供的double精确运算工具类(如果计算的数值总和很大很大,超过50 0000,请使用带有ToStr的方法,不然double数字会转成科学计数法显示)

package com.phone.common_library;import java.math.BigDecimal;public class BigDecimalManager {/*** double类型的加法运算(不需要舍入)* @param m1* @param m2* @return  不加doubleValue()则, 返回BigDecimal对象*/public static double additionDouble(double m1, double m2) {BigDecimal p1 = new BigDecimal(Double.toString(m1));BigDecimal p2 = new BigDecimal(Double.toString(m2));return p1.add(p2).doubleValue();}/*** double类型的加法运算(需要舍入,保留三位小数)* @param m1* @param m2* @return  不加doubleValue()则, 返回BigDecimal对象*/public static double additionDouble(double m1, double m2, int scale) {BigDecimal p1 = new BigDecimal(Double.toString(m1));BigDecimal p2 = new BigDecimal(Double.toString(m2));return p1.add(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();}/*** double类型的超大数值加法运算(需要舍入,保留三位小数)* @param m1* @param m2* @return  不加doubleValue()则, 返回BigDecimal对象*/public static String additionDoubleToStr(double m1, double m2, int scale) {BigDecimal p1 = new BigDecimal(Double.toString(m1));BigDecimal p2 = new BigDecimal(Double.toString(m2));return p1.add(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).toPlainString();}/*** double类型的减法运算* @param m1* @param m2* @return  不加doubleValue()则, 返回BigDecimal对象*/public static double subtractionDouble(double m1, double m2) {BigDecimal p1 = new BigDecimal(Double.toString(m1));BigDecimal p2 = new BigDecimal(Double.toString(m2));return p1.subtract(p2).doubleValue();}/*** double类型的减法运算(需要舍入,保留三位小数)* @param m1* @param m2* @return  不加doubleValue()则, 返回BigDecimal对象*/public static double subtractionDouble(double m1, double m2, int scale) {BigDecimal p1 = new BigDecimal(Double.toString(m1));BigDecimal p2 = new BigDecimal(Double.toString(m2));return p1.subtract(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();}/*** double类型的超大数值减法运算(需要舍入,保留三位小数)* @param m1* @param m2* @return  不加doubleValue()则, 返回BigDecimal对象*/public static String subtractionDoubleToStr(double m1, double m2, int scale) {BigDecimal p1 = new BigDecimal(Double.toString(m1));BigDecimal p2 = new BigDecimal(Double.toString(m2));return p1.subtract(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).toPlainString();}/*** double类型的乘法运算* @param m1* @param m2* @return  不加doubleValue()则, 返回BigDecimal对象*/public static double multiplicationDouble(double m1, double m2) {BigDecimal p1 = new BigDecimal(Double.toString(m1));BigDecimal p2 = new BigDecimal(Double.toString(m2));return p1.multiply(p2).doubleValue();}/*** double类型的乘法运算* @param m1* @param m2* @return  不加doubleValue()则, 返回BigDecimal对象*/public static double multiplicationDouble(double m1, double m2, int scale) {BigDecimal p1 = new BigDecimal(Double.toString(m1));BigDecimal p2 = new BigDecimal(Double.toString(m2));return p1.multiply(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();}/*** double类型的超大数值的乘法运算* @param m1* @param m2* @return  不加doubleValue()则, 返回BigDecimal对象*/public static String multiplicationDoubleToStr(double m1, double m2, int scale) {BigDecimal p1 = new BigDecimal(Double.toString(m1));BigDecimal p2 = new BigDecimal(Double.toString(m2));return p1.multiply(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).toPlainString();}/*** double类型的除法运算* @param   m1* @param   m2* @param   scale* @return  不加doubleValue()则, 返回BigDecimal对象*/public static double divisionDouble(double m1, double m2, int scale) {if (scale < 0) {throw new IllegalArgumentException("Parameter error");}BigDecimal p1 = new BigDecimal(Double.toString(m1));BigDecimal p2 = new BigDecimal(Double.toString(m2));return p1.divide(p2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();}/*** double类型的超大数值的除法运算* @param   m1* @param   m2* @param   scale* @return  不加doubleValue()则, 返回BigDecimal对象*/public static String divisionDoubleToStr(double m1, double m2, int scale) {if (scale < 0) {throw new IllegalArgumentException("Parameter error");}BigDecimal p1 = new BigDecimal(Double.toString(m1));BigDecimal p2 = new BigDecimal(Double.toString(m2));return p1.divide(p2, scale, BigDecimal.ROUND_HALF_UP).toPlainString();}}

·测试、验证问题是否解决:

public static void main(String[] args) {double result1 = additionDouble(1, 20.2);double result2 = additionDouble(result1, 300.03);System.out.println("使用BigDecimal运算结果:" + result2);
}

结果如下:

使用BigDecimal运算结果:321.23

完美解决了double类型数据加减操作时精度丢失的问题。

如对此有疑问,请联系qq1164688204。

推荐Android开源项目

项目功能介绍:RxJava2和Retrofit2项目,添加自动管理token功能,添加RxJava2生命周期管理,使用App架构设计是MVP模式和MVVM模式,同时使用组件化,部分代码使用Kotlin,此项目持续维护中。

项目地址:https://gitee.com/urasaki/RxJava2AndRetrofit2

double类型精度丢失问题以及解决方法相关推荐

  1. java中double类型精度丢失问题及解决方法

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源: https://blog.csdn.net/yacolsp ...

  2. double operator[](int i)_java中double类型精度丢失问题及解决方法

    原文链接:https://blog.csdn.net/yacolspace/article/details/78287394 double类型数据加减操作精度丢失问题 今天在项目中用到double类型 ...

  3. [ JAVA编程 ] double类型计算精度丢失问题及解决方法

    [ JAVA编程 ] double类型计算精度丢失问题及解决方法 参考文章: (1)[ JAVA编程 ] double类型计算精度丢失问题及解决方法 (2)https://www.cnblogs.co ...

  4. double java 精度丢失_java中double和float精度丢失问题及解决方法

    在讨论两位double数0.2和0.3相加时,毫无疑问他们相加的结果是0.5.但是问题总是如此吗? 下面我们让下面两个doubles数相加,然后看看输出结果: @Test public void te ...

  5. js做四则运算时,精度丢失问题及解决方法

    js做四则运算时,精度丢失问题及解决方法 参考文章: (1)js做四则运算时,精度丢失问题及解决方法 (2)https://www.cnblogs.com/smile-tianxia/p/117149 ...

  6. Java中double类型精度丢失的问题_double类型数据加减操作精度丢失解决方法_BigDecimal取整

    BigDecimal在用double做入参的时候,二进制无法精确地表示十进制小数,编译器读到字符串"0.0000002"和"1.0000002"之后,必须把它转 ...

  7. PHP怎样防止小数点精度不丢失,javascript小数精度丢失的完美解决方法

    @H_3010@ 原因:js按照2进制来处理小数的加减乘除,在arg1的基础上 将arg2的精度进行扩展或逆扩展匹配,所以会出现如下情况. @H301_0@javascript(js)的小数点加减乘除 ...

  8. Java接口long类型精度丢失,解决前后端交互Long类型精度丢失问题

    雪花算法ID,对应的后端Long类型,前端number类型,它们的精度不一样,导致精度丢失 现象 雪花算法得到的ID较长,传到前端后,精度丢失 库中:23754851322302474 后端:2375 ...

  9. 解决前后端交互Long类型精度丢失的问题

    雪花算法ID,对应的后端Long类型,前端number类型,它们的精度不一样,导致精度丢失 文章目录 一.现象与分析 1.1. 现象 1.2. 分析 二.解决方案 2.1. 方法一单个注解 2.2. ...

最新文章

  1. 什么数字万用表可以测量噪声?
  2. ols线性回归_普通最小二乘[OLS]方法使用于机器学习的简单线性回归变得容易
  3. 2017.9.30 物流运输 思考记录
  4. 江苏省2021年高考成绩查询有分数吗,2021年江苏省高考成绩查询入口
  5. 在centos 7.0上利用yum一键安装mono
  6. Web站点风格切换的实现
  7. SQL语言学习随手记——二进制与十六进制之间的转换
  8. MAC中安装Navicat Premium
  9. 攻防世界逆向入门题之logmein
  10. 服务器iis的作用,Web 服务器 (IIS) 概述
  11. SPL lookup
  12. 针对Sql Server中进行查询操作时提示“对象名无效”
  13. 关于数据库中存储密码的加密
  14. 日语语法(四):形容词
  15. 对拉勾网数据分析职位做数据分析
  16. Access内置SQL函数
  17. 使用VMware 16 安装中标麒麟 7
  18. 关于JS的一些面试题
  19. 蓝桥杯---Cowboys---DP
  20. 七龙纪2-戒指和项链属性说明

热门文章

  1. ubuntu安装dotnet
  2. 不会使用计算机的人怎么办,平时用电脑办公的人,这5件事情别做了
  3. 获取当年所有工作日及节假日信息
  4. 巴黎不同时期城市和城市规划的特点
  5. 鼠标的直接下滑,然后打开页面直接到底部解决方式;
  6. Word Ladder
  7. 如何检验光盘镜像(ISO格式文件)并刻录系统安装光盘?
  8. 网关与微服务的概念及在微服务架构中的作用详解
  9. 十个漂亮的数学定理赏析
  10. 动态添加OracleParameter 参数