如果直接看浮点数表示法有点费力或者不好理解,不妨复习一下科学计数法。毕竟我们一直接触十进制,从十进制的角度可能更好理解其特性。
目录
- 科学计数法 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到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 20∗103和 2 ∗ 1 0 4 2*10^4 2∗104,或者等等其他表示都可以表示为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×10−3。
- 应用场景
科学计数法在许多领域都有应用,包括工程学、物理学、化学和计算机科学。
在计算机编程中,科学计数法常用于浮点数的存储和计算。 - 优点
- 简洁:可以简化表示非常大或非常小的数,避免写出一长串数字。
- 方便:在科学计算中,使用科学计数法可以简化运算过程。
- 使用科学记数法,一个数的数量级、精确度和数值都较容易看出,
例如: - 一个质子质量的数值为︰0.00000000000000000000000167262158。科学记数法的形式︰ 1.67262158 × 1 0 − 24 1.67262158×10^−24 1.67262158×10−24
- 若以公斤为表示单位,则木星的质量值约为:1898130000000000000000000000科学记数法的形式︰ 1.89813 × 1 0 27 1.89813×10^{27} 1.89813×1027
2. 精确度与有效数字
- 精确度(
Precision
)指的是数值表示的准确性和细节程度。 - 有效数字(
Significant Figures
)是指在一个数中,从第一个非零数字起,直到末尾数字止的所有数字。表示了数据的精度和可靠性。 - 科学记数法中,尾数也被称作有效数。
科学计数法的精确度由有效数字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 - 有效数字的规则
- 所有非零数字都是有效的:例如,123.45有五位有效数字(1、2、3、4、5)。
- 两个非零数字之间的零也是有效的:例如,101.1203有七位有效数字(1、0、1、1、2、0、3)。
- 前缀零始终无效:例如,0.00052只有两位有效数字(5和2)。
- 包含小数点的数中,结尾的零是有效的:例如,12.2300有六位有效数字(1、2、2、3、0、0)。
- 不包含小数点的数,结尾的零可能有效也可能无效,取决于上下文。但在科学计数法中,有效数字便能够判别。
例如,1300的有效数字可能是2位、3位或4位,具体取决于上下文。
科学记数法可以帮助明确有效数字。例如,0.000122300可以写成 1.22300 × 1 0 − 4 1.22300 \times 10^-4 1.22300×10−4,这样就明确了有六位有效数字
参考:http://www.shuxueji.com/w/2623
3. 转换为科学计数法
要将一个数转换为科学计数法,需要执行以下步骤:
- 如果数字是整数并且
大于或等于``10
,则将数字的小数点向左移动
,直到只有一个非零数字位于小数点左侧。 - 如果数字是整数并且
小于``1
,则将数字的小数点向右移动
,直到只有一个非零数字位于小数点左侧。 - 如果数字是小数,则将小数点向左或向右移动,使得小数点
左侧
只有一个非零数字。 - 记录
小数点移动的位数
,这个位数就是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×10−4。
4. 运算
科学计数法的运算规则如下:
- 加法和减法:
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) - 乘法:
( 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 - 除法:
当两个数都以 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×10m−n
例如:
−
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×103−6×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. 浮点数的主要优点是能够表示非常大或非常小的数值,而且可以进行高精度的计算。
缺点:
1. 由于精度有限,浮点数可能产生舍入误差。
2. 浮点数的运算通常比整数运算慢,因为它们涉及到更多的处理步骤。
3. 浮点数的比较运算可能会产生意外的结果,因为微小的差异可能被解释为相等或不等,所以在编程的过程中需要格外注意
浮点数的表示
不同精度的浮点数
IEEE754半精度浮点数:16位,符号1位,指数5位,尾数10位
IEEE754单精度浮点数:32位,符号1位,指数8位,尾数23位
IEEE754双精度浮点数:64位,符号1位,指数11位,尾数52位
表示方式
- 从浮点数的组成成分来看
浮点数的实际值,等于符号位
乘以偏移指数位
(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 - 更细化描述每个成分的具体含义
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=Rk−1−1
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=Rk−1−1=28−1−1=127
3. 偏移表示法 - 计算方式
E
=
E
x
p
o
n
e
n
t
−
b
i
a
s
E = Exponent - bias
E=Exponent−bias
假设我们有一个单精度浮点数,其指数部分的二进制表示为 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=Exponent−bias=27+20−(28−1−1)=2
因此,这个浮点数的指数部分实际表示的是 2。
4. 指数位取值范围:-126到127。
(对于单精度浮点数(32 位),指数位占用 8 位)
- 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
- 8位指数位的指数部分最小值 0 0 0
- 指数实际值为0和255被用作特殊用途
- 指数值 0:表示非规格化数(也称为次正规数)或零。
- 指数值 255:表示无穷大(Infinity)或非数字(NaN)。
- 从图中可以看出,指数位置全为1,此时指数为的值为255,代表 Infinity或**-Infinity**
-
正无穷大
-
负无穷大
-
指数部分全为1,尾数部分非零:NaN (Not a Number)
-
- 8位指数位的指数部分可表达的最大值为
127
8 位指数位的指数部分最大值 − 1 − b i a s = 255 − 1 − 127 = 127 8位指数位的指数部分最大值 - 1 - bias = 255 -1 - 127 = 127 8位指数位的指数部分最大值−1−bias=255−1−127=127 - 8位指数位的指数部分最小值为
-126
8 位指数位的指数部分最小值 + 1 − b i a s = 0 + 1 − 127 = 126 8位指数位的指数部分最小值 + 1 - bias = 0 + 1 - 127 = 126 8位指数位的指数部分最小值+1−bias=0+1−127=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标准中:
- 非规格化浮点数:
当阶码全为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.尾数×R−126
可以看出此时可以表示0和非常接近于0的数据。 - 规格化浮点数与隐藏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×RExp−bias 或者写为 ( − 1 ) 符号位 × 1. 尾数 × R 偏移指数 (-1)^{符号位} \times 1.尾数 \times R ^{偏移指数} (−1)符号位×1.尾数×R偏移指数
所以当尾码为10000000000时,其值为 2 − 1 = 0.5 2^{-1} = 0.5 2−1=0.5,但实际计算时,尾数需要加 2 0 2^{0} 20,也就是 2 0 + 2 − 1 = 1.5 2^{0}+2^{-1} = 1.5 20+2−1=1.5。
规格化到非规格化的平滑过渡
根据尾码和阶码的介绍,可以总结出以下会浮点数内容:
- 当阶码全为0时,此时取消隐藏的1规则,此时浮点数表示接近于0和0
- 当阶码不全为0,也不全为1时,此时有隐藏的1规则,为规格化浮点数
- 当阶码全为1,尾码全为0,表示无穷大
- 当阶码全为1,尾码不全为0,表示NaN
那么非规格化数据和规格化数据之间数据变化是否会出现跳跃不连续的现象?
我们可以使用IEEE-754 Floating Point Converter (h-schmidt.net)提供的工具查看:
- 当阶码全为0,尾码最大表示为: 1.1754942 × 1 0 − 38 1.1754942 \times 10^{-38} 1.1754942×10−38
- 当阶码最低位为1,尾码全为0时表示为:
1.1754944
×
1
0
−
38
1.1754944 \times 10^{-38}
1.1754944×10−38
可以看出他们之间是连续的。
从数学的角度:
即:非规格化最大值 ( 1 − 2 − 23 ) × 2 − 126 (1- 2 ^{-23}) \times 2 ^{-126} (1−2−23)×2−126和规格化最小值 1.0 × 2 − 126 1.0 \times 2 ^{-126} 1.0×2−126,计算一下他们之间差多少,在和非规格化数据中最小值对比 2 − 23 × 2 − 126 2 ^{-23} \times 2 ^ {-126} 2−23×2−126,观察差值是否相等,来判断过度是否平滑:
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×2−126−(1−2−23)×2−126
= 2 − 23 × 2 − 126 2 ^ {-23} \times 2 ^ {-126} 2−23×2−126
综上得出规格化最小值和非规格化最大值,刚好差了一个 2 − 23 × 2 − 126 2 ^ {-23} \times 2 ^ {-126} 2−23×2−126,刚好是浮点数表示中最小的增量单位之一。所以说非规格化到规格化数据的过度是平滑的。
浮点数规格化
浮点数规格化是指将浮点数的尾数调整为符合特定格式的过程,以确保数值表示的精度和一致性。同十进制类似,阶码(指数位)要根据尾数的调整进行相应的增减
-
规格化的基本操作包括左规和右规,用于确保浮点数的尾数(或称为有效数字)符合一定的范围。
-
左规:当浮点数运算结果为非规格化时,将尾数左移一位,阶码减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,所以为非规格化浮点数数,需要进行左规处理。- 0.5对应二进制位0.1
- 左移一位:1 ,对应十进制为1
- 阶码-1:3-1 = 2
- 左规后的表示方法为 ( − 1 ) 0 × 1 × 2 2 (-1)^{0} \times 1 \times 2^{2} (−1)0×1×22
-
右规:当浮点数运算结果尾数出现溢出时,将尾数右移一位,阶码加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,所以为非规格化浮点数数,需要进行右规处理。- 2.5对应二进制位10.1
- 左移一位:1.01 对应十进制为5
- 阶码+1:3+1 = 4
- 左规后的表示方法为 ( − 1 ) 0 × 1.25 × 2 4 (-1)^{0} \times 1.25 \times 2^{4} (−1)0×1.25×24
-
优点
规格化的主要目的是使浮点数在计算机中更有效地表示和处理,提高数值的精度和运算的准确性。
- 下图为计算过程,来自IEEE 754 浮点数转换 - 锤子在线工具 (toolhelper.cn)
- 你也可以使用https://www.h-schmidt.net/FloatConverter/IEEE754.html提供的工具来观察二进制的实际存储
- 你也可以使用https://www.h-schmidt.net/FloatConverter/IEEE754.html提供的工具来观察二进制的实际存储
精度损失
-
浮点数的表示范围和精度有限,某些十进制小数无法精确表示。
-
舍入误差:由于尾数位数有限,某些数值无法精确表示
例如,将0.2转换为二进制表示时,结果是一个无限循环小数。具体步骤如下:
- 乘以2:0.2 × 2 = 0.4(整数部分为0)
- 取小数部分继续乘以2: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.001100110011…
因此,0.2在二进制中表示为0.001100110011…,这是一个无限循环的二进制小数
这种无限循环的小数在计算机中只能被截断,导致精度损失。
累积误差:多次运算后,舍入误差可能会累积,导致结果不准确
下溢和上溢:运算结果超出浮点数的表示范围时,会发生下溢(接近零)或上溢(接近无穷大)
-
浮点数的有效数、误差、精度、与范围
误差
误差分为绝对误差和相对误差:
- 绝对误差:测量值与真实值之间的差异。
绝对误差 = ∣ 测量值 − 真实值 ∣ \text{绝对误差} = |\text{测量值} - \text{真实值}| 绝对误差=∣测量值−真实值∣
-
相对误差:绝对误差与测量值的比值,通常用百分比表示。
$ 相对误差 = \frac {绝对误差} {真实值} \times 100 % $
精度
对于浮点数来说,精度也与尾数(有效数字)的位数有关。精度越高,表示的数值越准确,误差越小。
尾数、有效数字与精度的关系
浮点数的精度和范围取决于尾数和指数的位数。
当指数位和尾数位的位数之后一定的条件下:
- 更多的指数位意味着更大的范围,但精度会降低;
- 更多的尾数位意味着更高的精度,但范围会减小。
- 有效率数字越少,表示的数值越粗略,误差越大,精度越低。
- 有效数字越少,表示的数值越粗略:
- 有效数字越多,表示的数值越精确。例如,数值 3.14159 比 3.14 更精确,因为前者有更多的有效数字。
- 当有效数字减少时,数值的表示变得更粗略,细节丢失,误差增加。
- 有效数字越少,舍入误差越大:
- 当有效数字减少时,数值需要进行舍入。
- 舍入误差会累积,导致最终结果的准确性降低。
- eg
假设:- 数值 A:3.14159(有 6 位有效数字)
- 数值 B:3.14(有 3 位有效数字)
- 数值 C:3.1415926(有 8 位有效数字)
数值 A 比数值 B 更精确,因为它有更多的有效数字。将数值 C 舍入为数值 A和B 都会引入舍入误差,但是A相对与B误差更小,也就是精确度更高。
- 有效数字越少,表示的数值越粗略:
- 有效数字越少,相对误差越大:
- 相对误差是绝对误差与测量值的比值。当有效数字减少时,相对误差增加。
- 例如,测量值为 10.0 和 1.0,假设绝对误差均为 0.1。前者的相对误差为 1%,后者的相对误差为 10%
浮点数的运算与比较
1. 加减法运算
浮点数加减法运算主要包括以下步骤:
- 对阶:将两个浮点数的指数部分进行比较,将较小指数的尾数部分进行右移,使其指数部分与较大指数相同。
- 尾数相加/减:将两个浮点数的尾数部分进行相加或相减。
- 规格化:将相加/减后的尾数进行规格化处理,使其满足浮点数表示的要求。
- 舍入:根据舍入规则,对尾数部分进行舍入处理。
- 溢出判断:判断运算结果是否发生溢出,如果发生溢出,需要进行特殊处理。
如果此计算不好理解的话,代入十进制会很好理解哦
示例
以下代码为了展示二进制的过程,所以很多计算并不符合实际使用时常会使用的方法,而是使用二进制的方式表示
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
转换步骤
- 符号位:如果数是正的,符号位为0;如果是负的,符号位为1。
- 指数部分:将指数部分转换为二进制,并加上偏移量(单精度偏移量为127,双精度偏移量为1023)。
- 尾数部分:将小数部分转换为二进制。
示例
假设我们要将十进制数 -5.75
转换为IEEE 754单精度浮点数:
- 符号位:负数,所以符号位为1。
- 整数部分:5的二进制表示是
101
。 - 小数部分:0.75的二进制表示是
0.11
。 - 组合:将整数和小数部分组合得到
101.11
。 - 规格化:将
101.11
规格化为1.0111 x 2^2
。 - 指数部分:2 + 127 = 129,129的二进制表示是
10000001
。 - 尾数部分: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#中,常用的浮点数类型有float
和double
。float
为单精度浮点数,double
为双精度浮点数。
float singlePrecision = 3.14f;
double doublePrecision = 3.14;
科学计数法表示
在C#中,可以使用科学计数法表示浮点数:
double scientificNotation = 1.23e4; // 表示 1.23 × 10^4
2. 精度问题
- 单精度和双精度:
float
为单精度浮点数,double
为双精度浮点数。双精度浮点数具有更高的精度和更大的表示范围。在选择浮点数类型时,需要根据具体需求选择合适的类型 - 舍入误差:浮点数由于其有限的位数表示,可能会出现舍入误差。
- 累积误差:多次浮点数运算后,舍入误差会累积,导致结果不准确。
- 避免不必要的运算:尽量减少不必要的浮点数运算,以减少误差累积。例如,可以将常量计算提前,避免在循环中重复计算。
- 比较浮点数:直接比较浮点数可能会因为精度问题导致错误结果。建议使用一个小的容差值(epsilon)进行比较。
- 避免直接比较:使用容差值进行比较。
- 避免浮点数作为循环变量:由于浮点数的精度问题,使用浮点数作为循环变量可能会导致意外的结果。尽量使用整数作为循环变量
- 上溢和下溢:浮点数运算结果超出其表示范围时,会发生上溢(接近无穷大)或下溢(接近零)。
注意数据范围 - 使用高精度库:使用高精度库
- 测试和验证
- 单元测试:编写单元测试验证浮点数运算的正确性。
- 边界测试:测试浮点数的边界情况,如最大值、最小值、零等。
总结
浮点数是计算机中表示实数的重要数据类型,是许多科学和工程计算的关键工具。
它们遵循IEEE 754标准,由符号位、指数位和尾数位组成。
浮点数具有有限的精度,可能在运算过程中产生舍入误差。
参考资料
- IEEE 754 浮点数转换 - 锤子在线工具 (toolhelper.cn)
- IEEE 754标准
- 浮点数运算的精度问题
- 浮点型数据规范以及规格化与非规格化数据(更新:原文疑问已解)_非规格化浮点数-CSDN博客
- Lecture 04 Floating Point_哔哩哔哩_bilibili