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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Response.ContentType详细说明

    Response.ContentType详细说明不同的ContentType会影响客户端所看到的效果.默认的ContentType为text/html也就是网页格式.代码如:显示的为网页,而则会显示html原代码.以下为一些常用的ContentTypeGIFimagesJPEGimagesTIFFimagesMICR

    2022年7月19日
    45
  • 学习JAVA要安装什么软件?[通俗易懂]

    学习JAVA要安装什么软件?[通俗易懂]我写了一夜的代码,刚才上网查资料看到你问题,听一听我的建议,希望对你有所帮助,我们都是走在路上的人MyEclipse功能很强大,我的建议是先不要使用,开发工具从记事本–UltraEdit-32–JBuilder–MyEclipse一点点过渡初学java,一般都是从控制台应用程序开发开始的(我刚开始喜欢在记事本中写代码),在cmd下调试,首先你要为你的电脑搭建好开发环境

    2022年7月8日
    27
  • 最近在学习mars老师的视频。按计划的。学习安卓

    转眼之间暑假就过去一大半了。还有14天就开学了。估计再过个一周,同学们就陆续回校了。这个假期借了很多书,虽然没有想象中那么高的效率,学习太多东西,不过还是学了点东西,起码自我感觉还算可以。我是先看了看java的基础知识,一本国外的java面向对象程序设计,说实话书挺好,不过对我不合适,学过c++之后,很多里面讲的思想差不多懂了,然后例子很多,看得有点头疼,应该找一本专门讲java语法的书的。那

    2022年3月8日
    70
  • 优化SqlServer–数据压缩

    优化SqlServer–数据压缩

    2021年11月25日
    40
  • 文件上传文件的权限–lnmp 环境配置,尤其整个项目复制过来

    文件上传文件的权限–lnmp 环境配置,尤其整个项目复制过来

    2021年10月27日
    40
  • 用Python发免费短信的正确姿势

    用Python发免费短信的正确姿势前言今天带大家来玩一玩,如何用Python来实现免费短信。关于发短信,其实适用场景还挺多的。只有你想不到的场景,没有玩不转的场景!比如作为一个IT人员,经常会有监控服务器之说,那么自动告警的短信或者来电就显得非常重要了。再比如,你可以用发短信的功能,自己来实现一个温馨天气预报提示给你的爸爸妈妈。。。等等下面进入今天的主题-twilio网站。https://www.twilio.com/console准备工作先来介绍下这个网站主要用于发短信和来电,提供了完备的相关api。而

    2022年5月1日
    54

发表回复

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

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