浮点数的近似保存与计算

news2024/11/26 21:39:48

这里写目录标题

    • 负数的补码存储
    • 十进制浮点数与二进制的转换
      • 有限循环的二进制
      • 无限循环的二进制
    • 计算机对浮点数的保存
      • 无限循环二进制数的保存
      • 浮点数的近似
    • 参考文献

负数的补码存储

首先我们回忆一下负数的补码表示。我们都知道,有符号数的负数使用补码的方式进行存储:

补码表示

之所以这样,就是 方便统一运算 ,如果负数不是使用补码的方式表示,则在做基本对加减法运算的时候, 还需要多一步操作来判断是否为负数,如果为负数,还得把加法反转成减法,或者把减法反转成加法 ,这就非常不好了,毕竟加减法运算在计算机里是很常使用的,所以为了性能考虑,应该要尽量简化这个运算过程。

image-20230713150803755

十进制浮点数与二进制的转换

有限循环的二进制

这种最简单的情况,直接遵照我们的默认转换方法:

  • 整数部分采用:除 2 取余法
  • 小数部分采用:乘 2 取整法

其计算过程如下图所示:

image-20230713203912197

反之二进制转换为十进制则较为简单,按位乘以2的对应幂次即可:

image-20230713204023335

无限循环的二进制

上面所说的是可以用有限位二进制表示的十进制数,但是还有的数字是无法用有限位二进制来表达的,它们转换的过程中变成了无限循环的二进制。

例如按照之前的算法,对于 0.1 ,转换为二进制,过程如下:

image-20230713212239487

可以发现,0.1 的二进制表示是无限循环的。

由于计算机的资源是有限的,所以是没办法用二进制精确的表示 0.1,只能用「近似值」来表示,就是在有限的精度情况下,最大化接近 0.1 的二进制数,于是就会造成精度缺失的情况 。在后面会介绍0.1这种无限循环二进制数是如何在计算机中保存的。

计算机对浮点数的保存

现在绝大多数计算机使用的浮点数,一般采用的是 IEEE 制定的国际标准,这种标准形式如下图:

image-20230713205333734

这三个重要部分的意义如下:

  • 符号位:表示数字是正数还是负数,为 0 表示正数,为 1 表示负数;
  • 指数位:指定了小数点在数据中的位置,指数可以是负数,也可以是正数,指数位的长度越长则数值的表达范围就越大
  • 尾数位:小数点右侧的数字,也就是小数部分,比如二进制 1.0011 x 2^(-2),尾数部分就是 0011,而且尾数的长度决定了这个数的精度,因此如果要表示精度更高的小数,则就要提高尾数位的长度;

32 位来表示的浮点数,则称为单精度浮点数,也就是我们编程语言中的 float 变量,而用 64 位来表示的浮点数,称为双精度浮点数,也就是 double 变量,它们的结构如下:

image-20230713205421249

2进制小数转换为2进制浮点数保存的步骤如下:

image-20230713205626351

可以注意到转换过程中:

  1. 首先要移动小数点到第一个有效数字后面,然后小数点右侧的数字就是浮点数里的尾数位存储的值。
  2. 指数位在存储时以+127的方式进行;这样就可以把指数转换成无符号数,可以表达的指数取值范围在 -126 ~ +127
  3. 移动后的小数点左侧的有效位(即 1)消失了,因为 IEEE 标准规定默认左侧最高位就是1

因此转换公式为:

image-20230713210301657

我们举个例子,例如将 -5.125 转换成float类型进行保存,则按照IEEE 754标准的规则进行以下步骤:

  1. 符号位:由于-5.125是负数,符号位为1。
  2. 绝对值的二进制表示:将5.125的绝对值转换为二进制形式,得到101.001。
  3. 规范化:将二进制表示规范化,即将小数点左移,直到只有一位非零数字位,得到1.01001。
  4. 指数位:计算小数点左移的位数,这里是2。
  5. 偏移量:对于32位浮点数,偏移量为127。将实际指数值2加上偏移量,得到指数位的值为129。
  6. 最终二进制表示:将符号位、指数位和尾数位按顺序连接起来,得到最终的32位二进制表示: 1 10000001 01001000000000000000000

我们编写代码进行测试,这里使用联合体(union)来转换浮点数和二进制数据:

#include <iostream>
#include <iomanip>

union FloatBinary {
    float f;
    unsigned int i;
};

int main() {
    float num = -5.125;
    FloatBinary fb;
    fb.f = num;

    // 将二进制表示以字符串形式打印
    std::cout << num << "\t-->\t";
    for (int i = 31; i >= 0; --i) {
        std::cout << ((fb.i >> i) & 1);
        if (i == 31 || i == 23) {
            std::cout << ' ';
        }
    }
    std::cout << std::endl;

    return 0;
}

测试结果如下:

image-20230713212929680

和我们上面按照默认步骤计算的结果是一致的。

无限循环二进制数的保存

上面提到 0.1 这种无限循环的二进制数,对于这种数字,计算机是如何保存的呢?

使用 binaryconvert 这个工具,将十进制 0.1 小数转换成 float 浮点数,观察如下:

image-20230713212505777

可以看到,8 位指数部分是 01111011,23 位的尾数部分是 10011001100110011001101,可以看到 尾数部分是 0011 是一直循环的 ,只不过尾数是有长度限制的,所以只会显示一部分,所以是一个近似值,精度十分有限。

我们用刚刚的程序在代码中观察一下,发现和显示的一致:

image-20230713212916249

再看看 0.2 的保存方式:

image-20230713212607960

可以看到,8 位指数部分是 01111100,稍微和 0.1 的指数不同,23 位的尾数部分是 10011001100110011001101 和 0.1 的尾数部分是相同的,也是一个近似值。

再用代码观察,发现也是一致的:

image-20230713213016206

浮点数的近似

我们再将两个浮点数转换回十进制:

image-20230713213133665

这两个结果相加就是 0.300000004470348358154296875

image-20230713213201605

可以得出,计算机里对这样的浮点数采取近似保存,所以其相加得出的也是一个近似数。我们用代码测试一下:

#include <iostream>
#include <iomanip>

int main() {
    double num1 = 0.1f;
    double num2 = 0.2f;
    double sum = num1 + num2;

    std::cout << std::setprecision(32);
    std::cout << "num1:\t\t" << num1 << std::endl;
    std::cout << "num1:\t\t" << num2 << std::endl;
    std::cout << "num1+num2:\t" << sum << std::endl;

    return 0;
}

image-20230713214410526

至于这里为什么将三个数据类型都设定为了double,是因为float的时候,相加的结果并不会等于我们预想的值:

image-20230713214503319

之所以这样,是因为当进行相加操作时,0.1和0.2的近似值参与计算,由于浮点数精度有限,可能 会产生进一步的近似和舍入误差 。因此,相加的结果变成了 0.300000011920928955078125 ,与我们期望的精确结果0.3有微小的差异。

参考文献

  1. 2.7 为什么 0.1 + 0.2 不等于 0.3 ? | 小林coding


部分图片来源网络,如有侵权请联系我删除。
如有疑问或错误,欢迎和我私信交流指正。
版权所有,未经授权,请勿转载!
Copyright © 2023.07 by Mr.Idleman. All rights reserved.


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

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

相关文章

WVP+ZLMediaKit实现网络摄像头接入

​ 记录下本地调试监控摄像头相关信息。 参考来源&#xff1a;部署 WVPZLMediaKit 实现大华摄像头接入_wvp zlm_鬼畜的稀饭的博客-CSDN博客 ZLMediaKit 代码地址 WVP 代码地址 ⚠️ 摄像头需要连接PoE设备来供电&#xff08;插网线就能供电&#xff09; 资源清单&#xff1a…

如何通过设备管理系统实现设备全生命周期管理

设备是生产力的核心&#xff0c;对企业的运营和效益起着至关重要的作用。然而&#xff0c;随着设备数量和复杂性的增加&#xff0c;如何有效管理设备的全生命周期成为了一个挑战。 在这个时代&#xff0c;设备管理系统成为了一种重要的工具&#xff0c;帮助企业实现设备全生命周…

LJUBOMORA - 思维+二分

分析&#xff1a; 二分最小的嫉妒值&#xff0c;每次check需要判断每一种颜色需要分给几个小朋友&#xff0c;如果可以所有都分完那么返回true。 代码&#xff1a; #include <bits/stdc.h>using namespace std;typedef long long ll; typedef pair<int,int> pii;…

[QT编程系列-7]:C++图形用户界面编程,QT框架快速入门培训 - 3- QT窗体设计 - 自定义工具栏、状态栏、快捷键、图标

目录 3. QT窗体设计 3.2 自定义工具栏 3.2.1 目标 3.2.2 实现过程 3.2 自定义状态栏 3.2.1 目标 3.2.2 过程 3.3 自定义动作快捷键 3.4 自定义图标 3. QT窗体设计 3.2 自定义工具栏 在Qt中&#xff0c;ToolBar&#xff08;工具栏&#xff09;是一种常见的GUI元素&a…

【MEX】CF1629 C

Problem - C - Codeforces 题意&#xff1a; 思路&#xff1a; 模拟样例 可以发现做法是对该数列进行分段&#xff0c;当当前mex在后缀没有出现过时&#xff0c;形成一个区间[l,r] 然后有两个问题&#xff1a; 1.mex怎么去维护 2.怎么看mex在后缀有没有出现过 对于第一个…

提高Spark性能的关键:动态资源分配

前言 Spark提供了一种机制,可以根据工作负载动态调整应用程序占用的资源。这意味着,如果不再使用资源,应用程序可能会将资源返还给集群,并在以后有需求时再次请求这些资源。如果Spark集群中有多个应用程序共享资源,则此功能特别有用。 该功能在默认情况下被禁用,并且在…

107-免杀对抗-CC++溯源ShellCode上线混淆变异算法回调编译执行

知识点 #知识点&#xff1a; 1、ShellCode-分析&朔源&感知 2、ShellCode-混淆&编码&算法 3、回调执行解析-API&汇编&句柄#章节点&#xff1a; 编译代码面-ShellCode-混淆 编译代码面-编辑执行器-编写 编译代码面-分离加载器-编写 程序文件面-特征码定…

拖拽示教功能块(含算法介绍和完整SCL ST源代码)

这篇博客介绍简单拖拽示教功能的简单原理,在了解示教功能之前大家需要熟悉运动控制相关的内容,这篇博客我们以脉冲驱动为例介绍,所以大家可以先熟悉下脉冲控制功能块,有关运动控制的相关内容,可以查看运动控制专栏,主要链接如下: SMART PLC和V90伺服实现外部脉冲位置控…

【VUE】解决图片视频加载缓慢/首屏加载白屏的问题

1 问题描述 在 Vue3 项目中&#xff0c;有时候会出现图片视频加载缓慢、首屏加载白屏的问题 2 原因分析 通常是由以下原因导致的&#xff1a; 图片或视频格式不当&#xff1a;如果图片或视频格式选择不当&#xff0c;比如选择了无损压缩格式&#xff0c;可能会导致文件大小过大…

《3.linux应用编程和网络编程-第9部分-3.9.linux网络编程实践》 inet_addr inet_ntop inet_pton htons

3.9.1.linux网络编程框架 3.9.1.1、网络是分层的 (1)OSI 7层模型 &#xff1a; 理论指导&#xff0c;7层 (2)网络为什么要分层 网络太复杂 (3)网络分层的具体表现 我们只研究 APPAPI 3.9.1.2、TCP/IP协议引入 (1)TCP/IP协议是用的最多的网络协议实现 (2)TCP/IP分为4层&#xf…

浙大数据结构第三周之03-树1 树的同构

题目详情&#xff1a; 给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2&#xff0c;则我们称两棵树是“同构”的。例如图1给出的两棵树就是同构的&#xff0c;因为我们把其中一棵树的结点A、B、G的左右孩子互换后&#xff0c;就得到另外一棵树。而图2就不是同构的…

Python物联网开发-Python_Socket通信开发-Python与Tcp协议物联网设备通信-Socket客户端

一、前言 Python在物联网开发中的重要愈来愈重&#xff0c;因此&#xff0c;掌握Python语言与物联网设备之间的通信就显得尤为重要&#xff0c;可以通过编写Python程序实现获取物联网设备的传感器数值并可以更改物联网设备上的执行器状态。 二、程序实现 首先&#xff0c;当使…

中位数(树状数组+二分)

L-中位数_2023河南萌新联赛第&#xff08;一&#xff09;场&#xff1a;河南农业大学 (nowcoder.com) 树状数组二分 #include <bits/stdc.h> using namespace std; typedef long long ll; const int N2e6; const long long inf 0x7f7f7f7f7f7f7f7f; #define endl \n in…

【Android Framework系列】第5章 AMS启动流程

1 AMS简介 AMS&#xff08;Activity Manager Service&#xff09;是Android中最核心的服务&#xff0c;管理着四大组件的启动、切换、调度及应用进程的管理和调度等工作。AndroidQ将Activity移到了ActivityTaskManagerService中&#xff0c;但也和AMS相关联。 AMS通过使用一些…

【来不及刷题之】44、滑动窗口最小值

暴力方法&#xff1a;超时 class Solution {public int[] maxSlidingWindow(int[] nums, int k) {int nnums.length;int sizen-(k-1);int[] resnew int[size];int slow0,quickk-1;int maxNumfindMax(nums,slow,quick);res[0]maxNum;while (quick<n-1){quick;int addNumnums…

66. 两个链表的第一个公共结点

目录 链接&#xff1a; 题目&#xff1a; 思路&#xff1a; 代码&#xff1a; 图片&#xff1a; 链接&#xff1a; 原题链接 题目&#xff1a; 输入两个链表&#xff0c;找出它们的第一个公共结点。 当不存在公共节点时&#xff0c;返回空节点。 数据范围 链表长度 [1,200…

mapbox跳过accesstoken检测

不写token的时候&#xff0c;控制台就会报这个错 写上token的时候&#xff0c;就会报这个错 然后想让这个错不再出现&#xff0c;就不要再声明mapboxgl.accessToken&#xff0c;并且把node_mudules这个文件夹下面的这个文件里面的红圈内的括号内容改成false。 this._request…

React 安装 报错“Modal不能用作jsx组件”

提示 &#xff1a;‘Modal’ cannot be used as a JSX component. 原因&#xff1a;可能导致是类似antd组件报错 要确认react ts 版本是否适配 解决方法&#xff1a; 第一步&#xff1a;查看代码中的 package.json 文件 查看 typescript 与 types/react 第二步&#xff1a;查…

这些ai图片处理工具就是你神奇的创作伙伴

当我们需要图像来代表某种概念或主题时&#xff0c;我们通常需要花费大量的时间去寻找、编辑、或者设计一张符合要求的图片。但是现在&#xff0c;随着人工智能技术的发展&#xff0c;越来越多的ai自动生成图片软件被开发出来&#xff0c;让我们可以更快捷地获取所需的图像。那…