double类型精度丢失问题以及解决方法_double精度丢失-程序员宅基地

技术标签: java  android  

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.000000000002
    System.out.println(Math.round(a * 100));//还凑合
 
    double b = 152.70;
    System.out.println(b);
    System.out.println(b * 100);//15269.999999999998
    System.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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/NakajimaFN/article/details/125912091

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue