浮点数,二进制浮点数,IEEE 754 二进制浮点数介绍
浮点数
浮点数是一种小数表示方式,其中“浮点”的含义为小数点是浮动变化的,这通常对应着指数的改变,因为浮点数存储数字科学计数法的相关信息,包括指数,尾数等条目。
为何使用浮点数?
总的来说,浮点数是一种平衡方案,兼顾了扩大数字描述范围和节省存储空间,因为通过指数可以表示更趋向于0
或无穷的数字,而其自身占用的空间又很小。
假设浮点数采用了十进制,那么描述0.000000000001234
小数点后面大量的0
,只需存储其科学计数法1.234×10⁻¹²
中的指数-12
,而不是为每个0
分配真实的存储空间。
二进制浮点数
目前的浮点数硬件普遍采用二进制,这是由于 IEEE 754-1985 标准的流行,该标准仅规定了浮点数的二进制存储格式。而在 IEEE 754-1985 出现之前,浮点数的格式并不统一,有些硬件支持十进制甚至三进制格式的浮点数。
虽然后来的 IEEE 754-2008 加入了浮点数的十进制存储格式,但改变不会轻易发生,因为这对于硬件厂商意味着风险。
不同进制浮点数所存储的指数和尾数
理所当然的,对于不同进制的浮点数,其存储的指数和尾数信息,应来自于对应进制的科学计数法,否则会给计算带来不必要的麻烦。
比如,将小数12.3
转换为采用十进制的浮点数,如果确定使用科学计数法1.23×10¹
,那么指数应该可以换算为十进制的1
,尾数应该可以换算为十进制的1.23
。
二进制浮点数不准确
事实上因为进制的转换问题,采用二进制的浮点数具有不准确性,你可以查看为何二进制浮点数不准确?IEEE 754 二进制浮点数舍入规则一节了解相关信息。
IEEE 754 二进制浮点数
同样是依赖科学计数法和二进制格式,不同的设计方案可能导致浮点数的表现大相径庭,IEEE 754 定义了二进制浮点数符号,指数,尾数的格式,他们依次排列并占用 4,8 或更多个字节的存储空间。
IEEE 754 二进制浮点数符号格式
符号占用 1 个 bit 的存储空间,为1
时表示浮点数是一个负数,为0
时表示浮点数是一个非负数。
IEEE 754 二进制浮点数指数格式
指数在 4/8 字节浮点数中占用 8/11 个 bit 的存储空间,指数在存储之前需要计算为指数编码值,计算方法为科学计数法中的指数真实值加上指数偏移值。指数偏移值的计算公式是2ⁿ⁻¹-1
,n
为指数占用的 bit 个数,4/8 字节浮点数的指数偏移值为127
/1023
。
使用指数编码值是为了方便表示指数为负的情况,假设在 4 字节的浮点数中,指数存储的二进制内容为01111011
(对应十进制数字为123
),那么指数编码值就是123
,指数真实值就是123-127
,即-4
。
此外,除了参与正常运算,指数编码值还用于判断特殊值或特殊格式是否成立。
IEEE 754 二进制浮点数尾数格式
尾数在 4/8 字节浮点数中占用 23/52 个 bit 的存储空间,当尾数不等价于0
并且指数编码值对应的十进制数值大于等于0
小于2ⁿ⁻¹-1
(其中n
表示指数在存储空间中占用的 bit 个数)时,尾数最高位将被隐藏,不会真正存储在浮点数中。这种做法使得尾数节省出 1 个 bit 的存储空间,而且不会影响运算,因为根据指数编码值可以得知被隐藏的尾数最高位是0
还是1
。
十进制的0.5
对应的二进制科学计数法为1×10⁻¹
(其中的数位0
,1
均为二进制,10
对应了十进制的2
),尾数最高位1
被省去后,其在 4 字节浮点数中的存储内容将是00000000000000000000000
(仅尾数部分)。
IEEE 754 二进制正规和次正规浮点数
如果指数编码值对应的十进制数值大于0
小于2ⁿ⁻¹-1
(其中n
表示指数在存储空间中占用的 bit 个数),则浮点数属于正规浮点数,被隐藏的尾数最高位为1
。正规浮点数是最为常见的,其二进制科学计数法中的尾数最高位总是为1
。
如果指数编码值等价于0
,尾数存储的二进制内容不等价于0
,则浮点数属于次正规浮点数,被隐藏的尾数最高位为0
,指数真实值由0-(2ⁿ⁻¹-1)
改为0-(2ⁿ⁻¹-1)+1
(其中n
表示指数在存储空间中占用的 bit 个数)。
IEEE 754 二进制次正规浮点数的作用是什么?
次正规浮点数用于表示比正规浮点数更趋近于0
的小数,因为其隐藏的尾数最高位为0
,对应的二进制科学计数法尾数为纯小数而非混合小数,等同于变相增加了指数范围,而这一范围是正规浮点数指数无法到达的。
在次正规浮点数对应的二进制科学计数法中,出现在尾数小数点后以及尾数第一个1
之前的0
越多,指数变相增加的范围就越大。为了方便说明,我们以十进制科学计数法0.00003×10⁻³⁸
为例,他等同于3×10⁻⁴³
,指数变相由-38
成为了-43
。
IEEE 754 二进制浮点数特殊值的表示
当指数编码值和尾数存储的二进制内容均等价于0
时,浮点数表示数字0
。
当指数编码值对应的十进制数值等于2ⁿ⁻¹-1
时(其中n
表示指数在存储空间中占用的 bit 个数),则将根据尾数决定浮点数表示的特殊值,尾数存储的二进制内容等价于0
表示无穷,不等价于0
表示非数字NaN
。
编程语言中的二进制浮点数类型
几乎所有的编程语言都实现了 IEEE 754 标准的二进制浮点数,比如,C# 中的float
和double
类型,Python 中的float
类。这样做的好处是可以利用同样采用 IEEE 754 二进制格式标准的浮点数硬件,随着浮点数硬件性能的提升,程序运行的效率会得到改善。
运算规则不适用于浮点数类型
浮点数使用有限的存储空间来表示极大范围的数字,因此数字中的一些数位可能会被忽略。从这个角度讲,浮点数是一种近似值,他无法保证某些运算规则的正确性。比如,当x
与y
不相等时,a+x
与a+y
未必不相等,因为x
,y
可能在加法运算后被忽略。
在 C# 中,浮点数0.00000000000000000001f
不等于浮点数0.00000000000000000002f
,而1.0f+0.00000000000000000001f
却可以等于1.0f+0.00000000000000000002f
,因为小数部分在加法运算后被忽略。
// 不相等,输出 False
Console.WriteLine(0.00000000000000000001f == 0.00000000000000000002f);
// 相等,输出 True
Console.WriteLine(1.0f + 0.00000000000000000001f == 1.0f + 0.00000000000000000002f);
False
True
二进制浮点数的近似性与不准确无关
二进制浮点数不准确的根源是进制的转换,而近似则是其本身的设计理念,如果提供足够的存储空间,近似的情况可能会消失。
编程语言中的十进制浮点数类型
在编程语言中,并非所有的浮点数类型都采用了 IEEE 754 的二进制格式,比如,C/C++ 编译器 GCC 支持的_Decimal32
,_Decimal64
,_Decimal128
,他们是十进制格式的浮点数类型。十进制浮点数类型没有二进制浮点数类型不准确的问题,因此可用于精确计算,比如,统计银行存款。
编程语言中十进制浮点数类型的运算效率
虽然十进制浮点数类型没有了不准确的问题,但其运算效率可能不及二进制浮点数,因为缺少浮点数硬件的有效支持。硬件可能无法直接运算十进制数字,而是需要其自身或软件进行某种转换,这降低了运算效率。
当然,如果有需求,不排除未来一些浮点硬件提升十进制浮点数运算能力。