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)
上一篇 2022年6月4日 上午6:00
下一篇 2022年6月4日 上午6:00


相关推荐

  • OpenAI Sora 怎么用:最新详细教程-新手小白必看

    OpenAI Sora 怎么用:最新详细教程-新手小白必看

    2026年3月15日
    1
  • 树莓派4B +远程SSH+远程桌面[通俗易懂]

    树莓派4B +远程SSH+远程桌面[通俗易懂]一、有线SSH连接树莓派我的实验环境是笔记本电脑+树莓派4B具体步骤为:1、电脑连接上无线网络,将电脑网线连接树莓派2、打开如下界面3、双击WLAN——>>点击属性——>>再点击共享选择以太网4、双击以太网——>>点击属性——>>IPV4——>>在选择下面的…

    2022年5月15日
    108
  • Java正则表达式的语法与示例

    Java正则表达式的语法与示例Java正则表达式的语法与示例正则表达式是什么?用我的理解就是一个表达式。用来匹配,替换,判断字符串,之前业务就出现过判断返回值是否为邮箱。以下内容来自于http://baike.xsoftlab.net/view/207.html#3java正则表达式正则表达式语法java正则表达式语法java正则表达式概要:Java正则表达式的语法与示例

    2022年7月19日
    19
  • 四、Pycharm及Jupyter使用及对比

    四、Pycharm及Jupyter使用及对比目录一 pycharm 项目新建及使用二 Jupyter 项目新建及使用三 三种代码编辑方式对比一 pycharm 项目新建及使用 1 新建 pycharmproje 第二节中有讲 2 新建 python 文件 右键点击新创建的 pycharmproje gt new gt pythonfile3 写代码 右键点击 run 文件名称 即可 4 还有一种写程序的方式是在 pythonconsol 这里的代码是一行一行运行的 在本行写一个 点击 enter 就会运行二

    2026年3月27日
    3
  • 免备案空间推荐(超低价免备案空间)

    “空间”对于搭建网站来说是比较重要的,然而国内空间访问有备案这个限制。最近试用一些不错的免费免备案空间,今天整理分享出来

    2022年4月10日
    51
  • JavaScript如何判断是否为数字?

    JavaScript如何判断是否为数字?JavaScript如何判断是否为数字?方法1:使用isNaN()函数isNaN()函数是js自带的全局函数,isNaN()函数用于检查其参数是否是非数字值。如果值x是特殊的非数字值NaN(或者能被转换为这样的值),返回的值就是true;如果值x是其他值,则返回false。isNaN()的缺点就在于null、空格以及空串会被按照0来处理NaN:NotaNumber<script>document.write(isNaN(123));//数字-

    2022年6月29日
    30

发表回复

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

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