浮点数的近似保存与计算

2023-11-08

负数的补码存储

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

补码表示

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

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.


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

浮点数的近似保存与计算 的相关文章

  • ASP.NET Core Serilog 未将属性推送到其自定义列

    我有这个设置appsettings json对于我的 Serilog 安装 Serilog MinimumLevel Information Enrich LogUserName Override Microsoft Critical Wr
  • 从父类调用子类方法

    a doStuff 方法是否可以在不编辑 A 类的情况下打印 B did stuff 如果是这样 我该怎么做 class Program static void Main string args A a new A B b new B a
  • 如何忽略“有符号和无符号整数表达式之间的比较”?

    谁能告诉我必须使用哪个标志才能使 gcc 忽略 有符号和无符号整数表达式之间的比较 警告消息 gcc Wno sign compare 但你确实应该修复它警告你的比较
  • Newtonsoft JSON PreserveReferences处理自定义等于用法

    我目前在使用 Newtonsoft Json 时遇到一些问题 我想要的很简单 将要序列化的对象与所有属性和子属性进行比较以确保相等 我现在尝试创建自己的 EqualityComparer 但它仅与父对象的属性进行比较 另外 我尝试编写自己的
  • 将布尔参数传递给 SQL Server 存储过程

    我早些时候问过这个问题 我以为我找到了问题所在 但我没有 我在将布尔参数传递给存储过程时遇到问题 这是我的 C 代码 public bool upload false protected void showDate object sende
  • 获取没有非标准端口的原始 url (C#)

    第一个问题 环境 MVC C AppHarbor Problem 我正在调用 openid 提供商 并根据域生成绝对回调 url 在我的本地机器上 如果我点击的话 效果很好http localhost 12345 login Request
  • 将目录压缩为单个文件的方法有哪些

    不知道怎么问 所以我会解释一下情况 我需要存储一些压缩文件 最初的想法是创建一个文件夹并存储所需数量的压缩文件 并创建一个文件来保存有关每个压缩文件的数据 但是 我不被允许创建许多文件 只能有一个 我决定创建一个压缩文件 其中包含有关进一步
  • Json.NET - 反序列化接口属性引发错误“类型是接口或抽象类,无法实例化”

    我有一个类 其属性是接口 public class Foo public int Number get set public ISomething Thing get set 尝试反序列化Foo使用 Json NET 的类给我一条错误消息
  • WPF TabControl,用C#代码更改TabItem的背景颜色

    嗨 我认为这是一个初学者的问题 我搜索了所有相关问题 但所有这些都由 xaml 回答 但是 我需要的是后台代码 我有一个 TabControl 我需要设置其项目的背景颜色 我需要在选择 取消选择和悬停时为项目设置不同的颜色 非常感谢你的帮助
  • vector 超出范围后不清除内存

    我遇到了以下问题 我不确定我是否错了或者它是一个非常奇怪的错误 我填充了一个巨大的字符串数组 并希望在某个点将其清除 这是一个最小的例子 include
  • 在数据库中搜索时忽略空文本框

    此代码能够搜索数据并将其加载到DataGridView基于搜索表单文本框中提供的值 如果我将任何文本框留空 则不会有搜索结果 因为 SQL 查询是用 AND 组合的 如何在搜索 从 SQL 查询或 C 代码 时忽略空文本框 private
  • 从路径中获取文件夹名称

    我有一些路c server folderName1 another name something another folder 我如何从那里提取最后一个文件夹名称 我尝试了几件事 但没有成功 我只是不想寻找最后的 然后就去休息了 Thank
  • 当操作繁忙时,表单不执行任何操作(冻结)

    我有一个使用 C 的 WinForms 应用程序 我尝试从文件中读取一些数据并将其插入数据表中 当此操作很忙时 我的表单冻结并且无法移动它 有谁知道我该如何解决这个问题 这可能是因为您在 UI 线程上执行了操作 将文件和数据库操作移至另一个
  • 实体框架 4 DB 优先依赖注入?

    我更喜欢创建自己的数据库 设置索引 唯一约束等 使用 edmx 实体框架设计器 从数据库生成域模型是轻而易举的事 现在我有兴趣使用依赖注入来设置一些存储库 我查看了 StackOverflow 上的一些文章和帖子 似乎重点关注代码优先方法
  • 将 xml 反序列化为类,list<> 出现问题

    我有以下 XML
  • 插入记录后如何从SQL Server获取Identity值

    我在数据库中添加一条记录identity价值 我想在插入后获取身份值 我不想通过存储过程来做到这一点 这是我的代码 SQLString INSERT INTO myTable SQLString Cal1 Cal2 Cal3 Cal4 SQ
  • 32 位到 64 位内联汇编移植

    我有一段 C 代码 在 GNU Linux 环境下用 g 编译 它加载一个函数指针 它如何执行并不重要 使用一些内联汇编将一些参数推送到堆栈上 然后调用该函数 代码如下 unsigned long stack 1 23 33 43 save
  • C 中的异或运算符

    在进行按位操作时 我在确定何时使用 XOR 运算符时遇到一些困难 按位与和或非常简单 当您想要屏蔽位时 请使用按位 AND 常见用例是 IP 寻址和子网掩码 当您想要打开位时 请使用包含或 然而 XOR 总是让我明白 我觉得如果在面试中被问
  • 如何在 C++ BOOST 中像图形一样加载 TIFF 图像

    我想要加载一个 tiff 图像 带有带有浮点值的像素的 GEOTIFF 例如 boost C 中的图形 我是 C 的新手 我的目标是使用从源 A 到目标 B 的双向 Dijkstra 来获得更高的性能 Boost GIL load tiif
  • 使用 libcurl 检查 SFTP 站点上是否存在文件

    我使用 C 和 libcurl 进行 SFTP FTPS 传输 在上传文件之前 我需要检查文件是否存在而不实际下载它 如果该文件不存在 我会遇到以下问题 set up curlhandle for the public private ke

随机推荐

  • opencv 07 用Hausdorff距离做形状匹配(shape_example) vs2015

    01 资源 OpenCV自带的行人检测demo opencv samples cpp shape example cpp shape example cpp可以图形形状相似对比 通过判断Hausdorff距离的结果做出最匹配判断 Hausd
  • Schedule

    Part1背景 定时任务 在我们实际开发中经常会用到 比如 Linux 的 Corntab Django 的 Django celery Django corntab 等 但是这些工具和框架总有某些不合适的地方 比如不灵活 笨重等 今天我们
  • 【算法】模拟退火

    文章目录 1 模拟退火介绍 1 1模拟退火的可行性 1 2退火模型 2 详解退火 2 1退火过程 2 2各变量说明 2 2 1关于接收概率 3 退火模拟求根号n的值 4 洛谷POJ 2420 1 模拟退火介绍 模拟退火是模拟物理上退火方法
  • ionic修改控件内置class样式

    背景 使用ionic中控件 有时候他的样式不能满足实际开发需求 需要改改一些样式 ion datetime 时间控件 想修改一下默认的白色背景 修改后 代码 在variables scss文件中的root伪类中增加属性 root 修改时间控
  • 正在设定sun-java6-jre解决方法

    今天搭建一个linux的开发环境遇到一个很无解的问题 出现一个 正在设定 sun java6 jre 的界面 然后鼠标接盘都操作不了 还是惊叹网络的强大 在网上搜索到一个解决方案 才发现遇到问题特别时一个很无解的问题 要即使的上网搜索一下
  • 计算机图形学 期末复习 微课版 孔令德 六、自由曲线与曲面 期末复习

    重点 公式 连续性条件 参数连续性 0阶参数连续性 若两个相邻的曲线段在首末点相连接 C 0 1阶参数连续性 若两个相邻曲线段在相交点处有相同的一阶导数 C 1 2阶参数连续性 若两个相邻曲线段的方程在相交点处具有相同的一阶和二阶导数 C
  • 基于CCG算法的IEEE33配电网两阶段鲁棒优化调度matlab

    目录 1 前言 2基本内容 2 1 配网两阶段鲁棒模型 2 2 求解步骤 3部分程序 4程序结果 5程序链接 1 前言 鲁棒优化是电力系统研究的热点 而两阶段鲁棒和分布鲁棒研究就成为各类期刊 sci ei 核心 的宠儿 最简单的思路是通过改
  • unity3D塔防游戏-虚拟现实大作业-包含源程序、导出exe文件,游戏设计报告

    unity3d塔防游戏 下载链接在文末 点我下载资源 https download csdn net download weixin 43474701 35073702
  • franchisor and franchisee

    What is the difference between a franchisor and a franchisee The franchisor is the person or corporation that owns the t
  • SpringCloud(四)注册中心之Eureka

    SpringCloud 四 注册中心之Eureka 第一代 Spring Cloud 核心组件 从形式上来说 Feign一个顶三 Feign RestTemplate Ribbon Hystrix 常用的服务注册中心 Eureka Naco
  • 【ahk】映射按键到执行函数

    global zFuncCallPattern w IsFuncCallStr callFuncStr Return RegExMatch callFuncStr O zFuncCallPattern matchObj EvalStrArg
  • STM32 基础系列教程 33 - Lwip_tcp_client

    前言 学习stm32 以太网接口使用 及LWIP使用 用LWIP快速实现一个TCP client网络通信功能 学会基本LWIP的网络数据接收与发送功能 让初学者了解lwip 关于Lwip更多的功能介绍将会在后期的STM32 中级教程中介绍
  • 前端开发利器VSCODE推荐

    VSCODE 一个运行于 Mac OS X Windows和 Linux 之上的 针对于编写现代 Web 和云应用的跨平台源代码编辑器 注意 VSCODE适用于前端开发 仅仅是一个编辑器 并不是类似于Visual Studio 2015一样
  • 情人节送玫瑰花Java实现

    RoseException java package cn campsg java experiment exception public class RoseException extends Exception public RoseE
  • Spring面试题

    推荐博客 https blog csdn net a745233700 article details 80959716 1 Spring是什么 Spring是一个轻量级的IoC和AOP容器框架 是为Java应用程序提供基础性服务的一套框架
  • 在电子行业已经做了6年了

    从12年5月份到现在 已经快8年了 感觉时间过得好快 在从学校毕业后电子行业也做了6年了 从来没有想过要换行业 因为我也是在是很喜欢电子这一行 喜欢电路图 喜欢PCB 每到看到PCB总有一种亲切感 只可惜自己学艺不精 目前还是没有开窍 但还
  • ceph安装记录总结

    1 环境准备 准备三台虚机 每个虚机配合三块数据盘 2块网卡 一个网卡设置外网 一个网卡设置成内网 配置文件设置 根据实际情况配置每一个节点 编辑 letc sysconfig network scripts ifcfg ethO 文件 外
  • socket.io设置websocket优先使用

    查看https github com socketio engine io client blob master lib socket js L91 this transports opts transports polling webso
  • 国内领先的十大API接口排行

    应用程序编程接口API即 Application Programming Interface 现在众多企业的应用系统中常用的开放接口 对接相应的系统 软件功能 简化专业化的程序开发 一 百度API 百度API超市开通1136个数据服务接口
  • 浮点数的近似保存与计算

    这里写目录标题 负数的补码存储 十进制浮点数与二进制的转换 有限循环的二进制 无限循环的二进制 计算机对浮点数的保存 无限循环二进制数的保存 浮点数的近似 参考文献 负数的补码存储 首先我们回忆一下负数的补码表示 我们都知道 有符号数的负数