初窥JVM浮点运算


欢迎来到“Under The Hood”第四期。上期我们讨论了 JVM 的字节码指令集,本期我们继续这个话题。本文我们来看看 JVM 中的浮点运算

JVM 支持 IEEE-754 浮点数标准(1985)。该标准定义了 32 位和 64 位浮点数的格式,以及在此之上的各种运算。在 JVM 中,浮点运算是基于 32 位 float 数和 64 位 double 数的。对每个操作 float 数的字节码,都有一个对应的 double 数的版本。

浮点数由 4 部分组成:数符(sign),尾数(mantissa),基数(radix)和阶码(exponent)。数符取 1 或 -1。尾数是一个正数,它持有浮点数的有效位。阶码尾数符号位应该乘以的基数的正(负)幂数。这 4 个部分用下面的公式得到浮点数的值:

浮点数有很多种表现形式,因为你总是可以把浮点数尾数乘以基数的某次幂,然后通过改变阶码的方式获得原先的值。例如,-5 可以写成如下以 10 为基数的形式:

Sign Mantissa Radix Exponent
-1 50 10 -1
-1 5 10 0
-1 0.5 10 1
-1 0.05 10 2

每个浮点数都有一个规范化的表示形式。如果浮点数的尾数符合下面的公式,我们就说这个浮点数是规范化的。

规范化的以 10 为基数浮点数尾数的小数点出现在第一个非0的有效位前面。-5 的规范化形式是。也就是说,规范化的浮点数中,小数点的左边是0,右边第一位不是0。其他不是这种形式的浮点数都是非规范化数。注意,0没有规范化的形式,因为它没有非0数放在小数点后面。数字0们总是感叹“为什么要规范化我们呢?”。

JVM中的浮点数以2为基数,所以JVM中的浮点数值用下面的公式获得:

JVM中的浮点数尾数用2进制数表示。规范化的二进制数小数点出现在非0最高有效位前面。由于二进制数系统只有2个数字,1和0,所以最高有效位上的数字总是1。

float和double数的最高有效位是它的符号位,float的最后23位是尾数位,而double则为最后52位。阶码处在符号位和尾数中间,float为8位,double为11位。float的完整2进制形式如下,s表示符号位,e表示阶码,m表示尾数

1
s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm 

正数的符号位为0,负数的符号位为1。尾数总是一个正的二进制数,但不是二进制补码数。如果符号位为1,浮点数的值是负的,但尾数仍然是正的。

阶码有3种解释方式。如果阶码位全是1,意味着这是一个特殊的浮点数,表示正无穷或负无穷,或者不是一个数(NaN)。NaN是特定运算的结果,比如除法的除数是0。阶码的所有位为0,表示这是一个非规范化的浮点数。除上面2种情况之外的阶码,都是规范化浮点数的一部分。

尾数部分其实隐式包含了一个额外的精度位。float数的尾数占23位,却有24个精度位;同一样的,double数的尾数占52位,却有53个精度位。因为尾数部分的最高有效位是可以被预测的,所以并没有包含在尾数中。JVM中,浮点数的阶码可以指明该数是不是一个规范化浮点数。如果阶码位全0,则为非规范化数,且最高有效位肯定是0。其他情况下,则为规范化浮点数,且最高有效位肯定是1。

在JVM中,任何浮点运算都不会抛出异常。类似除数为0的问题操作,JVM会返回一些特殊值,比如正/负无穷,或NaN。尾数位全是0的情况下,如果阶码位全是1,符号位是0,则表示正无穷;如果阶码位全是1,符号位是1,则表示负无穷。如果阶码位全是1,尾数位不全是0,则表示NaN。JVM总是为NaN使用相同的尾数:最高有效位是1,其他全为0。下表列出了上面提到的3中特殊值:

Special float values Float bits (sign exponent mantissa)
+Infinity 0 11111111 00000000000000000000000
-Infinity 1 11111111 00000000000000000000000
NaN 1 11111111 10000000000000000000000

非全0和非全1的阶码表示规范化尾数要乘以的2的幂数。可以把阶码当做一个正数,然后减去一个偏移量,这样就能得到实际的幂数。对float数来说,偏移量是126;而double数,则为1023。例如,一个float数的阶码为00000001,则幂数为-125(1 – 126),这是float中最小的幂数。再看一个例子,如果阶码是11111110,则幂数为128(254 – 126),这是float中最大的幂数。下表列出了一些正规化的浮点数

Normalized float values Float bits (sign exponent mantissa) Unbiased exponent
Largest positive (finite) float 0 11111110 11111111111111111111111 128
Largest negative (finite) float 1 11111110 11111111111111111111111 128
Smallest normalized float 1 00000001 00000000000000000000000 -125
Pi 0 10000000 10010010000111111011011 2

阶码位全0,说明尾数没有规范化,也隐含说明了最高有效为是0,而不是1。这种情况下,幂数为浮点数的最小幂数。对float来说,是-125。这意味着,规范化尾数乘以2 -125的浮点数,它的阶码为00000001,而非规范化尾数乘以2 -125的浮点数,它的阶码为00000000。阶码范围底端的非规范化数修正值,使得下溢出较为平缓。如果最小阶码用来表示规范化数,下溢成0的最小数值会更大一些。 换句话说,让最小阶码表示非规范化数,可以使浮点数能表示更小的数值。虽然非规范化数的精度没有规范化数高,但是,这相对于阶码一旦达到最小规范化数值,浮点数就会下溢成0来说,非规范化数更好一些。

Denormalized float values Float bits (sign exponent mantissa)
Smallest positive (non-zero) float 0 00000000 00000000000000000000001
Smallest negative (non-zero) float 1 00000000 00000000000000000000001
Largest denormalized float 1 00000000 11111111111111111111111
Positive zero 0 00000000 00000000000000000000000
Negative zero 1 00000000 00000000000000000000000

原文地址:Floating-point arithmetic