浮点数原理与`BigDecimal`实践应用

浮点数原理与BigDecimal实践应用

问题引入:

image-20240625092730585

浮点数

浮点数如何表示数字?

浮点数采用科学计数法表示一个数字,具体格式为:
V = ( − 1 ) S ∗ M ∗ R E V = (-1)^S * M * R^E V=(1)SMRE

  • S:符号位,取值 0 或 1,决定一个数字的符号,0 表示正,1 表示负
  • M:尾数,用小数表示
  • R:基数,表示二进制数 R 就是 2
  • E:指数,用整数表示

如何使用以上方法表示:25.125(D)

  1. 转化为十进制
    • 整数部分: 25(D) = 11001(B)
    • 小数部分: 0.125(D) = 0.001(B)
  2. 用二进制科学计数法表示
    • 25.125(D) = 11001.001(B) = 1.1001001 * 2 ^4(B)

假如填充到32bit中,且假定符号位S占1bit、指数E占10bit、尾数M占21bit,那么就是这样的:

0 0000000100 100100100000000000000

指数和尾数分配的位数不同,会产生以下情况:

  1. 指数位越多,尾数位则越少,其表示的范围越大,但精度就会变差,反之,指数位越少,尾数位则越多,表示的范围越小,但精度就会变好
  2. 一个数字的浮点数格式,会因为定义的规则不同,得到的结果也不同,表示的范围和精度也有差异

早期人们提出浮点数定义时,就是这样的情况,当时有很多计算机厂商,例如IBM、微软等,每个计算机厂商会定义自己的浮点数规则,不同厂商对同一个数表示出的浮点数是不一样的。

浮点数标准与标准浮点数的表示

1985年,IEEE 组织推出了浮点数标准,这个标准统一了浮点数的表示形式,并提供了 2 种浮点格式:

  • 单精度浮点数 float:32 位,符号位 S 占 1 bit,指数 E 占 8 bit,尾数 M 占 23 bit
  • 双精度浮点数 float:64 位,符号位 S 占 1 bit,指数 E 占 11 bit,尾数 M 占 52 bit

为了使其表示的数字范围、精度最大化,浮点数标准还对指数和尾数进行了规定:

  1. 尾数 M 的第一位总是 1(因为 1 <= M < 2),因此这个 1 可以省略不写,它是个隐藏位,这样单精度 23 位尾数可以表示了 24 位有效数字,双精度 52 位尾数可以表示 53 位有效数字
  2. 指数 E 是个无符号整数,表示 float 时,一共占 8 bit,所以它的取值范围为 0 ~ 255。但因为指数可以是负的,所以规定在存入 E 时在它原本的值加上一个中间数 127,这样 E 的取值范围为 -127 ~ 128(实际为-126 ~ 127)。表示 double 时,一共占 11 bit,存入 E 时加上中间数 1023,这样取值范围为 -1023 ~ 1024。

除了规定尾数和指数位,还做了以下规定:

  • 指数 E 非全 0 且非全 1:规格化数字,按上面的规则正常计算
  • 指数 E 全 0,尾数非 0:非规格化数,尾数隐藏位不再是 1,而是 0(M = 0.xxxxx),这样可以表示 0 和很小的数
  • 指数 E 全 1,尾数全 0:正无穷大/负无穷大(正负取决于 S 符号位)
  • 指数 E 全 1,尾数非 0:NaN(Not a Number)

img

上述案例25.125转换为标准的float浮点数:

  1. 转化为十进制
    • 整数部分: 25(D) = 11001(B)
    • 小数部分: 0.125(D) = 0.001(B)
  2. 用二进制科学计数法表示
    • 25.125(D) = 11001.001(B) = 1.1001001 * 2 ^4(B)
  3. 标准转换
    • 尾数 M = 1.001001,去掉1后为 001001
    • 指数 E = 4 + 127(中间数) = 131(D) = 10000011

0 10000011 00100100000000000000

浮点数为什么有精度丢失?

如果我们现在想用浮点数表示 0.2,它的结果会是多少呢?

0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。

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.2(D) = 0.00110…(B)

因为十进制的 0.2 无法精确转换成二进制小数,而计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。

浮点数的范围和精度有多大?

范围

float能表示的最大二进制为: + 1.111111...1 * 2^127

1.111111...1 ≈ 2

因此float能表示的最大数为2^128 = 3.4 * 10^38,即 float 的表示范围为:-3.4 * 10^38 ~ 3.4 * 10 ^38

同样的方式算出来double的范围为: -1.79 * 10^308 ~ +1.79 * 10^308

精度

float 能表示的最小二进制数为 0.0000….1(小数点后22个0,1个1),用十进制数表示就是 1/2^23 ≈ 1.19 * 10^-7

double 的最小精度为:0.0000…1(51个0,1个1),用十进制表示就是 1/2^52 ≈ 2.22 * 10^-16

BigDecimal

BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。

通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。

BigDecimal原理简介

BigDecimal 类通过两个主要字段来表示一个数值:

  • int scale:表示小数位的数量。
  • BigInteger intVal:一个 BigInteger 对象,表示数值的非标度部分,即实际的数值(十进制)。

例如,数值 123.45 可以表示为:

  • intVal = 12345
  • scale = 2

使用BigDecimal的常见的坑

坑1:创建BigDecimal对象

常见的构造函数:

// 创建一个具有参数所指定整数值的对象
public BigDecimal(int) // 创建一个具有参数所指定双精度值的对象
public BigDecimal(double) // 创建一个具有参数所指定长整数值的对象
public BigDecimal(long) // 创建一个具有参数所指定以字符串表示的数值的对象
public BigDecimal(String) 

使用实例:

BigDecimal bd1 = new BigDecimal(0.01);
BigDecimal bd2 = BigDecimal.valueOf(0.01);
BigDecimal bd3 = new BigDecimal("0.01");
BigDecimal bd4 = new BigDecimal(Double.toString(0.01));
System.out.println("bd1 = " + bd1);
System.out.println("bd2 = " + bd2);
System.out.println("bd3 = " + bd3);
System.out.println("bd4 = " + bd4);

输出结果:

bd1 = 0.01000000000000000020816681711721685132943093776702880859375
bd2 = 0.01
bd3 = 0.01
bd4 = 0.01

阿里巴巴java开发手册:

在这里插入图片描述

坑2:使用divide方法方法结果为无限循环小数

使用实例:

// 含税金额
BigDecimal inclusiveTaxAmount = new BigDecimal("1000");
// 税率
BigDecimal taxRate = new BigDecimal("0.13");
// 不含税金额 = 含税金额 / (1+税率)
BigDecimal exclusiveTaxAmount = inclusiveTaxAmount.divide(BigDecimal.ONE.add(taxRate));
System.out.println(exclusiveTaxAmount);

输出结果:

Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

官网文档:

If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations.大意是,如果除法的商的结果是一个无限小数但是我们期望返回精确的结果,那程序就会抛出异常。

解决方案:

// 不含税金额 = 含税金额 / (1+税率)
BigDecimal exclusiveTaxAmount = inclusiveTaxAmount.divide(BigDecimal.ONE.add(taxRate), 2, RoundingMode.HALF_UP);
坑3:保留小数位数

BigDecimal保留小数位数,主要用setScale方法:

public BigDecimal setScale(int newScale)public BigDecimal setScale(int newScale, RoundingMode roundingMode)

RoundingMode 参数说明:

ROUND_CEILING      //向正无穷方向舍入
ROUND_DOWN         //向零方向舍入
ROUND_FLOOR        //向负无穷方向舍入
ROUND_HALF_DOWN    //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向下舍入,例如1.55 保留一位小数结果为1.5
ROUND_HALF_EVEN    //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,如果保留位数是奇数,使用ROUND_HALF_UP,如果是偶数,使用ROUND_HALF_DOWN
ROUND_HALF_UP      //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向上舍入, 1.55保留一位小数结果为1.6
ROUND_UNNECESSARY  //计算结果是精确的,不需要舍入模式
ROUND_UP           //向远离0的方向舍入

使用示例:

BigDecimal b = new BigDecimal("1.6666");
System.out.println("result b:" + b.setScale(2, BigDecimal.ROUND_HALF_UP));
System.out.println("result b:" + b.setScale(2)); 

输出结果:

result b:1.67
Exception in thread "main" java.lang.ArithmeticException: Rounding necessary
坑4:BigDecimal等值比较

BigDecimal提供了equalscompareTo两个方法可以进行比较;

使用示例:

BigDecimal bd1 = new BigDecimal("2.0");
BigDecimal bd2 = new BigDecimal("2.00");System.out.println(bd1.equals(bd2));
System.out.println(bd1.compareTo(bd2));

输出结果:

false
true

分析原因:

BigDecimal中equals方法的实现会比较两个数字的精度,而compareTo方法则只会比较数值的大小。

阿里巴巴Java开发手册:

在这里插入图片描述

坑5:使用BigDecimal进行除法计算时被除数不能为0

使用示例:

BigDecimal number1 = new BigDecimal("88.66");
BigDecimal number2 = BigDecimal.ZERO;BigDecimal number3 = number1.divide(number2);
System.out.println("number1 divide number2 = " + number3);

输出结果:

Exception in thread "main" java.lang.ArithmeticException: Division by zero
坑6: BigDecimal 不可变

BigDecimalString 一样具有对象不可变行,一旦赋值就不会再变,即便做加减乘除运算

BigDecimal count = new BigDecimal("3.1415");
count.add(new BigDecimal("0.1645"));System.out.println("count:" + count); 
System.out.println("result:" + count.add(new BigDecimal("0.1645"))); 

输出结果:

count:3.1415
result:3.3060
坑7: 字符串输出
BigDecimal d = BigDecimal.valueOf(12334535345456700.12345634534534578901);
String out = d.toString();
System.out.println(out); 

输出结果:

1.23345353454567E+16

BigDecimal为转化为String提供了三个方法:

// 有必要时使用科学计数法
String toString();   
// 不使用科学计数法
String toPlainString(); 
// 工程计算中经常使用的记录数字的方法,与科学计数法类似,但要求10的幂必须是3的倍数
String toEngineeringString();  

补充

小数转化为二进制的方法

将小数部分转化为二进制的方法是通过乘以2并记录每次乘法的整数部分,直到小数部分变为0或达到所需的精度。以下是具体步骤:

转换步骤
  1. 乘以2:将小数部分乘以2。
  2. 取整:记录乘以2后的整数部分(0或1),这就是二进制表示的下一位。
  3. 取小数:将乘以2后的结果减去整数部分,得到新的小数部分。
  4. 重复步骤:重复以上步骤,直到小数部分变为0或者达到所需的精度。
示例:将十进制小数0.625转换为二进制
  1. 初始值0.625
  2. 第一步
    • 0.625 × 2 = 1.25
    • 整数部分:1
    • 小数部分:0.25
  3. 第二步
    • 0.25 × 2 = 0.5
    • 整数部分:0
    • 小数部分:0.5
  4. 第三步
    • 0.5 × 2 = 1.0
    • 整数部分:1
    • 小数部分:0.0(结束)

将记录的整数部分按顺序排列得到二进制表示:

  1. 625(D)= 0.101(B)
示例:将十进制小数0.1转换为二进制
  1. 初始值0.1
  2. 第一步
    • 0.1 × 2 = 0.2
    • 整数部分:0
    • 小数部分:0.2
  3. 第二步
    • 0.2 × 2 = 0.4
    • 整数部分:0
    • 小数部分:0.4
  4. 第三步
    • 0.4 × 2 = 0.8
    • 整数部分:0
    • 小数部分:0.8
  5. 第四步
    • 0.8 × 2 = 1.6
    • 整数部分:1
    • 小数部分:0.6
  6. 第五步
    • 0.6 × 2 = 1.2
    • 整数部分:1
    • 小数部分:0.2
  7. 第六步
    • 0.2 × 2 = 0.4
    • 整数部分:0
    • 小数部分:0.4
  8. (重复上述步骤)

小数0.1的二进制表示是一个循环小数:
0.1(D)= 0.0001100110011...(B)

数学原理这里不再说明,若有疑问,请自行百度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/35281.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3+crypto-js插件实现对密码加密后传给后端

最近在做项目的过程中又遇到了一个新的问题&#xff0c;在实现后端管理系统的个人信息页面中&#xff0c;涉及到修改密码的功能&#xff0c;刚开始我直接通过传参的方式将修改的密码传入给后端&#xff0c;可是后端说需要将原密码、新密码以及确认密码都进行加密处理&#xff0…

大模型技术的应用场景

大模型技术&#xff08;Large Language Model&#xff0c;LLM&#xff09;是指具有大量参数和训练数据的神经网络模型&#xff0c;它能够学习语言的统计规律&#xff0c;并生成与人类书写的文本相似的文本。大模型技术在近年来取得了重大进展&#xff0c;并开始在各种领域得到应…

OpenAI 推迟了 ChatGPT 的新语音模式

今年 5 月&#xff0c;OpenAI 首次为其人工智能聊天机器人平台ChatGPT演示了一种非常逼真、近乎实时的"高级语音模式"。几个月后&#xff0c;OpenAI 表示需要更多时间。 OpenAI 在其官方 Discord 服务器上发布了一篇文章&#xff0c;称其原计划于 6 月底开始向一小部…

04 Shell编程之正则表达式与文本处理器

1、正则表达式 1.1 正则表达式的定义 正则表达式又称为正规表达式、常规表达式。 正则表达式是使用单个字符来描述、匹配一系列符合某个句法规则的字符串&#xff0c; 简单来说&#xff0c;正则表达式就是一种匹配字符串的方法&#xff08;通过一些特殊符号&#xff0c;实现…

搜狗微信文章数据爬取可视化

搜狗微信文章数据爬取可视化 一、爬取流程1.1 寻找数据接口1.2 发送请求获取数据1.3 xpath表达式解析数据1.4 保存数据二、数据可视化三、完整代码一、爬取流程 搜狗微信的主页:https://weixin.sogou.com/,主页截图如下,在搜索框中输入要查询的内容,以“百合花”为例: 观…

学习记录698@基带传输和频带传输基础

还是在学习计算机网络物理层时遇到这些知识点&#xff0c;这里简单的记录一下&#xff0c;主要都是通信专业的知识 基带传输 信源发出的原始信号叫做基带信号&#xff0c;基带信号分为模拟基带信号与数字基带信号。基带信号一般是低频成分&#xff0c;适合在具有低通特性的有…

203.回溯算法:N皇后(力扣)

class Solution { public:vector<vector<string>> result; // 用于存储所有合法的 N 皇后放置方案// 判断当前位置 (row, col) 是否可以放置皇后bool isValid(int row, int col, vector<string>& chess, int n) {// 检查当前列是否有皇后for (int i 0;…

学好 prompt 让大模型变身撩富婆专家,带你走上人生巅峰

前文 使用大模型的最重要的一步就是编写好的提示词 prompt &#xff0c;但是 prompt 既容易被低估也容易被高估。被低估是因为设计良好的提示词可以显著提升效果。被高估是因为即使是基于提示的应用也需要大量的工程工作才能使其发挥作用。下面我会介绍在编写 prompt 的时候&a…

【面试干货】Java中new与clone操作对象的比较

【面试干货】Java中new与clone操作对象的比较 1、new操作符创建对象的过程2、clone方法创建对象的过程3、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、new操作符创建对象的过程 new操作符在Java中用于创建对象&#xff0c;并执行…

一年Java|16K|同程艺龙面经

面经哥只做互联网社招面试经历分享&#xff0c;关注我&#xff0c;每日推送精选面经&#xff0c;面试前&#xff0c;先找面经哥 背景 公司&#xff1a;同程艺龙成都BU,现场部门老大面 之前的同程艺龙电话一面过了&#xff0c;然后通知到同程艺龙成都办公地点现场进行部门老大…

C语言实战 | “贪吃蛇”游戏重构

程序设计的过程中,面对复杂项目,利用模块化思维分解任务,是关键的一步。读者一定要掌握模块化思维设计思维,为将来团队合作、协同完成大型应用软件做好准备。 01、“贪吃蛇”游戏 有了游戏框架之后,按照游戏框架完成“贪吃蛇”游戏。 “贪吃蛇”游戏角色有两个:“贪吃蛇…

设计总监独家揭秘:后台管理系统设计全攻略!

后台管理是反映用户行为、提高产品保留率的有力依据。设计师将后台管理的各种数据创建成清晰、合乎逻辑、可操作的后台管理仪表板。当您设计一个美观实用的UI后台管理时&#xff0c;它会给用户带来更直观的信息内容&#xff0c;从而提高用户体验。因此&#xff0c;后台管理设计…

git 查看本地和远程分支

要查看 Git 仓库中的所有分支&#xff0c;可以使用以下命令&#xff1a; git branch执行该命令后&#xff0c;Git 会列出当前仓库中的所有分支&#xff0c;并在当前所在的分支前加上一个 * 标记。 如果你想查看远程仓库的分支&#xff0c;可以添加 -r 或 --remotes 选项&…

详解LLM大模型是如何理解并使用 tools ?

前文 大家肯定对使用大模型的函数回调或者说 Tools 已经耳熟能详了&#xff0c;那么他们具体内部是如何运作的呢&#xff0c;本文就此事会详细给大家介绍具体的细节。 tools 首先是大家最熟悉的环节&#xff0c;定义两个 tool 的具体实现&#xff0c;其实就是两个函数&#…

C语言:sprintf与snprintf

C语言提供了强大的格式化输出的接口&#xff0c;可以输出到不同的文件或者字符串等&#xff0c;以sprintf和snprintf为例介绍一下 sprintf 格式化输出到字符串 函数签名 int sprintf(char *str, const char *format, ...);与printf相比就是多了前面的char*参数&#xff0c;…

PointNet数据预处理+网络训练

PointNet数据预处理网络训练 数据预处理分类网络的训练分割网络训练分类和分割的结果 数据预处理 数据预处理&#xff0c;这里仅介绍一个shapenetdataset&#xff1b; class ShapeNetDataset(data.Dataset):def __init__(self,root,npoints2500,classificationFalse,class_ch…

前端应熟知的各种宽度高度

目录 一、window对象- 浏览器对象模型 二、Document对象-文档对象模型 前端做项目时经常需要使用到各种宽度高度&#xff0c;可以从两个地方获得这些数据。 一、window对象- 浏览器对象模型 浏览器对象模型 (BOM) 使 JavaScript 有能力与浏览器"对话"。 所有浏览…

动态图形设计:创造视觉运动的艺术

什么是动态设计&#xff1f;动态设计是一个设计领域&#xff0c;指在用户界面中使用动态效果的设计。简单地说是为了移动用户界面上的元素而设计的。良好的动态设计可以吸引用户的注意&#xff0c;提高用户体验和满意度。动态设计也是界面设计与动态设计的结合&#xff0c;将设…

无人机螺旋桨理论教学培训课程

本文档为一份详细的关于TYTO机器人公司提供的电机和螺旋桨理论及其实验操作的指南。指南首先概述了材料、实验目标以及实验的介绍部分&#xff0c;随后详细阐述了理论问题、实验步骤和附录内容。实验目的在于通过实际测试来测量和理解不同螺旋桨参数对无人机性能的影响&#xf…

上海亚商投顾:沪指5连阴 工业母机概念逆势走强

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数今日继续调整&#xff0c;沪指午后一度跌近1%&#xff0c;随后探底回升跌幅收窄&#xff0c;创业板指…