小数在C语言中称为浮点数(floating-point number),有float、double和long double三种类型。相比于整数,浮点数能表示更广范围的、更精确的数,常用于数学和金融领域。
浮点数的数值表示:1.231
、1.5E16
。
C语言的浮点数遵循IEEE 754标准,其规格化的表示为:
( − 1 ) s i g n × 1. m a n t i s s a × 2 e x p o n e n t − o f f s e t = ( − 1 ) s i g n × ( 1 + m a n t i s s a 2 23 ) × 2 e x p o n e n t − 127 \mathbf{(-1)^{sign} \times 1.mantissa \times 2^{exponent - offset} = (-1)^{sign} \times (1+\frac{mantissa}{2^{23}}) \times 2^{exponent - 127}} (−1)sign×1.mantissa×2exponent−offset=(−1)sign×(1+223mantissa)×2exponent−127
m a n t i s s a 2 23 \mathbf{\frac{mantissa}{2^{23}}} 223mantissa表示将mantissa的小数点移动至最前方。
C语言中的float可以叫做单精度浮点数,存储长度为4 byte。
系统通常使用32位bit存储单精度浮点数,其中,1位表示符号位sign,8位表示指数或阶码exponent的值及其符号,23位表示尾数mantissa或称为有效数字significand。符号位控制正负;指数控制了小数点的最后位置,决定了表示范围;尾数控制有效数字,也就是表示精度。指数使用移码1表示,偏置量为127;尾数使用原码表示,隐藏了1位整数部分1。
小数的有效数字是指从左边第一个非零数字开始,一直到最后一个数字。例如,0.01230的有效数字位数为4,1.02的有效数字位数为3。
C标准规定float类型必须至少能表示6位有效数字,并且至少提供 1 0 − 37 ∼ 1 0 37 10^{-37} \sim 10^{37} 10−37∼1037这个范围的数。
那float类型的实际精度是多少?
大部分的验证法如下:
由于 l o g 10 2 23 ≈ 6.92 log_{10}{2^{23}} \approx 6.92 log10223≈6.92,得出精度为6-7位。
较少的验证法:
我们说的有效数字是在十进制情况下。假设mantissa达到最小,即最后一位为1,值为 2 − 23 ≈ 0.000000119 2^{-23} \approx 0.000000119 2−23≈0.000000119,第二小的为 2 − 22 ≈ 0.000000238 2^{-22} \approx 0.000000238 2−22≈0.000000238,其次为 2 − 21 ≈ 0.000000476 2^{-21} \approx 0.000000476 2−21≈0.000000476,加上隐藏位1,这三个数之间的数是float类型无法进行表示的,也就是说,到了精度为8的时候,是无法表示所有数在内的,这只是部分精确。得出精度为7位。
本文考虑在计算有效位数的时候,加上对阶码的分析:
假设小数部分为 1. x x ⋅ ⋅ ⋅ x 1.xx···x 1.xx⋅⋅⋅x,计算阶码部分对它的影响后,变为 0.00 ⋅ ⋅ ⋅ 1 x x ⋅ ⋅ ⋅ x 0.00···1xx···x 0.00⋅⋅⋅1xx⋅⋅⋅x,最小正数为 2 − ( 126 − 23 ) = 0.00 ⋅ ⋅ ⋅ 100 ⋅ ⋅ ⋅ 0 ≈ 9.8607613 e − 32 2^{-(126-23)} = 0.00···100···0 \approx 9.8607613e-32 2−(126−23)=0.00⋅⋅⋅100⋅⋅⋅0≈9.8607613e−32,其次为 2 − 103 + 2 − 126 ≈ 9.8607624 e − 32 2^{-103}+2^{-126} \approx 9.8607624e-32 2−103+2−126≈9.8607624e−32。浮点数越小,变化幅度越小,密度越大。这样看能达到的最少有效位数是6。
然后是float类型的实际范围。当尾数和指数达到最大值时,达到float范围边界。由于指数为-127、128时有特殊用途2,所以指数最大为127,对应移码为1111 1101;尾数全1时最大。故 ± 1.111 ⋅ ⋅ ⋅ 1 × 2 127 ≈ ± 3.4 E 38 \pm 1.111···1 \times 2^{127} \approx \pm 3.4 E 38 ±1.111⋅⋅⋅1×2127≈±3.4E38为float范围边界。
C语言的double类型是双精度浮点型,要求的最小范围与float相同,但标准扩展了最小有效数字位数到了10位。并且,增加了32位额外bit,既可以补充指数,也可以补充尾数,或者两者皆有之。无论哪种做法,至少产生13位有效数字。根据IEEE 754标准,双精度浮点型使用64位来存储,其中有1位用于符号位,11位用于指数部分,剩下的52位用于尾数部分。double的实际精度为16,验证方式同上。double的表示范围为 − 1.79 E308 ∼ + 1.79 E308 -1.79\text{E308} \sim +1.79\text{E308} −1.79E308∼+1.79E308。
疑问:为什么无论哪种做法,至少产生13位有效数字?
一般来说,CPU处理单精度浮点数的速度比处理双精度浮点数快。
为了更高精度,C标准还提供了long double类型。其具体实现取决于系统,但是至少会比double更加精确。