计算机中的浮点数 - 为什么十进制的 0.1 在计算机中是一个无限循环小数

计算机中的浮点数 - 为什么十进制的 0.1 在计算机中是一个无限循环小数

flyfish

用 float 或 double 来存储小数时不是精确值

浮点数在计算机中是以二进制形式存储的,通常使用 IEEE 754 标准。浮点数由三个部分组成:符号位、指数位和尾数位。
先看一个例子

#include <iostream>
#include <iomanip>using namespace std;int main()
{cout << "Hello World!" << endl;double x = 1.0 / 10.0;double y = 1.0 - 0.9;double z = 1.0 + 0.1;// 设置输出精度cout << fixed << setprecision(17);// 观察 x、y、z 的结果cout << "x = " << x << endl;cout << "y = " << y << endl;cout << "z = " << z << endl;return 0;
}
Hello World!
x = 0.10000000000000001
y = 0.09999999999999998
z = 1.10000000000000009

浮点数比较

由于浮点数运算可能产生微小的误差,在比较浮点数时,应避免直接使用 ==。可以定义一个非常小的数(称为 epsilon)来进行比较。

#include <cmath>
#include <iostream>bool isEqual(double a, double b, double epsilon = 1e-10) {return std::fabs(a - b) < epsilon;
}int main() {double a = 0.1 * 3;double b = 0.3;if (isEqual(a, b)) {std::cout << "a and b are equal." << std::endl;} else {std::cout << "a and b are not equal." << std::endl;}return 0;
}
a and b are equal.

float 和 double 类型的 0.1 并不相等,因为它们在二进制中的表示不完全相同

#include <iostream>
#include <iomanip>int main() {float a = 0.1f;double b = 0.1;std::cout << std::setprecision(20);std::cout << "float a = 0.1f: " << a << std::endl;std::cout << "double b = 0.1: " << b << std::endl;if (a == b) {std::cout << "a and b are equal." << std::endl;} else {std::cout << "a and b are not equal." << std::endl;}return 0;
}
float a = 0.1f: 0.10000000149011611938
double b = 0.1: 0.10000000000000000555
a and b are not equal.

float 和 double 的精度差异

#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>int main() {float floatNum = 1.0f / 7.0f;double doubleNum = 1.0 / 7.0;// 设置输出精度std::cout << std::fixed << std::setprecision(64);// 输出 float 和 double 的值std::cout << "float:  " << floatNum << std::endl;std::cout << "double: " << doubleNum << std::endl;return 0;
}
float:  0.1428571492433547973632812500000000000000000000000000000000000000
double: 0.1428571428571428492126926812488818541169166564941406250000000000

将循环小数转换为分数

#include <iostream>
#include <string>
#include <sstream>
#include <cmath>
#include <iomanip>// 定义一个结构来表示分数
struct Fraction {long long numerator;long long denominator;
};// 最大公约数
long long gcd(long long a, long long b) {return b == 0 ? a : gcd(b, a % b);
}// 将小数部分转换为分数
Fraction repeatingDecimalToFraction(const std::string& decimal) {size_t pos = decimal.find('(');std::string nonRepeatingPart = decimal.substr(0, pos);std::string repeatingPart = decimal.substr(pos + 1, decimal.size() - pos - 2);// 非循环部分和循环部分长度int n = nonRepeatingPart.size() - 2; // 减去 "0." 的长度int m = repeatingPart.size();// 非循环部分的小数double nonRepeatingDecimal = std::stod(nonRepeatingPart);// 构造非循环部分的分数long long nonRepeatingNumerator = static_cast<long long>(nonRepeatingDecimal * std::pow(10, n));long long nonRepeatingDenominator = std::pow(10, n);// 构造循环部分的分数long long repeatingNumerator = std::stoll(repeatingPart);long long repeatingDenominator = std::pow(10, m) - 1;// 将循环部分的分数移动到正确的位置repeatingNumerator += nonRepeatingNumerator * repeatingDenominator;repeatingDenominator *= nonRepeatingDenominator;// 简化分数long long divisor = gcd(repeatingNumerator, repeatingDenominator);repeatingNumerator /= divisor;repeatingDenominator /= divisor;return {repeatingNumerator, repeatingDenominator};
}int main() {std::string decimal = "0.285714(285714)";Fraction fraction = repeatingDecimalToFraction(decimal);std::cout << "Fraction: " << fraction.numerator << "/" << fraction.denominator << std::endl;return 0;
}
Fraction: 2/7

查看浮点数的IEEE 754表示

IEEE 754表示:这是浮点数在计算机内存中的存储格式,包含了符号、指数和尾数。用于浮点数计算和存储。

#include <iostream>
#include <bitset>
#include <iomanip>void printFloatBinary(float number) {// 将 float 类型重新解释为 uint32_t 类型uint32_t binary = *reinterpret_cast<uint32_t*>(&number);std::bitset<32> bits(binary);std::cout << "Float: " << number << std::endl;std::cout << "Binary: " << bits << std::endl;
}void printDoubleBinary(double number) {// 将 double 类型重新解释为 uint64_t 类型uint64_t binary = *reinterpret_cast<uint64_t*>(&number);std::bitset<64> bits(binary);std::cout << "Double: " << number << std::endl;std::cout << "Binary: " << bits << std::endl;
}int main() {float floatNum = 0.1f;double doubleNum = 0.1;printFloatBinary(floatNum);printDoubleBinary(doubleNum);return 0;
}
Float: 0.1
Binary: 00111101110011001100110011001101
Double: 0.1
Binary: 0011111110111001100110011001100110011001100110011001100110011010

符号位:第 1 位
指数位:
对于 float(32 位):第 2 到第 9 位(共 8 位)
对于 double(64 位):第 2 到第 12 位(共 11 位)
尾数位:
对于 float(32 位):第 10 到第 32 位(共 23 位)
对于 double(64 位):第 13 到第 64 位(共 52 位)

手工将0.1转换为二进制

转换整数部分:0(已经是零)

  1. 0.1 × 2 = 0.2 (整数部分:0)
  2. 0.2 × 2 = 0.4 (整数部分:0)
  3. 0.4 × 2 = 0.8 (整数部分:0)
  4. 0.8 × 2 = 1.6 (整数部分:1)
  5. 0.6 × 2 = 1.2 (整数部分:1)
  6. 0.2 × 2 = 0.4 (整数部分:0)
  7. 0.4 × 2 = 0.8 (整数部分:0)
  8. 0.8 × 2 = 1.6 (整数部分:1)
  9. 0.6 × 2 = 1.2 (整数部分:1)
  10. 0.2 × 2 = 0.4 (整数部分:0)

合并整数部分

将上述每一步的整数部分合并起来:

0. 1 10 = 0.0001100110011001100110011001100 … 2 0.1_{10} = 0.0001100110011001100110011001100 \ldots_2 0.110=0.00011001100110011001100110011002

最终得到的二进制表示是一个无限循环小数:
0. 1 10 = 0. ( 0001100110011001100110011001100 … ) 2 0.1_{10} = 0.(0001100110011001100110011001100 \ldots)_2 0.110=0.(0001100110011001100110011001100)2
其中,上面的横线表示循环节: 0001 1001 ‾ 0001\overline{1001} 00011001

IEEE 754表示与32位二进制表示的关系

小数二进制表示

我们前面计算的0.1的小数二进制表示(0.0001100110011001100110011001100…)是直接将小数部分转换为二进制的结果,这是一个无限循环的小数。

IEEE 754 二进制浮点数表示

而“00111101110011001100110011001101”是0.1在计算机中存储时的IEEE 754标准的32位单精度浮点数表示。IEEE 754标准规定了浮点数的存储格式,包括符号位、指数位和尾数(或称为有效数字位)。

IEEE 754 单精度浮点数表示解释

IEEE 754单精度浮点数使用32位来表示一个浮点数,其中:

  • 1位用于符号位
  • 8位用于指数位
  • 23位用于尾数位
    以0.1 为例
  1. 符号位:0 表示正数。
  2. 将0.1转化为二进制:0.0001100110011001100110011001100110011001100110011001100…(无限循环)
  3. 规格化二进制:将其表示为 1.xxxxxx × 2^(-4) 的形式,所以 0.1 = 1.10011001100110011001101 × 2^(-4)
  4. 指数:由于偏移量为127,所以储存的指数为 -4 + 127 = 123(即二进制的01111011)
  5. 尾数:取1后面的23位:10011001100110011001101
    合并这些部分后得到IEEE 754表示:
    0 ∣ 01111011 ∣ 10011001100110011001101 0 | 01111011 | 10011001100110011001101 0∣01111011∣10011001100110011001101

这就对应我们之前看到的32位二进制:
00111101110011001100110011001101 00111101110011001100110011001101 00111101110011001100110011001101

数据类型大小指数位尾数位偏移量
binary1616 位5 位10 位15
binary3232 位8 位23 位127
binary6464 位11 位52 位1023
binary128128 位15 位112 位16383

ratio来处理有理数

#include <iostream>
#include <ratio>int main() {// 定义分数类型using MyRatio = std::ratio<1, 3>;// 获取分子和分母constexpr int numerator = MyRatio::num;constexpr int denominator = MyRatio::den;std::cout << "Fraction: " << numerator << "/" << denominator << std::endl;return 0;
}
Fraction: 1/3

自定义类实现用分数精确表达浮点数

#include <iostream>
#include <numeric> // for std::gcd
#include <iomanip>
class Fraction {
public:Fraction(long long numerator, long long denominator) : numerator(numerator), denominator(denominator) {reduce();}// 加法运算Fraction operator+(const Fraction& other) const {long long new_numerator = numerator * other.denominator + other.numerator * denominator;long long new_denominator = denominator * other.denominator;return Fraction(new_numerator, new_denominator);}// 减法运算Fraction operator-(const Fraction& other) const {long long new_numerator = numerator * other.denominator - other.numerator * denominator;long long new_denominator = denominator * other.denominator;return Fraction(new_numerator, new_denominator);}// 乘法运算Fraction operator*(const Fraction& other) const {return Fraction(numerator * other.numerator, denominator * other.denominator);}// 除法运算Fraction operator/(const Fraction& other) const {return Fraction(numerator * other.denominator, denominator * other.numerator);}// 输出friend std::ostream& operator<<(std::ostream& os, const Fraction& fraction) {os << fraction.numerator << "/" << fraction.denominator;return os;}private:long long numerator;long long denominator;// 约分void reduce() {long long gcd_value = std::gcd(numerator, denominator);numerator /= gcd_value;denominator /= gcd_value;if (denominator < 0) {numerator = -numerator;denominator = -denominator;}}
};int main() {Fraction frac1(1, 10); // 0.1Fraction frac2(1, 3);  // 1/3std::cout << "Fraction 1: " << frac1 << std::endl;std::cout << "Fraction 2: " << frac2 << std::endl;Fraction sum = frac1 + frac2;Fraction diff = frac1 - frac2;Fraction prod = frac1 * frac2;Fraction quot = frac1 / frac2;std::cout << "Sum: " << sum << std::endl;std::cout << "Difference: " << diff << std::endl;std::cout << "Product: " << prod << std::endl;std::cout << "Quotient: " << quot << std::endl;return 0;
}
Fraction 1: 1/10
Fraction 2: 1/3
Sum: 13/30
Difference: -7/30
Product: 1/30
Quotient: 3/10

64位的存储空间,虽然范围很大,但如果分子和分母的值超出这个范围,仍然会发生溢出。
对于非常大的数,gcd 函数的计算可能会变得非常慢,因为它需要计算两个大数的最大公约数。
如果要处理极其巨大的数,即使它们没有溢出,内存消耗也是一个问题。
在实践中可以先测试下 Boost Multiprecision 这样的库。

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

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

相关文章

【2024】LeetCode HOT 100——图论

目录 1. 岛屿数量1.1 C++实现1.2 Python实现1.3 时空分析2. 腐烂的橘子2.1 C++实现2.2 Python实现2.3 时空分析3. 课程表3.1 C++实现3.2 Python实现3.3 时空分析4. 实现 Trie (前缀树)4.1 C++实现4.2 Python实现4.3 时空分析1. 岛屿数量 🔗 原题链接:200. 岛屿数量 经典的Fl…

鸿蒙应用开发之OpenGL的EGL

要开发OpenGL程序,那么这个程序就需要与操作系统进行交流,由于存在不同的操作系统,这样就面临着开发不同的程序的困难,为了解决这个问题,就引入了一个中间层的软件库,这个软件库叫做EGL。 众所周知,Opengl是跨平台的,那么面对各种平台的差异性,Opengl是如何抹平而做到…

CleanMyMacX2024让你的苹果电脑重获生机!

在电脑使用过程中&#xff0c;你是否遇到过这样的问题&#xff1a;运行速度变慢、磁盘空间不足、系统出现故障……这些问题不仅影响你的工作效率&#xff0c;还会让电脑的使用寿命大大缩短。那么&#xff0c;如何轻松解决这些问题呢&#xff1f;答案就是CleanMyMac X。 CleanM…

苏州大厂面试题JAVA 面试集

基础知识1、强引用、软引用、弱引用、幻象引用有什么区别?(java基础) 答案参考:https://time.geekbang.org/column/article/6970 2、 对比Hashtable、HashMap、TreeMap有什么不同?(数据结构) 答案参考:https://time.geekbang.org/column/article/8053 3、一个线程调用两次…

ubuntu20.04安装kazam桌面屏幕录制工具

在Ubuntu 20.04上安装Kazam可以通过以下步骤进行&#xff1a; 1.打开终端&#xff1a;可以通过按下Ctrl Alt T组合键来打开终端。 2.添加PPA源&#xff1a;Kazam不再在官方Ubuntu仓库中&#xff0c;但可以通过PPA源进行安装。在终端中输入以下命令来添加PPA&#xff1a; su…

AI绘画:P图如此丝滑,OpenAI上线ChatGPT图像编辑功能,DallE-3绘画如此简单

大家好我是极客菌&#xff0c;用ChatGPT的DallE-3进行AI绘画对很多人来说是一个门槛很低的选择&#xff0c;现在OpenAI又重磅上线了图像编辑器功能(DallE editor)&#xff0c;可以很方便的对图片的局部进行修改&#xff0c;而且支持中文&#xff0c;主打一个功能强大且好用&…

Jquery 获得Form下的所有text、checkbox等表单的值

Jquery使用表单我主要是想获得某一个表单下的所有text获得checkbox的值: 可以这样写: var parameter{}; $("input[typetext]",document.forms[0]).each(function(){ alert(this.name); }); 获得所有名为hobby的选中的checkbox的值和form2下的所有text的值 function s…

【云原生】Prometheus 使用详解

目录 一、前言 二、服务监控概述 2.1 什么是微服务监控 2.2 微服务监控指标 2.3 微服务监控工具 三、Prometheus概述 3.1 Prometheus是什么 3.2 Prometheus 特点 3.3 Prometheus 架构图 3.3.1 Prometheus核心组件 3.3.2 Prometheus 工作流程 3.4 Prometheus 应用场景…

Elasticsearch 配置

Elasticsearch提供良好的默认设置&#xff0c;并且只需要很少的配置。可以使用群集更新设置API在正在运行的群集上更改大多数设置。 配置文件应包含特定于节点的设置&#xff08;如node.name和paths&#xff09;&#xff0c;或节点为了能够加入集群而需要的设置&#xff0c;如…

webrtc-m120编译 (m126)

WebRTC实时互动入门 环境 lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 22.04.4 LTS Release: 22.04 Codename: jammyuname -a Linux yqw-Lenovo-XiaoXinPro-13ARE-2020

Agent技术在现代软件开发与应用中的探索

一、引言 随着计算机科学的快速发展&#xff0c;Agent技术作为人工智能和分布式计算领域的重要分支&#xff0c;已经渗透到软件开发的各个方面。Agent技术通过赋予软件实体自主性和交互性&#xff0c;使得软件系统能够更加智能、灵活地响应环境变化和用户需求。本文将对Agent技…

java基于ssm+jsp 个人交友网站

1前台首页功能模块 个人交友网站&#xff0c;在系统首页可以查看首页、交友信息、线下活动、系统公告、论坛信息、我的、跳转到后台、客服等内容&#xff0c;如图1所示。 图1系统功能界面图 用户注册&#xff0c;在用户注册页面可以填写用户账号、密码、用户姓名、年龄等信息进…

深入理解 Spring MVC:原理与架构解析

文章目录 前言一、MVC二、Spring MVC三、Spring MVC 工作流程四、小结推荐阅读 前言 Spring MVC 是一种基于 Java 的 Web 应用开发框架&#xff0c;它通过模型-视图-控制器&#xff08;Model-View-Controller, MVC&#xff09;的设计模式来组织和管理 Web 应用程序。本文将深入…

java基于ssm+jsp 二手车交易网站

1用户功能模块 定金支付管理&#xff0c;在定金支付管理页面可以填写订单编号、车型、品牌、分类、车身颜色、售价、订金金额、付款日期、备注、用户名、姓名、联系方式、是否支付等信息&#xff0c;进行详情、修改&#xff0c;如图1所示。 图1定金支付管理界面图 预约到店管…

1.搭建篇——帝可得后台管理系统

目录 前言项目搭建一、搭建后端项目1.初始化项目Maven构建 2.MySQL相关导入sql配置信息 3. Redis相关启动配置信息 4.项目运行 二、 搭建前端项目1.初始化项目2.安装依赖3.项目运行 三、问题 前言 提示&#xff1a;本篇讲解 帝可得后台管理系统 项目搭建 项目搭建 一、搭建后…

单段时间最优S型速度规划算法

一&#xff0c;背景 在做机械臂轨迹规划的单段路径的速度规划时&#xff0c;除了参考《Trajectory Planning for Automatic Machines and Robots》等文献之外&#xff0c;还在知乎找到了这位大佬 韩冰 写的在线规划方法&#xff1a; https://zhuanlan.zhihu.com/p/585253101/e…

单片机的学习(15)--LCD1602

LCD1602 14.1LCD1602的基础知识1.LCD1602介绍2.引脚及应用电路3.内部结构框图4.时序结构5.LCD1602指令集6.字符值7.LCD1602操作流程 14.2LCD1602功能函数代码1.显示一个字符&#xff08;1&#xff09;工程目录&#xff08;2&#xff09;main.c函数&#xff08;3&#xff09;LCD…

oj E : 投资项目的方案

Description 有n种基础的投资项目&#xff0c;每一种的单位收益率为profitn&#xff0c;存在m种投资组合&#xff0c;限制每一种的投资总额不能超过invest_summ 每种投资组合中项目所需的单位投入是不同的&#xff0c;为costmn 求&#xff1a;使得收益率之和最高的每种项目投…

基于机器学习的制冷系统过充电和欠充电故障诊断(采用红外热图像数据,MATLAB)

到目前为止&#xff0c;制冷系统故障诊断方法已经产生很多种&#xff0c;概括起来主要有三大类&#xff1a;基于分析的方法&#xff0c;基于知识的方法和基于数据驱动的方法。基于分析的方法主要获得制冷系统的数学模型&#xff0c;通过残差来检测和诊断故障。如果存在残差且很…

[JS]BOM操作

介绍 BOM(Browser Object Model)是浏览器对象模型 window对象是一个全局对象, 也是JS中的顶级对象通过var定义在全局作用域中的变量和函数都会变成window对象的属性和方法window对象下的属性和方法调用时一般省略window 间歇函数 定时器 定时器是间歇函数的一种, 可以每个每…