有可能(至少对于 IEEE 754 float 和 double 值)通过(伪代码)计算最大浮点值:
~(-1.0) | 0.5
在进行位旋转之前,我们必须将浮点值转换为整数,然后再转换回来。这可以通过以下方式完成:
uint64_t m_one, half;
double max;
*(double *)(void *)&m_one = -1.0;
*(double *)(void *)&half = 0.5;
*(uint64_t *)(void *)&max = ~m_one | half;
那么它是如何工作的呢?为此,我们必须知道如何对浮点值进行编码。
最高位编码符号,接下来的k 位编码指数,最低位将保存小数部分。对于2 的幂,小数部分为0。
指数将以2**(k-1) - 1 的偏差(偏移量)存储,这意味着0 的指数对应于除了最高位之外的所有模式。
有两种具有特殊含义的指数位模式:
- 如果没有设置位,如果小数部分为零,则值为
0;否则,该值是次正规的
- 如果所有位都已设置,则值为
infinity 或NaN
这意味着最大的正则指数将通过设置除最低位之外的所有位进行编码,如果减去偏差,则对应于 2**k - 2 或 2**(k-1) - 1 的值。
对于double 值,k = 11,即最大指数为1023,因此最大浮点值顺序为2**1023,约为1E+308。
最大的价值将有
- 符号位设置为
0
- 除最低指数位以外的所有位都设置为
1
- 所有小数位设置为
1
现在,我们可以理解幻数的工作原理了:
-
-1.0 设置了符号位,指数是偏差 - 即除了最高位之外的所有位都存在 - 小数部分是 0
-
~(-1.0) 仅设置了最高指数位和所有小数位
-
0.5 有一个符号位和0 的小数部分;指数将是偏差减去1,即除了最高和最低指数位之外的所有位都会出现
当我们通过逻辑或组合这两个值时,我们会得到我们想要的位模式。
该计算也适用于 x86 80 位扩展精度值(又名long double),但必须按字节进行位旋转,因为没有足够大的整数类型来保存 32 位硬件上的值.
偏差实际上并不一定是2**(k-1) - 1 - 只要它是奇数,它就可以用于任意偏差。偏差必须是奇数,否则1.0 和0.5 的指数的位模式将在除最低位之外的其他地方有所不同。
如果浮点类型的基数b(又名基数)不是2,则必须使用b**(-1) 而不是0.5 = 2**(-1)。
如果最大指数值不是 reservedrd,请使用 1.0 而不是 0.5。无论基数或偏差如何,这都将起作用(这意味着它不再限于奇数值)。使用1.0的区别在于不会清除最低指数位。
总结一下:
~(-1.0) | 0.5
只要基数为2,偏差为奇数并保留最高指数即可。
~(-1.0) | 1.0
适用于任何基数或偏差,只要不保留最高指数。