java浮点型精度丢失浅析

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

测试用例

代码如下

1@Test 2 public void fd() { 3 double abc = 0.56D; 4 System.out.println("abc: " + abc); 5 System.out.println("new BigDecimal(abc): " + new BigDecimal(abc)); 6 System.out.println("BigDecimal.valueOf(abc): " + BigDecimal.valueOf(abc)); 7 }

输出

1abc: 0.56 2new BigDecimal(abc): 0.560000000000000053290705182007513940334320068359375 3BigDecimal.valueOf(abc): 0.56

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

深入源码

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

1public BigDecimal(double val, MathContext mc) { 2 ..... 3 long valBits = Double.doubleToLongBits(val); 4 int sign = ((valBits >> 63) == 0 ? 1 : -1); 5 int exponent = (int) ((valBits >> 52) & 0x7ffL); 6 long significand = (exponent == 0 7 ? (valBits & ((1L << 52) - 1)) << 1 8 : (valBits & ((1L << 52) - 1)) | (1L << 52)); 9 exponent -= 1075; 10 ... 11}

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

BigDecimal.valueOf核心代码

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

可以看到使用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 协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。

点赞
收藏

评论区

加载中...

相关推荐

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

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Java:利用BigDecimal类巧妙处理Double类型精度丢失

本篇要点简单描述浮点数十进制转二进制精度丢失的原因。介绍几种创建BigDecimal方式的区别。整理了高精度计算的工具类。学习了阿里巴巴Java开发手册关于BigDecimal比较相等的规定。经典问题:浮点数精度丢失精度丢失的问题是在其他计算机语言中也都会出

java浮点型精度丢失浅析 - HelloWorld