double类型精度丢失问题以及解决方法
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类型精度丢失问题以及解决方法相关推荐
- java中double类型精度丢失问题及解决方法
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源: https://blog.csdn.net/yacolsp ...
- double operator[](int i)_java中double类型精度丢失问题及解决方法
原文链接:https://blog.csdn.net/yacolspace/article/details/78287394 double类型数据加减操作精度丢失问题 今天在项目中用到double类型 ...
- [ JAVA编程 ] double类型计算精度丢失问题及解决方法
[ JAVA编程 ] double类型计算精度丢失问题及解决方法 参考文章: (1)[ JAVA编程 ] double类型计算精度丢失问题及解决方法 (2)https://www.cnblogs.co ...
- double java 精度丢失_java中double和float精度丢失问题及解决方法
在讨论两位double数0.2和0.3相加时,毫无疑问他们相加的结果是0.5.但是问题总是如此吗? 下面我们让下面两个doubles数相加,然后看看输出结果: @Test public void te ...
- js做四则运算时,精度丢失问题及解决方法
js做四则运算时,精度丢失问题及解决方法 参考文章: (1)js做四则运算时,精度丢失问题及解决方法 (2)https://www.cnblogs.com/smile-tianxia/p/117149 ...
- Java中double类型精度丢失的问题_double类型数据加减操作精度丢失解决方法_BigDecimal取整
BigDecimal在用double做入参的时候,二进制无法精确地表示十进制小数,编译器读到字符串"0.0000002"和"1.0000002"之后,必须把它转 ...
- PHP怎样防止小数点精度不丢失,javascript小数精度丢失的完美解决方法
@H_3010@ 原因:js按照2进制来处理小数的加减乘除,在arg1的基础上 将arg2的精度进行扩展或逆扩展匹配,所以会出现如下情况. @H301_0@javascript(js)的小数点加减乘除 ...
- Java接口long类型精度丢失,解决前后端交互Long类型精度丢失问题
雪花算法ID,对应的后端Long类型,前端number类型,它们的精度不一样,导致精度丢失 现象 雪花算法得到的ID较长,传到前端后,精度丢失 库中:23754851322302474 后端:2375 ...
- 解决前后端交互Long类型精度丢失的问题
雪花算法ID,对应的后端Long类型,前端number类型,它们的精度不一样,导致精度丢失 文章目录 一.现象与分析 1.1. 现象 1.2. 分析 二.解决方案 2.1. 方法一单个注解 2.2. ...
最新文章
- 什么数字万用表可以测量噪声?
- ols线性回归_普通最小二乘[OLS]方法使用于机器学习的简单线性回归变得容易
- 2017.9.30 物流运输 思考记录
- 江苏省2021年高考成绩查询有分数吗,2021年江苏省高考成绩查询入口
- 在centos 7.0上利用yum一键安装mono
- Web站点风格切换的实现
- SQL语言学习随手记——二进制与十六进制之间的转换
- MAC中安装Navicat Premium
- 攻防世界逆向入门题之logmein
- 服务器iis的作用,Web 服务器 (IIS) 概述
- SPL lookup
- 针对Sql Server中进行查询操作时提示“对象名无效”
- 关于数据库中存储密码的加密
- 日语语法(四):形容词
- 对拉勾网数据分析职位做数据分析
- Access内置SQL函数
- 使用VMware 16 安装中标麒麟 7
- 关于JS的一些面试题
- 蓝桥杯---Cowboys---DP
- 七龙纪2-戒指和项链属性说明