java的BigDecimal也会存在丢失精度的问题

2023-11-07

先说结论

务必使用BigDecimal.valueOf(1.01),或者使用 new BigDecimal("1.01") ———— 而不要使用new BigDecimal(1.01)

查看源码可以知道,BigDecimal.valueOf(double val) 的底层是 return new BigDecimal(Double.toString(val));

如下图,源码中 “是把double先转换成字符串,再转BigDecimal” 。源码文档中也说明了参数直接为double的精度问题。
第二句中说了建议使用new BigDecimal(String val)的方式,而不要使用new BigDecimal(Double val);
而且最后一句也说了建议使用BigDecimal.valueOf(double)方式;

——————————————————

正文

我们基本已经形成了常识,需要用到金钱的地方要用 BigDecimal 而不是其他,而我们也都知道浮点型变量在进行计算的时候会出现丢失精度的问题。

那么,你知道其实 BigDecimal 也会丢失精度吗?而使用 BigDecimal 的背后又有什么值得去探究的地方吗?今天,告诉你,知其然,也知其所以然。

如下一段代码:

System.out.println(0.05 + 0.01);  
System.out.println(1.0 - 0.42);  
System.out.println(4.015 * 100);  
System.out.println(123.3 / 100);  

输出:
0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999

可以看到在 Java 中进行浮点数运算的时候,会出现丢失精度的问题。那么我们如果在进行商品价格计算的时候,就会出现问题。

很有可能造成我们手中有 0.06 元,却无法购买一个 0.05 元和一个 0.01 元的商品。

因为如上所示,他们两个的总和为 0.060000000000000005。

这无疑是一个很严重的问题,尤其是当电商网站的并发量上去的时候,出现的问题将是巨大的。可能会导致无法下单,或者对账出现问题。所以接下来我们就可以使用 Java 中的 BigDecimal 类来解决这类问题。

普及一下:

Java 中 float 的精度为 6-7 位有效数字。double 的精度为 15-16 位。

API

构造器:

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

函数:

方法                    描述
add(BigDecimal)       BigDecimal对象中的值相加,然后返回这个对象。
subtract(BigDecimal)  BigDecimal对象中的值相减,然后返回这个对象。
multiply(BigDecimal)  BigDecimal对象中的值相乘,然后返回这个对象。
divide(BigDecimal)    BigDecimal对象中的值相除,然后返回这个对象。
toString()            将BigDecimal对象的数值转换成字符串。
doubleValue()         将BigDecimal对象中的值以双精度数返回。
floatValue()          将BigDecimal对象中的值以单精度数返回。
longValue()           将BigDecimal对象中的值以长整数返回。
intValue()            将BigDecimal对象中的值以整数返回。

由于一般的数值类型,例如 double 不能准确的表示 16 位以上的数字。

BigDecimal 精度也丢失

我们在使用 BigDecimal 时,使用它的 BigDecimal(String) 构造器创建对象才有意义。其他的如 BigDecimal b = new BigDecimal(1) 这种,还是会发生精度丢失的问题。如下代码:

BigDecimal a = new BigDecimal(1.01);
BigDecimal b = new BigDecimal(1.02);
BigDecimal c = new BigDecimal("1.01");
BigDecimal d = new BigDecimal("1.02");
System.out.println(a.add(b));
System.out.println(c.add(d));

输出:
2.0300000000000000266453525910037569701671600341796875
2.03

可见论丢失精度 BigDecimal 显的更为过分。但是使用 Bigdecimal 的 BigDecimal(String) 构造器的变量在进行运算的时候却没有出现这种问题。

究其原因计算机组成原理里面都有,它们的编码决定了这样的结果。

long 可以准确存储 19 位数字,而 double 只能准备存储 16 位数字。

double 由于有 exp 位,可以存 16 位以上的数字,但是需要以低位的不精确作为代价。如果需要高于 19 位数字的精确存储,则必须用 BigInteger 来保存,当然会牺牲一些性能。

所以我们一般使用 BigDecimal 来解决商业运算上丢失精度的问题的时候,声明 BigDecimal 对象的时候一定要使用它构造参数为 String 的类型的构造器。

同时这个原则 Effective Java 和 MySQL 必知必会中也都有提及。float 和 double 只能用来做科学计算和工程计算。商业运算中我们要使用 BigDecimal。

而且我们从源码的注释中官方也给出了说明,如下是 BigDecimal 类的 double 类型参数的构造器上的一部分注释说明:

* The results of this constructor can be somewhat unpredictable.  
     * One might assume that writing {@codenew BigDecimal(0.1)} in  
     * Java creates a {@code BigDecimal} which is exactly equal to  
     * 0.1 (an unscaled value of 1, with a scale of 1), but it is  
     * actually equal to  
     * 0.1000000000000000055511151231257827021181583404541015625.  
     * This is because 0.1 cannot be represented exactly as a  
     * {@codedouble} (or, for that matter, as a binary fraction of  
     * any finite length).  Thus, the value that is being passed  
     * <i>in</i> to the constructor is not exactly equal to 0.1,  
     * appearances notwithstanding.  
       ……  
        * When a {@codedouble} must be used as a source for a  
     * {@code BigDecimal}, note that this constructor provides an  
     * exact conversion; it does not give the same result as  
     * converting the {@codedouble} to a {@code String} using the  
     * {@link Double#toString(double)} method and then using the  
     * {@link #BigDecimal(String)} constructor.  To get that result,  
     * use the {@codestatic} {@link #valueOf(double)} method.  
     * </ol>  
public BigDecimal(double val) {  
    this(val,MathContext.UNLIMITED);  
}  

第一段也说的很清楚它只能计算的无限接近这个数,但是无法精确到这个数。

第二段则说,如果要想准确计算这个值,那么需要把 double 类型的参数转化为 String 类型的。并且使用 BigDecimal(String) 这个构造方法进行构造。去获取结果。

正确运用 BigDecimal

另外,BigDecimal 所创建的是对象,我们不能使用传统的 +、-、*、/ 等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是 BigDecimal 的对象,由刚才我们所罗列的 API 也可看出。

在一般开发过程中,我们数据库中存储的数据都是 float 和 double 类型的。在进行拿来拿去运算的时候还需要不断的转化,这样十分的不方便。这里我写了一个工具类:

/**  
 * @author: Ji YongGuang.  
 * @date: 19:50 2017/12/14.  
 */  
publicclass BigDecimalUtil {  

    private BigDecimalUtil() {  

    }  

    public static BigDecimal add(double v1, double v2) {// v1 + v2  
        BigDecimal b1 = new BigDecimal(Double.toString(v1));  
        BigDecimal b2 = new BigDecimal(Double.toString(v2));  
        return b1.add(b2);  
    }  

    public static BigDecimal sub(double v1, double v2) {  
        BigDecimal b1 = new BigDecimal(Double.toString(v1));  
        BigDecimal b2 = new BigDecimal(Double.toString(v2));  
        return b1.subtract(b2);  
    }  

    public static BigDecimal mul(double v1, double v2) {  
        BigDecimal b1 = new BigDecimal(Double.toString(v1));  
        BigDecimal b2 = new BigDecimal(Double.toString(v2));  
        return b1.multiply(b2);  
    }  

    public static BigDecimal div(double v1, double v2) {  
        BigDecimal b1 = new BigDecimal(Double.toString(v1));  
        BigDecimal b2 = new BigDecimal(Double.toString(v2));  
        // 2 = 保留小数点后两位   ROUND_HALF_UP = 四舍五入  
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);// 应对除不尽的情况  
    }  
}  

该工具类提供了 double 类型的基本的加减乘除运算。直接调用即可。

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

java的BigDecimal也会存在丢失精度的问题 的相关文章

  • 在java中轮询Http服务器(重复发送http get请求)

    当对其进行 REST 调用时 我的 Web 服务器会发送一些信息 我想不断轮询该服务器 间隔5秒后重复发送HTTP GET请求 以检查返回的信息是否有任何变化 做到这一点最有效的方法是什么 您能提供一些代码示例吗 请注意 我只想开发客户端代
  • 带路径压缩算法的加权 Quick-Union

    有一种 带路径压缩的加权快速联合 算法 代码 public class WeightedQU private int id private int iz public WeightedQU int N id new int N iz new
  • java中监视目录变化

    我正在使用 WatchService 来监视目录中的更改 特别是目录中新文件的创建 下面是我的代码 package watcher import java nio file import static java nio file Stand
  • Java:扩展类并实现具有相同方法的接口

    可能无法完成以下操作 我收到编译错误 继承的方法 A doSomthing int 无法隐藏 B 中的公共抽象方法 public class A int doSomthing int x return x public interface
  • 如何在java中将数组值排序为循环格式?

    我的数组值如下 String value 1 2 3 4 5 6 7 8 9 10 假设如果我将值 5 传递给 tat 数组 它应该按如下顺序排序 5 6 7 8 9 10 1 2 3 4 怎么办 有人帮忙吗 感谢你 你需要的就是所谓的轮换
  • 什么是抽象类? [复制]

    这个问题在这里已经有答案了 当我了解抽象类时 我说 WT H 问题 创建一个无法实例化的类有什么意义呢 为什么有人想要这样的课程 什么情况下需要抽象类 如果你明白我的意思 最常见的是用作基类或接口 某些语言有单独的interface构建 有
  • 如何调试“com.android.okhttp”

    在android kitkat中 URLConnection的实现已经被OkHttp取代 如何调试呢 OkHttp 位于此目录中 external okhttp android main java com squareup okhttp 当
  • 如何在 Java 中向时间戳添加/减去时区偏移量?

    我正在使用 JDK 8 并且玩过ZonedDateTime and Timestamp很多 但我仍然无法解决我面临的问题 假设我得到了格式化的Timestamp在格林威治标准时间 UTC 我的服务器位于某处 假设它设置为Asia Calcu
  • Android中如何使用JNI获取设备ID?

    我想从 c 获取 IMEIJNI 我使用下面的代码 但是遇到了未能获取的错误cls 它总是返回NULL 我检查了环境和上下文 它们都没有问题 为什么我不能得到Context班级 我在网上搜索了一下 有人说我们应该使用java lang Ob
  • java inputstream 打印控制台内容

    sock new Socket www google com 80 out new BufferedOutputStream sock getOutputStream in new BufferedInputStream sock getI
  • Mockito 使用 @Mock 时将 Null 值注入到 Spring bean 中?

    由于我是 Spring Test MVC 的新手 我不明白这个问题 我从以下代码中获取了http markchensblog blogspot in search label Spring http markchensblog blogsp
  • Sun 在 EDT 之外做 GUI 工作的演示?

    我正在看SplashDemo java http download oracle com javase tutorial uiswing examples misc SplashDemoProject src misc SplashDemo
  • Java Applet 中的 Apache FOP - 未找到数据的 ImagePreloader

    我正在研究成熟商业产品中的一个问题 简而言之 我们使用 Apache POI 库的一部分来读取 Word DOC 或 DOCX 文件 并将其转换为 XSL FO 以便我们可以进行标记替换 然后 我们使用嵌入到 Java 程序中的 FOP 将
  • Akka 与现有 java 项目集成的示例

    如果我已经有现有的javaWeb 应用程序使用spring and servlet容器 将 Akka 集成到其中的正确方法是什么 就像我将会有Actor1 and Actor2互相沟通的 开始使用这些演员的切入点是什么 例如 1 把它放在那
  • 在Java中运行bat文件并等待

    您可能会认为从 Java 启动 bat 文件是一项简单的任务 但事实并非如此 我有一个 bat 文件 它对从文本文件读取的值循环执行一些 sql 命令 它或多或少是这样的 FOR F x in CD listOfThings txt do
  • 为什么\0在java中不同系统中打印不同的输出

    下面的代码在不同的系统中打印不同的输出 String s hello vsrd replace 0 System out println s 当我在我的系统中尝试时 Linux Ubuntu Netbeans 7 1 它打印 When I
  • Spring @Cacheable 和 @Async 注解

    我需要缓存一些异步计算的结果 具体来说 为了克服这个问题 我尝试使用 Spring 4 3 缓存和异步计算功能 作为示例 我们采用以下代码 Service class AsyncService Async Cacheable users C
  • 将 JavaFX FXML 对象分组在一起

    非常具有描述性和信息性的答案将从我这里获得价值 50 声望的赏金 我正在 JavaFX 中开发一个应用程序 对于视图 我使用 FXML
  • 在java中以原子方式获取多个锁

    我有以下代码 注意 为了可读性 我尽可能简化了代码 如果我忘记了任何关键部分 请告诉我 public class User private Relations relations public User relations new Rela
  • java'assert'和'if(){}else exit;'之间的区别

    java和java有什么区别assert and if else exit 我可以用吗if else exit代替assert 也许有点谷歌 您应该记住的主要事情是 if else 语句应该用于程序流程控制 而assert 关键字应该仅用于

随机推荐

  • [LeetCode] Binary Tree Level Order Traversal 二叉树层次遍历(DFS

    目录 1 Binary Tree Level Order Traversal 二叉树层次遍历 BFS 2 Binary Tree Level Order Traversal II 二叉树层次遍历从低往高输出 BFS 3 Maximum De
  • 如何用dev-c++创建lib(静态链接库)文件

    虽说dev c 适合初学者 但是它的功能还是很强大的 那如何用它制作一个lib 静态链接库 呢 目录 第一步 制作静态链接库 第二步 链接静态链接库 方法一 使用项目 方法二 修改编译选项 第三步 使用库函数 方法一 方法二 第一步 制作静
  • git出现fatal: unable to auto-detect email address (got ‘用户@PC机.(none)’)

    git出现fatal unable to auto detect email address got 用户 PC机 none 解决方法 cd git 进入git中的 git文件夹 找到config vim config 按i进入编辑模式 在
  • Linux入门

    1 1 Linux操作系统简介 Linux是一套免费使用和自由传播的类Unix操作系统 是一个基于POSIX和UNIX的多用户 多任务 支持多线程和多CPU的操作系统 它能运行主要的UNIX工具软件 应用程序和网络协议 它支持32位和64位
  • MP4中同时有逐行、隔行编码视频内容

    公司做了个MP4视频 一部分是逐行的 一个部门是隔行扫描方式 用mediainfo看了下 整个显示视频信息为 Scan Type Interlaced Scan Order TFF 用Stream Eye观看视频 隔行部分如下图 逐行部分
  • Windows Qt设置环境变量

    目录 附 其他相关文章 问题 在Qt软件中新建项目 可以直接构建运行 但是当我们直接双击运行项目构建生成的exe文件时 会提示以下内容 由于找不到 Qt5Core dll 无法继续执行代码 重新安装程序可能会解决此问题 另外 可能还会有Qt
  • 关于vue项目在IE11上遇到的兼容性问题

    1 chunk vendors js语法错误 分析 sockjs client包的语法错误 解决 在vue config js中配置transpileDependencies属性 module exports 打包时将转化成低语法 tran
  • 【Django基础学习】python manage.py inspectdb相关的一些问题和解决方案

    Django 之 inspectdb inspectdb简单介绍 具体用法 在Django项目下 python manage py inspecdb gt your app name models py 作用 众所周知 Django较为适合
  • windows系统中通过 运行(win+R) 的方式打开电脑程序或文件夹

    或许你有这样的经历 在电脑 win系统 中 按下win徽标加R键会弹出 运行对话框 里面输入 notepad mspaint snippingtool等后会出来系统的程序 那么我们如何也这样打开自己的程序 或文件夹呢 从而让桌面变得较为干净
  • AR-虚实融合文献阅读整理(一)

    一 增强现实中虚实融合中光照一致性的研究 http xueshu baidu com s wd paperuri 3A 28e867ba9c51b7d137175268533e46399d 29 filter sc long sign tn
  • PCIe专题学习——2.5(Flow control缓存架构及信用积分)

    之前我们讲了对PCIe的一些基础概念作了一个宏观的介绍 了解了PCIe是一种封装分层协议 packet based layered protocol 主要包括事务层 Transaction layer 数据链路层 Data link lay
  • Ubuntu的gcc卸载与安装

    1 卸载旧版本gcc sudo apt get remove gcc 2 添加软件源 sudo add apt repository ppa ubuntu toolchain r test 3 更新 sudo apt get update
  • FIR与IIR滤波器

    H z 有分母的就是IIR 因为有自环 递归型 一个冲激响应会一直在系统里循环 H z 没有分母的就是FIR 因为没有自环 非递归型 一个冲激响应不会在系统里一直循环 通过H Z 也可简单判断 一般FIR滤波器的系统函数无分母 IIR滤波器
  • 网络爬虫-----初识爬虫

    目录 1 什么是爬虫 1 1 初识网络爬虫 1 1 1 百度新闻案例说明 1 1 2 网站排名 访问权重pv 2 爬虫的领域 为什么学习爬虫 2 1 数据的来源 2 2 爬虫等于黑客吗 2 3 大数据和爬虫又有啥关系 2 4 爬虫的领域 前
  • 用户态和内核态的区别

    一 操作系统需要两种CPU状态 内核态 Kernel Mode 运行操作系统程序 操作硬件 用户态 User Mode 运行用户程序 二 指令划分 特权指令 只能由操作系统使用 用户程序不能使用的指令 举例 启动I O 内存清零 修改程序状
  • 数字电路的时序分析

    记录一下时序分析的学习 首先搞清楚以下时序分析时用到的名词 记录一下时序分析的学习 1 什么是setup time和hold time 为什么需要setup time和hold time 2 时序路径是什么 有哪些时序路径 3 如何分析时序
  • node编写C++扩展文件并引入到项目方案总结

    插入模块 通过Addon实现可扩展接口 可以将C 插入到前端项目中 编写扩展文件 编译扩展 调用扩展 参考 在项目中引入本地模块 1 使用 npm install module1 2 直接修改 package json 字段 增加依赖 3
  • layUI基本使用——布局

    layui特点 1 layui属于轻量级框架 简单美化 是用于开发后端模式 它在服务端页面上有非常好的效果 2 layui是提供给后端开发人员的ui框架 基于DOM驱动 layui的使用 引入layui的核心css文件 引入layui的he
  • 深度学习学习笔记(一):深度学习在图像和视频的应用

    深度学习在图像和视频的应用 文章目录 深度学习在图像和视频的应用 一 图像 视频处理 1 超分辨率问题 2 图像去噪 3 图像增强 3 1 弱光照增强 3 2 动态范围增强 4 质量评价 二 图像 视频压缩 2 1 传统压缩编码框架 2 2
  • java的BigDecimal也会存在丢失精度的问题

    先说结论 务必使用BigDecimal valueOf 1 01 或者使用 new BigDecimal 1 01 而不要使用new BigDecimal 1 01 查看源码可以知道 BigDecimal valueOf double va