double类型精度丢失问题以及解决方法

2023-11-18

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

double类型精度丢失问题以及解决方法 的相关文章

随机推荐

  • Cocos2dx-OpenGL ES2.0教程:编写自己的shader(2)

    在上篇文章中 我给大家介绍了如何在cocos2d x里面绘制一个三角形 当时我们使用的是cocos2d x引擎自带的shader和一些辅助函数 在本文中 我将演示一下如何编写自己的shader 同时 我们还会介绍VBO 顶点缓冲区对象 和V
  • 确知信号分析

    写在前面 现代通信原理是电子系的一门专业必修课 介绍了通信原理系统的基本概念 基本原理 基本技术以及设计和分析的方法 俗话说得好 实践出真知 要想学好这门课 必要的动手实践是必不可少的 实践是基于Matlab上实现仿真的 Matlab具有函
  • Access denied for user XX

    解决方法 将pom xml中的mysql connector java降低版本 直接去maven仓库复制 https mvnrepository com 比如
  • python+flask+mysql实现数据可视化

    准备工作 1 Python3 2 Echarts 下载地址 echarts 3 Flask 项目结构 创建DB 使用sqlite数据库 coding utf 8 author liudinglong software pycharm fil
  • 1.R语言基础知识

    目录 一 R语言的介绍 R语言的下载与按照 Rstudio R包的安装 工作空间管理 基本运算 二 R的数据结构 1 数据类型 2 数据类型的转换和判断 3 数据结构 三 导入 导出数据 获取内置数据集 获取其他格式的数据 四 数据框的常用
  • AI绘画指南:在CentOS7中安装Stable Diffusion WebUI

    一 安装GPU驱动 1 1 购买云服务器 在腾讯云选购一台GPU型的服务器 最好的境外的 境外的服务器后面关于镜像加速的步骤都可以跳过 购买成功之后腾讯的站内信会发送初始的密码给你 登录成功后会自动帮我们安装相关的驱动 提示整个过程大概需要
  • LeetCode-283. 移动零【数组,双指针】

    LeetCode 283 移动零 数组 双指针 题目描述 解题思路一 首先想到的是双指针 但是不行 非零元素的位置会改变 解题思路二 改进的是每次从当前0元素的位置取后面第一个非0元素替换过来 替换之和那个break非常重要 解题思路三 优
  • Future 和 Callable

    一 Runnable 缺陷 不能返回一个返回值 不能抛出 checked Execption 二 Callable接口 类似于Runnable 被其他线程执行的任务 实现call方法 有返回值 三 Future的作用 Callable和Fu
  • 【infiniband】 MAD、 uMAD、Verbs、RDMACM

    1 MAD Management Datagram MAD是InfiniBand网络中用于管理和配置的数据报文 它包含了各种类型的管理操作 如查询端口状态 配置端口参数等 MAD通常用于执行网络管理任务 2 uMAD User MAD uM
  • 模板类 通用数组的实现

    实现自定义数组 重载 lt lt 运算符 并且数组可以使用自定义类 头文件 ifndef MYARRAY H define MYARRAY H include
  • pandas datetime与时间戳互相转换,字符串转换datetime

    参考pandas to datetime的api 字符串转换为pandas datetime 通过to datetime函数可以把字符串转换为pandas datetime df pd DataFrame date 2011 04 24 0
  • python的xlrd、xlwt模块/pymsql使用

    xlrd模块 https www cnblogs com machangwei 8 p 10736528 html label0 xlwt模块https www cnblogs com machangwei 8 p 10738244 htm
  • Filter过滤器实现权限拦截

    一 要求 用户登陆之后才能进入主页 用户注销之后不能进入首页 二 思路 用户登陆之后 向session中放入用户的数据 进入主页的时候要判断用户是否已经登陆 在过滤器中实现 public void doFilter ServletReque
  • 电脑怎样连接打印机?分享4个简单操作!

    为了更方便学习 我买了一个打印机来打印需要用的资料 但是操作了半天还是没连接上 想请问一下有经验的朋友是怎么将打印机与电脑进行连接的呢 在现代人的工作和生活中 打印机是一个重要的设备 我们可以利用打印机进行资料 文件等的打印 但是也会有很多
  • SpringBoot调用PageHelper.startPage(Object params)报错:分页查询缺少必要的参数:XXX

    问题描述 项目中使用了MyBatis分页插件 调用以下方法实现分页 无论传入JavaBean还是Map都报错 分页查询缺少必要的参数 XXX Map
  • 【Docker】云原生利用Docker确保环境安全、部署的安全性、安全问题的主要表现和新兴技术产生

    前言 Docker 是一个开源的应用容器引擎 让开发者可以打包他们的应用以及依赖包到一个可移植的容器中 然后发布到任何流行的Linux或Windows操作系统的机器上 也可以实现虚拟化 容器是完全使用沙箱机制 相互之间不会有任何接口 云原生
  • Python学习 第二章 数据类型

    Python学习 第二章 数据类型上 1 数字 1 1 整型 int 1 2 浮点型 float 1 3 布尔类型 bool 1 4 代码实现 1 5 复数 2 字符串 string 2 1 如果字符串内容中出现了引号 2 2 代码实现 2
  • pandas生成excel文件

    可以使用pandas中的to excel 函数将DataFrame数据写入Excel文件 例如 import pandas as pd 创建测试数据 data name Mike John Bob age 25 32 45 city New
  • STM-32:SPI通信协议/W25Q64简介—软件SPI读写W25Q64

    目录 一 SPI简介 1 1电路模式 1 2通信原理 1 3SPI时序基本单元 1 3 1起始和终止 1 3 2交换字节 二 W25Q64 2 1W25Q64简介 2 2W25Q64硬件电路 2 3W25Q64框图 2 4Flash操作注意
  • double类型精度丢失问题以及解决方法

    double类型精度丢失问题 1 加法运算 public static void main String args double number1 1 double number2 20 2 double number3 300 03 dou