java浮点型精度丢失浅析

Wesley13
• 阅读 835

java浮点型数值在运算中会出现精度损失的情况,在业务要求比较高比如交易等场景,一般使用BigDecimal来解决精度丢失的情况。最近一个同事在使用BigDecimal时仍然出现了精度损失,简略记录一下

测试用例

代码如下

@Test
    public void fd() {
        double abc = 0.56D;
        System.out.println("abc: " + abc);
        System.out.println("new BigDecimal(abc): " + new BigDecimal(abc));
        System.out.println("BigDecimal.valueOf(abc): " + BigDecimal.valueOf(abc));
    }

输出

abc: 0.56
new BigDecimal(abc): 0.560000000000000053290705182007513940334320068359375
BigDecimal.valueOf(abc): 0.56

可以看到在使用BigDecimal构造器转化浮点型仍然会有损失,而使用valueOf方法则不会出现精度损失。

深入源码

BigDecimal构造器,核心代码(BigDecimal(double val))如下

public BigDecimal(double val, MathContext mc) {
  .....
    long valBits = Double.doubleToLongBits(val);
    int sign = ((valBits >> 63) == 0 ? 1 : -1);
    int exponent = (int) ((valBits >> 52) & 0x7ffL);
    long significand = (exponent == 0
                      ? (valBits & ((1L << 52) - 1)) << 1
                      : (valBits & ((1L << 52) - 1)) | (1L << 52));
  exponent -= 1075;
  ...
}

划重点, Double.doubleToLongBits返回根据IEEE754浮点“双精度格式”位布局,返回指定浮点值的表示

BigDecimal.valueOf核心代码

public static BigDecimal valueOf(double val) {
        return new BigDecimal(Double.toString(val));
    }
public BigDecimal(char[] in, int offset, int len, MathContext mc) {
  ....
}

可以看到使用valueOf方法实际上是把double转为String,再调用string构造器的。

那么为什么使用Double.doubleToLongBits会出现精度损失,而使用string构造器不会呢。主要原因是BigDecimal使用十进制(BigInteger)+小数点(scale)位置来表示小数,而不是直接使用二进制,如101.001 = 101001 * 0.1^3,运算时会分成两部分,BigInteger间的运算以及小数点位置的更新,这里不再展开。

原理浅析

Double.doubleToLongBits为什么会出现精度损失呢,主要原因是因为浮点型不能用精确的二进制来表述,就如十进制不能准确描述无穷小数一样。

浮点型转化为二进制的算法是乘以2直到没有了小数为止,举个栗子,0.8表示成二进制

0.8*2=1.6 取整数部分 1

0.6*2=1.2 取整数部分 1

0.2*2=0.4 取整数部分 0

0.4*2=0.8 取整数部分 0

可以看到上述的计算过程出现循环了,所以说浮点型转化为二进制有时是不可能精确的。

结论

如果想要把浮点型转化为BigDecimal,尽量选择使用valueOf方法,而不是使用构造器。

本文由 歧途老农 创作,采用 CC BY 4.0 CN 协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java:利用BigDecimal类巧妙处理Double类型精度丢失
本篇要点简单描述浮点数十进制转二进制精度丢失的原因。介绍几种创建BigDecimal方式的区别。整理了高精度计算的工具类。学习了阿里巴巴Java开发手册关于BigDecimal比较相等的规定。经典问题:浮点数精度丢失精度丢失的问题是在其他计算机语言中也都会出
Wesley13 Wesley13
3年前
Java开发笔记(三十)大小数BigDecimal
前面介绍的BigInteger只能表达任意整数,但不能表达小数,要想表达任意小数,还需专门的大小数类型BigDecimal。如果说设计BigInteger的目的是替代int和long类型,那么设计BigDecimal的目的便是替代浮点型float和双精度型double了。正如它的兄弟BigInteger一般,BigDecimal不存在什么数值范围限制,无论是
Wesley13 Wesley13
3年前
Java中的BigDecimal类和int和Integer总结
前言我们都知道浮点型变量在进行计算的时候会出现丢失精度的问题。如下一段代码:System.out.println(0.050.01);System.out.println(1.00.42);System.out.println(4.015100);System.out.println(1
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这