浮点数始末详解|科学计数法、浮点数表示、精度有效值、规格化与非规格化

news2024/11/13 9:35:30

如果直接看浮点数表示法有点费力或者不好理解,不妨复习一下科学计数法。毕竟我们一直接触十进制,从十进制的角度可能更好理解其特性。

目录

  • 科学计数法 Scientific Notation
    • 1. 科学计数法的定义
    • 2. 精确度与有效数字
    • 3. 转换为科学计数法
      • 示例:
    • 4. 运算
  • 浮点数概述
  • 浮点数的表示
    • 不同精度的浮点数
    • 表示方式
      • **S**:符号码、符号位(Sign bit),占1位,取值为0或1,0表示正数,1表示负
      • **E**:阶码、指数位(Exponent bits),表示数值的大小范围
        • 1. **R**
        • 2. **偏移量**
        • 3. **偏移表示法 - 计算方式**
        • 4. 指数位取值范围:-126到127。
        • 为什么使用偏移量
        • **M**:尾码、尾数位(Mantissa bits),表示有效数字部分或者分数(significand or fraction),表示数值的精确度。
        • 规格化与非规格化
        • 规格化到非规格化的平滑过渡
          • 那么非规格化数据和规格化数据之间数据变化是否会出现跳跃不连续的现象?
        • 浮点数规格化
  • 精度损失
  • 浮点数的有效数、误差、精度、与范围
      • 误差
      • 精度
      • 尾数、有效数字与精度的关系
  • 浮点数的运算与比较
      • 1. 加减法运算
      • 示例
      • 2. 乘除法运算
      • 示例
      • 3. 浮点数比较
  • 十进制数转IEEE754标准的浮点表示法 和C#Demo
      • 转换步骤
      • 示例
      • C#代码示例
  • 浮点数运算在编程中的注意事项和技巧
    • 1. 浮点数在C#中的表现
      • C#中的浮点数
      • 科学计数法表示
    • 2. 精度问题
    • 总结
    • 参考资料

科学计数法 Scientific Notation

1. 科学计数法的定义

  1. 概述

    它将一个数表示为一个1到10之间的数字与10的幂的乘积。

    即一个数被表示为 A × 1 0 n A \times 10^n A×10n的形式,其中:

    • A A A是一个大于等于1且小于10的实数(通常是一个整数或小数)。

    规定了A的大小,这点就对应了浮点数表示中的规约或者说规范化。
    其目的是为了保证表示法的准确性和一致性:不然 20 ∗ 1 0 3 20*10^3 20103 2 ∗ 1 0 4 2*10^4 2104,或者等等其他表示都可以表示为20000,到底用哪个表示呢

  • n n n是一个整数,表示10的指数。
    示例:
    • 1234 1234 1234可以表示为 1.234 × 1 0 3 1.234 \times 10^3 1.234×103
    • 0.00567 0.00567 0.00567可以表示为 5.67 × 1 0 − 3 5.67 \times 10^{-3} 5.67×103
  1. 应用场景
    科学计数法在许多领域都有应用,包括工程学、物理学、化学和计算机科学。
    在计算机编程中,科学计数法常用于浮点数的存储和计算。
  2. 优点
    • 简洁:可以简化表示非常大或非常小的数,避免写出一长串数字。
    • 方便:在科学计算中,使用科学计数法可以简化运算过程。
  3. 使用科学记数法,一个数的数量级、精确度和数值都较容易看出,
    例如:
  4. 一个质子质量的数值为︰0.00000000000000000000000167262158。科学记数法的形式︰ 1.67262158 × 1 0 − 24 1.67262158×10^−24 1.67262158×1024
  5. 若以公斤为表示单位,则木星的质量值约为:1898130000000000000000000000科学记数法的形式︰ 1.89813 × 1 0 27 1.89813×10^{27} 1.89813×1027

2. 精确度与有效数字

  1. 精确度(Precision)指的是数值表示的准确性和细节程度。
  2. 有效数字(Significant Figures)是指在一个数中,从第一个非零数字起,直到末尾数字止的所有数字。表示了数据的精度和可靠性。
  3. 科学记数法中,尾数也被称作有效数
    科学计数法的精确度由有效数字A的位数决定。
    13410,精确到十位,记作: 1.341 × 1 0 4 1.341 \times 10^4 1.341×104
    13410 ,精确到百位,记作: 1.34 × 1 0 4 1.34 \times 10^4 1.34×104
  4. 有效数字的规则
  5. 所有非零数字都是有效的:例如,123.45有五位有效数字(1、2、3、4、5)。
  6. 两个非零数字之间的零也是有效的:例如,101.1203有七位有效数字(1、0、1、1、2、0、3)。
  7. 前缀零始终无效:例如,0.00052只有两位有效数字(5和2)。
  8. 包含小数点的数中,结尾的零是有效的:例如,12.2300有六位有效数字(1、2、2、3、0、0)。
  9. 不包含小数点的数,结尾的零可能有效也可能无效,取决于上下文。但在科学计数法中,有效数字便能够判别。
    例如,1300的有效数字可能是2位、3位或4位,具体取决于上下文。
    科学记数法可以帮助明确有效数字。例如,0.000122300可以写成 1.22300 × 1 0 − 4 1.22300 \times 10^-4 1.22300×104,这样就明确了有六位有效数字
    参考:http://www.shuxueji.com/w/2623

3. 转换为科学计数法

要将一个数转换为科学计数法,需要执行以下步骤:

  1. 如果数字是整数并且大于或等于``10,则将数字的小数点向左移动,直到只有一个非零数字位于小数点左侧。
  2. 如果数字是整数并且小于``1,则将数字的小数点向右移动,直到只有一个非零数字位于小数点左侧。
  3. 如果数字是小数,则将小数点向左或向右移动,使得小数点左侧只有一个非零数字。
  4. 记录小数点移动的位数,这个位数就是10的指数。

示例:

  • 12345 12345 12345转换为科学计数法:
  • 移动小数点,得到 1.2345 × 1 0 4 1.2345 \times 10^4 1.2345×104
  • 0.000567 0.000567 0.000567转换为科学计数法:
  • 移动小数点,得到 5.67 × 1 0 − 4 5.67 \times 10^{-4} 5.67×104

4. 运算

科学计数法的运算规则如下:

  1. 加法和减法
    a × 1 0 n + b × 1 0 n = ( a + b ) × 1 0 n a \times 10^n + b \times 10^n = (a+b) \times 10^n a×10n+b×10n=(a+b)×10n
    例如: ( 3 × 1 0 4 + 4 × 1 0 4 = 7 × 1 0 4 ) (3 \times 10^4 + 4 \times 10^4 = 7 \times 10^4) (3×104+4×104=7×104)
  2. 乘法
    ( a × 1 0 m ) × ( b × 1 0 n ) = ( a × b ) × 1 0 m + n (a \times 10^m) \times (b \times 10^n) = (a \times b) \times 10^{m+n} (a×10m)×(b×10n)=(a×b)×10m+n
    例如: ( 3 × 1 0 6 ) × ( 6 × 1 0 5 ) = ( 3 × 6 ) × 1 0 6 + 5 = 18 × 1 0 11 = 1.8 × 1 0 12 (3 \times 10^6) \times (6 \times 10^5) = (3 \times 6) \times 10^{6+5} = 18 \times 10^{11} = 1.8 \times 10^{12} (3×106)×(6×105)=(3×6)×106+5=18×1011=1.8×1012
  3. 除法
    当两个数都以 10 10 10的幂次形式表示时,它们的除法可以表示为:

a × 1 0 m b × 1 0 n = a b × 1 0 m − n \frac{a \times 10^m}{b \times 10^n} = \frac{a}{b} \times 10^{m-n} b×10na×10m=ba×10mn

例如:

− 6 × 1 0 4 3 × 1 0 3 = − 2 × 1 0 1 \frac{-6 \times 10^4}{3 \times 10^3} = -2 \times 10^{1} 3×1036×104=2×101
这表示 − 6 × 1 0 4 -6 \times 10^4 6×104除以 3 × 1 0 3 3 \times 10^3 3×103等于 − 2 -2 2乘以 10 10 10 1 1 1次幂,即 − 20 -20 20.

浮点数概述

  1. 浮点数是一种用于表示小数的数值类型,顾名思义,其小数点位置可以浮动,使其能够灵活地表示大范围或小范围内的实数。
  2. 与定点数不同,浮点数采用科学计数法来表示数值。

优点:
1. 浮点数的主要优点是能够表示非常大或非常小的数值,而且可以进行高精度的计算。

缺点:
1. 由于精度有限,浮点数可能产生舍入误差。
2. 浮点数的运算通常比整数运算慢,因为它们涉及到更多的处理步骤。
3. 浮点数的比较运算可能会产生意外的结果,因为微小的差异可能被解释为相等或不等,所以在编程的过程中需要格外注意

浮点数的表示

图片来自:https://blog.csdn.net/weixin_47713503/article/details/108699001

不同精度的浮点数

IEEE754半精度浮点数:16位,符号1位,指数5位,尾数10位
IEEE754单精度浮点数:32位,符号1位,指数8位,尾数23位
IEEE754双精度浮点数:64位,符号1位,指数11位,尾数52位

表示方式

  1. 从浮点数的组成成分来看
    浮点数的实际值,等于 符号位 乘以 偏移指数位exponent bias)再乘以分数值(fraction)。
    V a l u e = s i g n × E x p o n e n t b i a s × F r a c t i o n Value = sign \times Exponentbias \times Fraction Value=sign×Exponentbias×Fraction
  2. 更细化描述每个成分的具体含义

V = ( − 1 ) S × R E × M V = (-1)^S \times R^E \times M V=(1)S×RE×M

S:符号码、符号位(Sign bit),占1位,取值为0或1,0表示正数,1表示负

符号码或符号位,一个更偏重于该位置的二进制码的名称,一个更偏重于这个二进制该位置的名称。

E:阶码、指数位(Exponent bits),表示数值的大小范围

阶码一词,可能更能体现该位置编码的作用 → 表示数值的大小范围

在 IEEE 754 标准中,指数部分采用了偏移表示法,这样可以避免使用符号位来表示指数的正负,从而简化硬件实现。

1. R
基数(Radix ),通常为2(二进制)或10(十进制)
2. 偏移量

b i a s = R k − 1 − 1 bias = R^{k-1} - 1 bias=Rk11
K K K为指数位的位数;
例如,对于单精度浮点数(32 位),指数位占用 8 位,偏移量为 b i a s = R k − 1 − 1 = 2 8 − 1 − 1 = 127 bias = R^{k-1} - 1 = 2 ^{8 -1}-1 = 127 bias=Rk11=2811=127

3. 偏移表示法 - 计算方式

E = E x p o n e n t − b i a s E = Exponent - bias E=Exponentbias
假设我们有一个单精度浮点数,其指数部分的二进制表示为 10000001
E = E x p o n e n t − b i a s = 2 7 + 2 0 − ( 2 8 − 1 − 1 ) = 2 E = Exponent - bias = 2^{7} + 2^{0} - ( 2^{8 -1} -1 ) = 2 E=Exponentbias=27+20(2811)=2
因此,这个浮点数的指数部分实际表示的是 2。

4. 指数位取值范围:-126到127。

(对于单精度浮点数(32 位),指数位占用 8 位)

  1. 8位指数位的指数部分最大值 2 7 + 2 6 + 2 5 + 2 4 + 2 3 + 2 2 + 2 1 + 2 0 = 255 2^{7}+2^{6}+2^{5}+2^{4}+2^{3}+2^{2}+2^{1}+2^{0} = 255 27+26+25+24+23+22+21+20=255
  2. 8位指数位的指数部分最小值 0 0 0
  3. 指数实际值为0和255被用作特殊用途
    • 指数值 0:表示非规格化数(也称为次正规数)或零。
    • 指数值 255:表示无穷大(Infinity)或非数字(NaN)。
    • 从图中可以看出,指数位置全为1,此时指数为的值为255,代表 Infinity或**-Infinity**
      • 正无穷大
        在这里插入图片描述

      • 负无穷大
        在这里插入图片描述

      • 指数部分全为1,尾数部分非零:NaN (Not a Number
        在这里插入图片描述

  4. 8位指数位的指数部分可表达的最大值为127
    8 位指数位的指数部分最大值 − 1 − b i a s = 255 − 1 − 127 = 127 8位指数位的指数部分最大值 - 1 - bias = 255 -1 - 127 = 127 8位指数位的指数部分最大值1bias=2551127=127
  5. 8位指数位的指数部分最小值为-126
    8 位指数位的指数部分最小值 + 1 − b i a s = 0 + 1 − 127 = 126 8位指数位的指数部分最小值 + 1 - bias = 0 + 1 - 127 = 126 8位指数位的指数部分最小值+1bias=0+1127=126
为什么使用偏移量

从以上可以看出其实际值范围为0到255,全是正数,却可以表示-126到127,和特殊值,无需额外的符号位来表示指数的正负。
又因为无符号整数的比较和运算通常比有符号整数更简单和高效,并且对于硬件实现也更高效。

M:尾码、尾数位(Mantissa bits),表示有效数字部分或者分数(significand or fraction),表示数值的精确度。

Mantissa [数]尾数;假数;定点部分;小数部分

相关词汇:mantissa pricing 尾数定价,一种商品定价测率,例如¥9.98

相当于科学计数法 A × 1 0 n A \times 10^n A×10n中的实数 A A A的小数部分。

规格化与非规格化

类似于十进制科学计数法一个数被表示为 A × 1 0 n A \times 10^n A×10n的形式,其中 A A A是一个大于等于1且小于10的实数(通常是一个整数或小数)。

在IEEE754标准中:

  1. 非规格化浮点数
    当阶码全为0时,此时为表示非规格化数(也称为次正规数)或零。
    此时指数此时为:E= 1-bias,此时公式为: ( − 1 ) 符号位 × 0. 尾数 × R 1 + b i a s (-1)^{符号位} \times 0.尾数 \times R ^{1 + bias} (1)符号位×0.尾数×R1+bias = ( − 1 ) 符号位 × 0. 尾数 × R − 126 (-1)^{符号位} \times 0.尾数 \times R ^ {-126} (1)符号位×0.尾数×R126
    可以看出此时可以表示0和非常接近于0的数据。
  2. 规格化浮点数与隐藏1:
    尾数的整数部分固定为1,又因为固定为1,所以可以隐藏以节省存储。此时数据也必称为规格化数据。
    所以在浮点数的表示公式也可以写为:
    ( − 1 ) S i g n × 1. M × R E x p − b i a s (-1)^{Sign} \times 1.M \times R ^{Exp-bias} (1)Sign×1.M×RExpbias 或者写为 ( − 1 ) 符号位 × 1. 尾数 × R 偏移指数 (-1)^{符号位} \times 1.尾数 \times R ^{偏移指数} (1)符号位×1.尾数×R偏移指数
    所以当尾码为10000000000时,其值为 2 − 1 = 0.5 2^{-1} = 0.5 21=0.5,但实际计算时,尾数需要加 2 0 2^{0} 20,也就是 2 0 + 2 − 1 = 1.5 2^{0}+2^{-1} = 1.5 20+21=1.5
规格化到非规格化的平滑过渡

根据尾码和阶码的介绍,可以总结出以下会浮点数内容:

  1. 当阶码全为0时,此时取消隐藏的1规则,此时浮点数表示接近于0和0
  2. 当阶码不全为0,也不全为1时,此时有隐藏的1规则,为规格化浮点数
  3. 当阶码全为1,尾码全为0,表示无穷大
  4. 当阶码全为1,尾码不全为0,表示NaN
    在这里插入图片描述
那么非规格化数据和规格化数据之间数据变化是否会出现跳跃不连续的现象?

我们可以使用IEEE-754 Floating Point Converter (h-schmidt.net)提供的工具查看:

  1. 当阶码全为0,尾码最大表示为: 1.1754942 × 1 0 − 38 1.1754942 \times 10^{-38} 1.1754942×1038
  2. 当阶码最低位为1,尾码全为0时表示为: 1.1754944 × 1 0 − 38 1.1754944 \times 10^{-38} 1.1754944×1038
    可以看出他们之间是连续的。
    在这里插入图片描述在这里插入图片描述
    从数学的角度:
    即:非规格化最大值 ( 1 − 2 − 23 ) × 2 − 126 (1- 2 ^{-23}) \times 2 ^{-126} (1223)×2126和规格化最小值 1.0 × 2 − 126 1.0 \times 2 ^{-126} 1.0×2126,计算一下他们之间差多少,在和非规格化数据中最小值对比 2 − 23 × 2 − 126 2 ^{-23} \times 2 ^ {-126} 223×2126,观察差值是否相等,来判断过度是否平滑:
    Normalization-MinValue - de_normalization _max
    = 1.0 × 2 − 126 − ( 1 − 2 − 23 ) × 2 − 126 1.0 \times 2 ^{-126} - (1- 2 ^{-23}) \times 2 ^{-126} 1.0×2126(1223)×2126
    = 2 − 23 × 2 − 126 2 ^ {-23} \times 2 ^ {-126} 223×2126
    综上得出规格化最小值和非规格化最大值,刚好差了一个 2 − 23 × 2 − 126 2 ^ {-23} \times 2 ^ {-126} 223×2126,刚好是浮点数表示中最小的增量单位之一。所以说非规格化到规格化数据的过度是平滑的。
浮点数规格化

浮点数规格化是指将浮点数的尾数调整为符合特定格式的过程,以确保数值表示的精度和一致性。同十进制类似,阶码(指数位)要根据尾数的调整进行相应的增减

  1. 规格化的基本操作包括左规和右规,用于确保浮点数的尾数(或称为有效数字)符合一定的范围。

  2. 左规:当浮点数运算结果为非规格化时,将尾数左移一位,阶码减1
    例如:浮点数 4.0 = ( − 1 ) 0 × 0.5 × 2 3 4.0 = (-1)^{0} \times 0.5 \times 2^{3} 4.0=(1)0×0.5×23,其尾数为0.5,其整数部分小于1,所以为非规格化浮点数数,需要进行左规处理。

    1. 0.5对应二进制位0.1
    2. 左移一位:1 ,对应十进制为1
    3. 阶码-1:3-1 = 2
    4. 左规后的表示方法为 ( − 1 ) 0 × 1 × 2 2 (-1)^{0} \times 1 \times 2^{2} (1)0×1×22
  3. 右规:当浮点数运算结果尾数出现溢出时,将尾数右移一位,阶码加1。
    例如:浮点数 20.0 = ( − 1 ) 0 × 2.5 × 2 3 20.0 = (-1)^{0} \times 2.5 \times 2^{3} 20.0=(1)0×2.5×23,其尾数为2.5,其整数部分大于1,所以为非规格化浮点数数,需要进行右规处理。

    1. 2.5对应二进制位10.1
    2. 左移一位:1.01 对应十进制为5
    3. 阶码+1:3+1 = 4
    4. 左规后的表示方法为 ( − 1 ) 0 × 1.25 × 2 4 (-1)^{0} \times 1.25 \times 2^{4} (1)0×1.25×24
  4. 优点
    规格化的主要目的是使浮点数在计算机中更有效地表示和处理,提高数值的精度和运算的准确性。

  • 下图为计算过程,来自IEEE 754 浮点数转换 - 锤子在线工具 (toolhelper.cn)
    在这里插入图片描述
    • 你也可以使用https://www.h-schmidt.net/FloatConverter/IEEE754.html提供的工具来观察二进制的实际存储
      在这里插入图片描述

精度损失

  1. 浮点数的表示范围和精度有限,某些十进制小数无法精确表示。

    • 舍入误差:由于尾数位数有限,某些数值无法精确表示

      例如,将0.2转换为二进制表示时,结果是一个无限循环小数。具体步骤如下:

      1. 乘以2:0.2 × 2 = 0.4(整数部分为0)
      2. 取小数部分继续乘以2:0.4 × 2 = 0.8(整数部分为0)
      3. 继续:0.8 × 2 = 1.6(整数部分为1)
      4. 继续:0.6 × 2 = 1.2(整数部分为1)
      5. 继续:0.2 × 2 = 0.4(整数部分为0)

      重复上述步骤,得到的二进制表示为:

      0.001100110011…

      因此,0.2在二进制中表示为0.001100110011…,这是一个无限循环的二进制小数

      这种无限循环的小数在计算机中只能被截断,导致精度损失。

    累积误差:多次运算后,舍入误差可能会累积,导致结果不准确

    下溢和上溢:运算结果超出浮点数的表示范围时,会发生下溢(接近零)或上溢(接近无穷大)

浮点数的有效数、误差、精度、与范围

误差

误差分为绝对误差和相对误差:

  • 绝对误差:测量值与真实值之间的差异。

绝对误差 = ∣ 测量值 − 真实值 ∣ \text{绝对误差} = |\text{测量值} - \text{真实值}| 绝对误差=测量值真实值

  • 相对误差:绝对误差与测量值的比值,通常用百分比表示。

    $ 相对误差 = \frac {绝对误差} {真实值} \times 100 % $

精度

对于浮点数来说,精度也与尾数(有效数字)的位数有关。精度越高,表示的数值越准确,误差越小。

尾数、有效数字与精度的关系

浮点数的精度和范围取决于尾数和指数的位数。
当指数位和尾数位的位数之后一定的条件下:

  1. 更多的指数位意味着更大的范围,但精度会降低;
  2. 更多的尾数位意味着更高的精度,但范围会减小。
  3. 有效率数字越少,表示的数值越粗略,误差越大,精度越低。
    1. 有效数字越少,表示的数值越粗略:
      • 有效数字越多,表示的数值越精确。例如,数值 3.14159 比 3.14 更精确,因为前者有更多的有效数字。
      • 当有效数字减少时,数值的表示变得更粗略,细节丢失,误差增加。
    2. 有效数字越少,舍入误差越大:
      • 当有效数字减少时,数值需要进行舍入。
      • 舍入误差会累积,导致最终结果的准确性降低。
      • eg
        假设:
        • 数值 A:3.14159(有 6 位有效数字)
        • 数值 B:3.14(有 3 位有效数字)
        • 数值 C:3.1415926(有 8 位有效数字)
          数值 A 比数值 B 更精确,因为它有更多的有效数字。将数值 C 舍入为数值 A和B 都会引入舍入误差,但是A相对与B误差更小,也就是精确度更高。
  4. 有效数字越少,相对误差越大
    • 相对误差是绝对误差与测量值的比值。当有效数字减少时,相对误差增加。
    • 例如,测量值为 10.0 和 1.0,假设绝对误差均为 0.1。前者的相对误差为 1%,后者的相对误差为 10%

浮点数的运算与比较

1. 加减法运算

浮点数加减法运算主要包括以下步骤:

  1. 对阶:将两个浮点数的指数部分进行比较,将较小指数的尾数部分进行右移,使其指数部分与较大指数相同。
  2. 尾数相加/减:将两个浮点数的尾数部分进行相加或相减。
  3. 规格化:将相加/减后的尾数进行规格化处理,使其满足浮点数表示的要求。
  4. 舍入:根据舍入规则,对尾数部分进行舍入处理。
  5. 溢出判断:判断运算结果是否发生溢出,如果发生溢出,需要进行特殊处理。
    如果此计算不好理解的话,代入十进制会很好理解哦

示例

以下代码为了展示二进制的过程,所以很多计算并不符合实际使用时常会使用的方法,而是使用二进制的方式表示

using System;

class Program
{
    static void Main()
    {
        // 示例浮点数
        float num1 = 1.234567f;
        float num2 = 9.876543f;

        // 对阶
        int exponent1 = BitConverter.SingleToInt32Bits(num1) >> 23 & 0xFF;//单精度浮点数 右移23位 并与11111111 ,取得指数位8位
        int exponent2 = BitConverter.SingleToInt32Bits(num2) >> 23 & 0xFF;
        if (exponent1 > exponent2)
        {
            //计算 2 的 exponent1 - exponent2 次方,即右移的倍数。
            //将 num2 除以这个倍数,相当于将 num2 的尾数部分右移,使其指数部分与 num1 的指数部分相同。
            num2 = num2 / (float)Math.Pow(2, exponent1 - exponent2);
        }
        else
        {
            num1 = num1 / (float)Math.Pow(2, exponent2 - exponent1);
        }

        // 尾数相加
        float result = num1 + num2;

        // 规格化
        int exponentResult = BitConverter.SingleToInt32Bits(result) >> 23 & 0xFF;
        if (exponentResult > 127)
        {
            result = result / (float)Math.Pow(2, exponentResult - 127);
        }

        // 舍入
        result = (float)Math.Round(result, 6);
        
        // 将结果转换为二进制表示
        int resultBits = BitConverter.SingleToInt32Bits(result);
        
        // 溢出判断
        // 提取指数部分(第23到30位)
        int exponent = (result >> 23) & 0xFF;

        // 判断是否溢出
        if (exponent == 0xFF)
        {
            Console.WriteLine("运算结果发生溢出");
        }
        else
        {
            Console.WriteLine("运算结果: " + result);
        }
    }
}

2. 乘除法运算

乘除法相对简单:

  • 乘法: 尾数相乘,阶码相加。规格化、舍入、溢出判断。
  • 除法: 尾数相除,阶码相减。规格化、舍入、溢出判断。

示例

using System;

class FloatingPointOperations
{
    public static void Main()
    {
        // 示例浮点数
        float a = 1.5f;
        float b = 2.5f;

        // 将浮点数转换为二进制表示
        int aBits = BitConverter.SingleToInt32Bits(a);
        int bBits = BitConverter.SingleToInt32Bits(b);

        // 提取尾数和阶码
        int aMantissa = aBits & 0x007FFFFF;
        int bMantissa = bBits & 0x007FFFFF;
        int aExponent = (aBits >> 23) & 0xFF;
        int bExponent = (bBits >> 23) & 0xFF;

        // 添加隐含的1位
        aMantissa |= 0x00800000;
        bMantissa |= 0x00800000;

        // 乘法运算
        long productMantissa = (long)aMantissa * bMantissa;//尾数相乘,阶码相加。
        int productExponent = aExponent + bExponent - 127; 

        // 规格化
        if ((productMantissa & 0xFF000000) != 0)
        {
            productMantissa >>= 1;
            productExponent++;
        }

        // 舍入处理
        productMantissa = (productMantissa + 0x400000) >> 23;

        // 组合结果
        int productBits = (productExponent << 23) | ((int)productMantissa & 0x007FFFFF);
        float product = BitConverter.Int32BitsToSingle(productBits);

        Console.WriteLine($"乘法结果: {product}");

        // 除法运算
        long quotientMantissa = ((long)aMantissa << 23) / bMantissa;
        int quotientExponent = aExponent - bExponent + 127;

        // 规格化
        if ((quotientMantissa & 0x00800000) == 0)
        {
            quotientMantissa <<= 1;
            quotientExponent--;
        }

        // 舍入处理
        quotientMantissa = (quotientMantissa + 0x400000) >> 23;

        // 组合结果
        int quotientBits = (quotientExponent << 23) | ((int)quotientMantissa & 0x007FFFFF);
        float quotient = BitConverter.Int32BitsToSingle(quotientBits);

        Console.WriteLine($"除法结果: {quotient}");
    }
}

3. 浮点数比较

由于浮点数的精度问题,直接使用==进行比较通常是不可靠的。例如,以下代码可能不会输出“等于”:

这是因为f2 - 1.1的结果可能并不精确等于f1

using System;

public class FloatingPointOperations
{
    public static void Main(string[] args)
    {
        float f1 = 1.1f;
        float f2 = 2.2f;
        if (f2 - f1 == 1.1 ) {
            Console.WriteLine("等于");
        } 
        else {
            Console.WriteLine("不等于");
        }
        float epsilon = 0.0001f; // 定义一个很小的值作为误差范围
        if (Math.Abs(f2 - 1.1f - f1) < epsilon) {
            Console.WriteLine("等于");
        } 
        else {
            Console.WriteLine("不等于");
        }
    }
}

运行结果:

你会发现2.2 -1.1竟然不等于1.1,

不等于
等于

十进制数转IEEE754标准的浮点表示法 和C#Demo

转换步骤

  1. 符号位:如果数是正的,符号位为0;如果是负的,符号位为1。
  2. 指数部分:将指数部分转换为二进制,并加上偏移量(单精度偏移量为127,双精度偏移量为1023)。
  3. 尾数部分:将小数部分转换为二进制。

示例

假设我们要将十进制数 -5.75 转换为IEEE 754单精度浮点数:

  1. 符号位:负数,所以符号位为1。
  2. 整数部分:5的二进制表示是 101
  3. 小数部分:0.75的二进制表示是 0.11
  4. 组合:将整数和小数部分组合得到 101.11
  5. 规格化:将 101.11 规格化为 1.0111 x 2^2
  6. 指数部分:2 + 127 = 129,129的二进制表示是 10000001
  7. 尾数部分:01110000000000000000000。
    最终结果为:1 10000001 01110000000000000000000

C#代码示例

以下是一个将浮点数转换为字节数组,然后将其转换为二进制字符串,以便查看其在内存中的表示形式的C#代码示例:

using System;

class Program
{
    static void Main()
    {
        float number = -5.75f;
        byte[] bytes = BitConverter.GetBytes(number);
        Array.Reverse(bytes); // 将字节数组反转为大端序

        string binaryString = "";
        foreach (byte b in bytes)
        {
            binaryString += Convert.ToString(b, 2).PadLeft(8, '0');
        }

        Console.WriteLine($"十进制数: {number}");
        Console.WriteLine($"IEEE 754表示法: {binaryString}");
    }
}
十进制数: -5.75
IEEE 754表示法: 11000000101110000000000000000000

这个代码示例使用 BitConverter.GetBytes 方法将浮点数转换为字节数组,然后将每个字节转换为二进制字符串并组合起来。

浮点数运算在编程中的注意事项和技巧

1. 浮点数在C#中的表现

C#中的浮点数

在C#中,常用的浮点数类型有floatdoublefloat为单精度浮点数,double为双精度浮点数。

float singlePrecision = 3.14f;
double doublePrecision = 3.14;

科学计数法表示

在C#中,可以使用科学计数法表示浮点数:

double scientificNotation = 1.23e4; // 表示 1.23 × 10^4

2. 精度问题

  1. 单精度和双精度float为单精度浮点数,double为双精度浮点数。双精度浮点数具有更高的精度和更大的表示范围。在选择浮点数类型时,需要根据具体需求选择合适的类型
  2. 舍入误差:浮点数由于其有限的位数表示,可能会出现舍入误差。
  3. 累积误差:多次浮点数运算后,舍入误差会累积,导致结果不准确。
  4. 避免不必要的运算:尽量减少不必要的浮点数运算,以减少误差累积。例如,可以将常量计算提前,避免在循环中重复计算。
  5. 比较浮点数:直接比较浮点数可能会因为精度问题导致错误结果。建议使用一个小的容差值(epsilon)进行比较。
    • 避免直接比较:使用容差值进行比较。
  6. 避免浮点数作为循环变量:由于浮点数的精度问题,使用浮点数作为循环变量可能会导致意外的结果。尽量使用整数作为循环变量
  7. 上溢和下溢:浮点数运算结果超出其表示范围时,会发生上溢(接近无穷大)或下溢(接近零)。
    注意数据范围
  8. 使用高精度库:使用高精度库
  9. 测试和验证
    - 单元测试:编写单元测试验证浮点数运算的正确性。
    - 边界测试:测试浮点数的边界情况,如最大值、最小值、零等。

总结

浮点数是计算机中表示实数的重要数据类型,是许多科学和工程计算的关键工具。

它们遵循IEEE 754标准,由符号位、指数位和尾数位组成。

浮点数具有有限的精度,可能在运算过程中产生舍入误差。

参考资料

  1. IEEE 754 浮点数转换 - 锤子在线工具 (toolhelper.cn)
  2. IEEE 754标准
  3. 浮点数运算的精度问题
  4. 浮点型数据规范以及规格化与非规格化数据(更新:原文疑问已解)_非规格化浮点数-CSDN博客
  5. Lecture 04 Floating Point_哔哩哔哩_bilibili

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

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

相关文章

商务人士必备的精准翻译工具盘点

网易翻译是一款我外出游玩时候必备的翻译工具&#xff0c;最近没出去玩但是有更多的翻译需求了&#xff0c;为了方便在电脑上的操作我也找了不少翻译工具&#xff0c;这次一起分享给大家&#xff0c;看看哪款更得你的眼缘。 1.福昕在线翻译 链接直达&#xff1a;https://fany…

集群聊天服务器项目【C++】(六)MySql数据库

前面已经介绍了网络模块和业务模块&#xff0c;本章介绍数据模块&#xff0c;同样保持模块解耦的特性&#xff0c;即业务模块不能出现数据模块内容&#xff0c;如出现SQL语句&#xff0c;接下来看看怎么实现的。 1.环境安装 第一章已经介绍了MySql安装&#xff0c;但注意需要…

基于R语言的统计分析基础:使用SQL语句操作数据集

在使用R语言数据分析时&#xff0c;可以融合SQL语言使数据聚集操作更加便利&#xff0c;同时也可以增加对SQL语句的熟悉。借助sqldf、DBI、RSDLite等包&#xff0c;可以在R环境中直接运用SQL语句&#xff0c;轻松实现数据的分组统计、汇总分析&#xff0c;SQL的强大查询能力简化…

MTC完成右臂抓取放置任务\\放置姿态设置

#include "mtc_tutorial/mtc_glass_bottle.hpp" static const rclcpp::Logger LOGGER rclcpp::get_logger("mtc_glass_right"); // 获取节点基础接口的实现 rclcpp::node_interfaces::NodeBaseInterface::SharedPtr MTCTaskNode_Right::getNodeBaseInterf…

(c++)字符串相加(真没想到字符串还有相加运算)

#include<iostream> #include<string> using namespace std;int main() {string ch1 "你好";string ch2 "再见";string ch3 ch1 ch2;cout << ch3 << endl;system("pause");return 0; } 运行结果&#xff1a; 学了c…

FreeRTOS学习——链表list

FreeRTOS学习——链表&#xff08;列表&#xff09;list&#xff0c;仅用于记录自己阅读与学习源码 FreeRTOS Kernel V10.5.1 参考大佬的好文章&#xff1a; freertos内核原理 Day1(链表) FreeRTOS-链表的源码解析 *list_t只能存储指向list_item_t的指针。每个list_item_t都…

【UE5 C++课程系列笔记】01——Visual Studio环境安装

1. 进入Visual Studio 官网&#xff0c;点击下载 下载社区版即可 下载后点击应用程序开始安装 2. 在“工作负荷”中&#xff0c;勾选如下选项 在“单个组件”中&#xff0c;勾选如下选项&#xff1a; 3. 等待下载安装 4. 安装好后&#xff0c;点击“继续但无需代码” 选择“工具…

《python语言程序设计》2018版第8章17题point类设计一个名为point的类

TypeError: point_class.dis_m() missing 1 required positional argument: ‘y2’ 这段代码为什么出错 一个又一个错误 终于摸到点头绪 #distance方法 我做的叫get_dis_m def get_dis_m(self):a_m self.__x1 - self.__x2b_m self.__y1 - self.__y2return (pow(a_m, 2) po…

k8s中的存储

目录 一 configmap 1.1 configmap的功能 1.2 configmap的使用场景 1.3 configmap创建方式 1.3.1 字面值创建 1.3.2 通过文件创建 1.3.3 通过目录创建 1.3.4 通过yaml文件创建 1.3.5 configmap的使用方式 1.3.5.1 使用configmap填充环境变量 1.3.5.2 通过数据卷使用c…

《程序猿之设计模式实战 · 观察者模式》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

AJAX 进阶 day4

目录 1.同步代码和异步代码 2.回调函数地狱和 Promise 链式调用 2.1 回调函数地狱 2.2 Promise - 链式调用 2.3 Promise 链式应用 3.async 和 await 使用 3.1 async函数和await 3.2 async函数和await_捕获错误 4.事件循环-EventLoop 4.1 事件循环 4.2 宏任务与微任务…

R语言统计分析——散点图1(常规图)

参考资料&#xff1a;R语言实战【第2版】 R语言中创建散点图的基础函数是plot(x,y)&#xff0c;其中&#xff0c;x和y是数值型向量&#xff0c;代表着图形中的&#xff08;x,y&#xff09;坐标点。 attach(mtcars) plot(wt,mpg,main"Basic Scatter plot of MPG vs. Weigh…

数据结构(Day14)

一、学习内容 结构体 概念 引入&#xff1a;定义整数赋值为10 int a10; 定义小数赋值为3.14 float b3.14; 定义5个整数并赋值 int arr[5] {1 , 2 , 3 , 4 ,5}; 定义一个学生并赋值学号姓名成绩 定义一个雪糕并赋值名称产地单价 问题&#xff1a;没有学生、雪糕 数据类型 解决&…

Text2vec -文本转向量

文章目录 一、关于 Text2vec1、Text2vec 是什么2、Features3、Demo4、News5、Evaluation英文匹配数据集的评测结果&#xff1a;中文匹配数据集的评测结果&#xff1a; 6、Release Models 二、Install三、使用1、文本向量表征1.2 Usage (HuggingFace Transformers)1.3 Usage (se…

★ C++进阶篇 ★ 多态

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将继续和大家一起学习C进阶篇第一章----多态 ~ ❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️ 澄岚主页&#xff1a;椎名澄嵐-CSDN博客 C基础篇专栏&#xff1a;★ C基础篇 ★_椎名澄嵐的博客-CSDN博客 …

2024/9/16 英语每日一段

Stark argues that, in their gummies, at least,“The total sugar in a serving is less than in half a cherry.”Of course, cherries also provide fibre, vitamin C, and antioxidants--and 14 of them would count as one of your five-a-day. Artificial sweeteners to…

Ubuntu24.04部署docker

1、更新软件 apt update 2、安装curl apt install apt-transport-https curl 3、导入阿里云GPG秘钥 curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg 4、添加Docker阿里云仓库到Ubuntu 24.04的…

使用 release key 对 LineageOS 进行编译和签名

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 为什么需要使用 release key test-key 是一个公开的、众所周知的开发测试密钥&#xff0c;广泛用于测试阶段。这意味着任何人都可以获取这个密钥&#xff0c;…

详解HTTP/HTTPS协议

HTTP HTTP协议全名为超文本传输协议。HTTP协议是应用层协议&#xff0c;其传输层协议采用TCP协议。 请求—响应模型 HTTP协议采用请求-响应模型&#xff0c;通常由客户端发起请求由服务端完成响应。资源存储在服务端&#xff0c;客户端通过请求服务端获取资源。 认识URL 当…

jacoco生成单元测试覆盖率报告

前言 单元测试是日常编写代码中常用的&#xff0c;用于测试业务逻辑的一种方式&#xff0c;单元测试的覆盖率可以用来衡量我们的业务代码经过测试覆盖的比例。 目前市场上开源的单元测试覆盖率的java插件&#xff0c;主要有Emma&#xff0c;Cobertura&#xff0c;Jacoco。具体…