Java Double转Bigdecimal丢失精度原因学习

Java Double转Bigdecimal丢失精度原因学习记录学习Double转Bigdecimal丢失精度的原因注意事项:不能直接使用Bigdecimal的构造函数传double进行转换,部分数值会丢失精度,因为计算机是二进制的Double无法精确的储存一些小数位,0.1的double数据存储的值实际上并不真的等于0.1如该方式将0.1转换为Bigdecimal得到的结果是0.1000000000000000055511151231257827021181583404541015625这是为什么呢,以往只是知道结论知道不能这么用,也大概知道是因为do

大家好,又见面了,我是你们的朋友全栈君。

记录学习Double转Bigdecimal丢失精度的原因

注意事项:
不能直接使用Bigdecimal的构造函数传double进行转换,部分数值会丢失精度,因为计算机是二进制的Double无法精确的储存一些小数位,0.1的double数据存储的值实际上并不真的等于0.1
如该方式将0.1转换为Bigdecimal得到的结果是
0.1000000000000000055511151231257827021181583404541015625
使用Bigdecimal构造函数
这是为什么呢,以往只是知道结论知道不能这么用,也大概知道是因为double是双精度导致的,但是没有太关注原因。这次就来进一步学习一下

首先给出Double转BIgdecimal的常用方式
1、可以手动先将Double转换为String再转换为Bigdecimal 则不会发生精度丢失问题

BigDecimal bigDecimal = new BigDecimal(String.valueOf(0.1));

2、可以直接调用Bigdecimal的函数

BigDecimal bigDecimal = BigDecimal.valueOf(0.1); 

这个函数跟一下源码内部其实也是先将Double转为String

public static BigDecimal valueOf(double val) {
        // Reminder: a zero double returns '0.0', so we cannot fastpath
        // to use the constant ZERO.  This might be important enough to
        // justify a factory approach, a cache, or a few private
        // constants, later.
        return new BigDecimal(Double.toString(val));
}

在这里插入图片描述
接下来我们找一下会出现丢失精度的原因
首先要知道计算机语言是二进制语言,而0.1是我们日常十进制的言语。
二进制与十进制的转换比较简单。大多数时候不需要我们手动去计算,但还是可以学习一下。网上的在线转换工具也很多,这里不详细介绍了

第二个要知道Double的数据格式,Double是双精度,Float是单精度。
Double与Float的数据格式是一致的,但是长度不同。数据分为三段

Double | ----符号位长度1-- | ------指数位长度8--------| --------尾数长度23-----------
Float  | ----符号位长度1-- | ------指数位长度11-------| --------尾数长度52-----------

第一段也就是第一位是符号位,然后第二段是指数位,第三段是尾数

符号位

符号位好理解 十进制为正数则 = 0 负数 = 1
比如0.1与-0.1的Float内存数据:
	 0.1  =  	00111101110011001100110011001101
	-0.1  = 	10111101110011001100110011001101

指数位

指数位存储的是转换为二进制数值后类似再转换为科学计数法的指数数值
指数位长度是8。8位二进制正常的范围值为0~255。但是十进制的小数的对应的指数位可能为负数,为了方便记录所以规定指数位的指数偏移 Float+127,Double+1023 后再转换为二进制。
注意这里指数位存储的不是十进制科学计数法的指数,而是二进制的指数值。我们以0.1为例 
  • 错误的示例 0.1(10) = 1 * 10-1 十进制科学计数法指数位 -1 + 127 = 126(10)= 01111110(2)然而指数位不是存储这个数值01111110 。0.1对应正确的指数位是应该是 01111011(2)= 123(10)
    为什么呢?我们和尾数一起学习一下

尾数位

尾数位存储的是数值转换为二进制后的类似科学计数法的二进制数的基数。我们还是以0.1为例
先将0.1转换为二进制,方法我们不详细介绍,0.1的计算大致可以乘以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.4 * 2 = 0.8 小数位继续计算 二进制取整数位: 0 重复

一直算下去得到就是一个无限循环数

  • 0.1(10)= (2) 0 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011…

这就是0.1对应的二进制,类似于十进制中的 1/3 = 0.33333…是个无限数而Float(32)与Double(64)长度是有限的是无法精确表示出这个数值的,只能是无限接近0.1。有兴趣可以手动计算它等于0+0+0+0.0625+0.0315+0.015625…> 0.1 而一直加下去但是永远不可能等于0.1

再转换为科学计数法移动三位小数点 :

  • 0.1(10)= (2) 0 0011 0011 0011 0011 0011 0011 0011 0011… = (2^)1.1001100110011…* 2-3

所以0.1Float指数位值是

  • -3+127(偏移值 )= 123(10) = 1111011(2) 补齐8位存入指数位就是 01111011

那么尾数呢,尾数位本应该是110011001100110011…
但是因为科学计数法正数首位一定为1,所以二进制存储时不存储这个数值,只是在计算过程里加上即可。所以去除首位的1 最终尾数位存储的就是

  • Float: 10011001100110011001101
    Double: 1001100110011001100110011001100110011001100110011010

合在一起最终0.1的储存数据为

  • 0.1 -> Float :0 01111011 10011001100110011001101
    0.1 -> Double:0 01111111011 1001100110011001100110011001100110011001100110011010

对应-0.1则只需要把首位的符号位改为1

  • -0.1 -> Float :1 01111011 10011001100110011001101

计算逻辑

我们再整理一下数据的存储逻辑

  1. 符号位判断十进制数正负 赋值 (正数:0、负数:1) 存入符号位
  2. 将十进制转换为二进制数
    例:2.2(10) = 100011001100110011001101…
  3. 将二进制数转换为二进制的科学计数法表达
    例 : 2.2(10) = (2) 1.00011001100110011001101… = (2)1.00011001100110011001101… * 21
  4. 指数值偏移(Float+127、Double+1023)得出十进制的指数 再转换为二进制数 存入指数位
    例:(2)1.00011001100110011001101… * 21 —— 偏移——-> 1+127或1023 = 128或1024 ———转为二进制——->10000000或10000000000
  5. 将得出的二进制科学计数法基数去除首位的1(因为这个值是固定为1的)存入尾数位
    例:1.00011001100110011001101 —-去除首位的1—–> 00011001100110011001101
    最终结果
    2.2 —Float—> 01000000000011001100110011001101
    2.2 —-Double —->0100000000000001100110011001100110011001100110011001100110011010

搞明白精确丢失的原因,就是因为有限的二进制无法准确的存储一些数值如:0.1/0.2。那么自然有数值也是能精确存储的。可以直接使用new Bigdecimal(Double d)并且不会丢失精度,那么什么样的数值使用构造方式不会丢失精度呢?

可以找几个数值动手算一下加深理解

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/136178.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 2000数据库置疑怎么处理_msdb数据库置疑的解决方法

    2000数据库置疑怎么处理_msdb数据库置疑的解决方法由于服务器意外的断电,导致SQLSERVER服务器上数据库出现“置疑”而无法使用,通过网上搜索,找到以下方法解决问题,这里记录一下:产生数据库置疑的时侯,数据库文件和日志文件都是存在的,如果数据库文件都不存在了,则要另当处理。1、停止数据库服务器,将数据库MDF文件和LDF文件复制备份一份2、启动数据库服务器,删除置疑的数据库3、仅用备份的数据库MDF文件附加数据库,sp_attach_…

    2022年8月20日
    6
  • 通达信5分钟.lc5和.lc1文件格式

    通达信5分钟.lc5和.lc1文件格式一、通达信日线*.day文件文件名即股票代码每32个字节为一天数据每4个字节为一个字段,每个字段内低字节在前00~03字节:年月日,整型04~07字节:开盘价*100,整型08~11字节:最高价*100,整型12~15字节:最低价*100,整型16~19字节:收盘价*100,整型2…

    2022年7月24日
    86
  • 基于Linux平台下分子建模软件的安装

    基于Linux平台下分子建模软件的安装文档及视频资料下载地址:http://down.51cto.com/data/1149515附:Linux图形工作站下应用演示。

    2022年5月18日
    19
  • datetime.date()_datenum函数使用

    datetime.date()_datenum函数使用比如在windowscmd命令行窗口执行date命令后这个环境变量的值为当前日期:2014-03-01 星期六那么如下的各个操作的意义如下:%date:~0,4% 表示从左向右指针向右偏0位,然后从指针偏移到的位置开始提取4位字符,结果是2014(年的值)%date:~5,2% 表示指针从左向右偏移5位,然后从偏移处开始提取2位字符,结果是03(月的值)%date:~8,

    2022年9月20日
    0
  • 负采样方式

    负采样方式一、随机负采样二、曝光未点击三、混合负采样四、重要性采样五、有偏采样六、NCE采样参考:[mixednegativesampling]MixedNegativeSamplingforLearningTwo-towerNeuralNetworksinRecommendations(2020) [Youtube]Sampling-Bias-CorrectedNeuralModelingforLargeCorpusItemRecomme

    2022年6月29日
    23
  • CSS3 opacity属性

    CSS3 opacity属性CSS3opacity属性记录设置一个div元素的透明度级别实现原理:opacity属性在实现的原理上极度类似于PS中的蒙版概念样式:div{opacity:0.5;}取值范围:0~1注意:IE8和早期版本支持另一种过滤器属性。像:filter:Alpha(opacity=50)属性说明默认值:1继承性:no(不继承)版本:CSS3属性:object.style.opacity问题1.如果父元素设置opacity属性,那么这个的所有子元素都

    2022年5月26日
    35

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号