参考
https://www.geeksforgeeks.org/ieee-standard-754-floating-point-numbers/
在JDK中Float,Double类中有一个方法
public static native float intBitsToFloat(int bits);
public static native long doubleToRawLongBits(double value);
原理
单精度浮点数在内存中的存储方式为:符号位(1bit)+指数(8bit)+尾数(23)
双精度浮点数在内存中的存储方式为:符号位(1bit)+指数(11bit)+尾数(52)
如图所示
当我们谈论浮点数的表示时,尤其是IEEE 754标准,指数部分(或称为“幂”或“指数”)是一个关键组成部分。IEEE 754标准规定了如何存储浮点数的三个部分:符号、指数和尾数(或称为分数或有效数字)。
为了理解为什么需要为实际指数添加一个偏差(bias),我们需要先了解为什么这样做。
- 为什么需要正负指数?
- 在科学计数法中,一个数字可以表示为 ±a × 10^n 的形式,其中 a 是一个介于1到10之间的数(包括1但不包括10),而 n 是一个整数。n 可以是正数、负数或零。正指数表示数字被放大,而负指数表示数字被缩小。
- 同样,在浮点数的二进制表示中,我们也需要能够表示正的和负的指数。
- 为什么需要添加偏差?
- 为了简化硬件设计和提高精度,IEEE 754标准决定使用一个固定大小的字段来表示指数。例如,在单精度浮点数中,指数字段是8位,而在双精度浮点数中,它是11位。
- 如果我们直接使用实际的指数值来存储,那么对于负数,我们需要一种方法来表示它。最简单的方法可能是使用补码表示法,但是补码对于负数需要求反码,然后加1。增加硬件设计的复杂性。整数的补码就是其本身。
- 为了避免这种情况,IEEE 754标准决定为实际的指数值添加一个固定的偏差。这样,所有的指数值都会变为正数,从而简化了硬件设计和实现。
- 例如,对于单精度浮点数,偏差是127。所以,如果实际的指数值是-1,存储的指数值将是126;如果实际的指数值是0,存储的指数值将是127;如果实际的指数值是1,存储的指数值将是128,依此类推。
总之,添加偏差是为了简化浮点数的存储和计算,尤其是在硬件实现中。它确保了我们总是使用正数来表示指数,从而避免了处理负数的复杂性。
特殊值
- Zero(零)
- 无穷大(包含正无穷大、负无穷大)
- NAN(not a number)
当阶码全为 0:
- 尾数 M 不全为 0 时,表示非规格化小数:±(0.xxx…xx)×2-126
- 尾数 M 全为 0 时,表示真值 ±0
当阶码全为 1:
- 尾数 M 全为 0:表示无穷大
- 尾数 M 不全为 0:表示数值“NAN”,如0/0、∞-∞等
浮点数的 underflow(下溢)是指当一个接近于零的数值被当作零来处理的情况。
在计算机中,浮点数都是离散的、有限的。因此,计算机无法用有限的浮点数来表示无限的实数。在用浮点数表示实数时,很多数都会存在近似误差。其中一种是下溢(underflow)。比如,一个非常接近于零的数值A(实际上A>0),计算机会直接把该数值判为零,虽然实际上它并不为零。
源码实现
JNIEXPORT jint JNICALL
Java_java_lang_Float_floatToRawIntBits(JNIEnv *env, jclass unused, jfloat v)
{
union {
int i;
float f;
} u;
u.f = (float)v;
return (jint)u.i;
}
这段代码使用了一个联合体(union),这是一个特殊的数据结构,可以存储不同的数据类型,但是使用的内存是重叠的,因此在同一时间只能存储其中一个值。这里联合体用于将 jfloat 类型的值转换为 jint 类型的值,并直接返回这个值。