这次我们来看看Float
类的源代码,基于 jdk1.8.0_181.jdk 版本 。
前言
因为Float
是float
数据类型的包装类,所以先介绍一下float
的一些基础知识点,我们先看下官方文档的基本介绍。
The
float
data type is a single-precision 32-bit IEEE 754 floating point. Its range of values is beyond the scope of this discussion, but is specified in the Floating-Point Types, Formats, and Values section of the Java Language Specification. As with the recommendations forbyte
andshort
, use afloat
(instead ofdouble
) if you need to save memory in large arrays of floating point numbers. This data type should never be used for precise values, such as currency. For that, you will need to use the java.math.BigDecimal class instead. Numbers and Strings coversBigDecimal
and other useful classes provided by the Java platform.
Java中的float
数据类型是个单精度 32bit 的 IEEE 754 标准的浮点数。而IEEE 754是目前最广泛使用的浮点数运算标准,定义了表示浮点数的格式(包括-0)与反常值,一些特殊数值(+/-∞与NaN),以及这些数值的"浮点数运算符"。另外规定了四种表示浮点数值的方式:单精度(32位)、双精度(64位)、延伸单精度(43位以上)与延伸双精度(79位以上)。具体更加详细的标准说明可以参考 IEEE 754 维基百科。
单精度(32bit)
借用维基百科的图进行说明,
- sign: 符号位,
1
位。0表示正数,1表示负数。 - exponent: 指数位,
8
位。单精度的指数部分是−126~+127加上偏移值127,指数值的大小从1~254(0和255是特殊值) - fraction: 尾数位,
23位
。
举个简单的例子,现在有个"01000001001100010100011110101110"字符串进行简单的分析:
- 符号位为
0
,表示正数 - 指数位为
10000010
,结果为130,减去偏移量127后为3 - 尾数位为
01100010100011110101110
,对应的值为1.0110001010001111010111
- 于是得到浮点数为
1011.0001010001111010111
,转成十进制为11.07999992370605469
,约等于11.08
更加详细的说明可以自行搜索,或者查看官方文档 Floating-Point Types, Formats, and Values
规约形式的浮点数
如果浮点数中指数域的编码值在0 < exponent <= 2^e - 2
,且在科学表示法的表示方式下,尾数部分的最高有效位为1,那么这个浮点数称为规约形式的浮点数
。在这种情况下,尾数有一位隐含的二进制有效数字1。
非规约形式的浮点数
如果浮点数的指数部分的编码值为0,尾数部分不为0,那么这个浮点数被称为非规约形式的浮点数
。一般是某个数字相当接近于0时才会使用非规约形式来表示。
类定义
public final class Float extends Number implements Comparable<Float>
定义中带有final
标识,表示是不可继承的,另外继承了Number
类,实现了Comparable
接口
属性
public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;
public static final float NaN = 0.0f / 0.0f;
POSITIVE_INFINITY
表示正无穷大,正无穷的值为0 1111 1111 000 0000 0000 0000 0000 0000
(为了方便看,中间保留了空格);标准定义为指数域全为1,尾数域全为0NEGATIVE_INFINITY
表示负无穷大,对应的值为1 1111 1111 000 0000 0000 0000 0000 0000
;标准定义为指数域全为1,尾数域全为0NaN
英文缩写,not a number,用来表示错误的情况,例如0 / 0
的问题;标准定义为指数域全为1,尾数域不全为0,如0 1111 1111 000 0000 0000 0000 0000 0001
public static final float MAX_VALUE = 0x1.fffffeP+127f; // 3.4028235e+38f
public static final float MIN_NORMAL = 0x1.0p-126f; // 1.17549435E-38f
public static final float MIN_VALUE = 0x0.000002P-126f; // 1.4e-45f
MAX_VALUE
表示了float
类型的最大规约数为0x1.fffffeP+127f
,这里是十六进制的表示方式,即(2 - Math.pow(2, -23))*Math.pow(2, 127)
,结果即为3.4028235e+38f
MIN_NORMAL
表示了最小的规约数,为0x1.0p-126f
,即Math.pow(2, -126)
,结果为1.17549435E-38f
MIN_VALUE
表示了最小的非规约数,为0x0.000002P-126f
,即Math.pow(2, -149)
,结果为1.4e-45f
public static final int MAX_EXPONENT = 127;
public static final int MIN_EXPONENT = -126;
MAX_EXPONENT
表示了最大的指数值,为127
MIN_EXPONENT
表示了最小的指数值,为-126
public static final int SIZE = 32;
定义了Float
的 bit
位数
public static final int BYTES = SIZE / Byte.SIZE;
定义了Float
的字节数,计算值固定为4
@SuppressWarnings("unchecked")
public static final Class<Float> TYPE = (Class<Float>) Class.getPrimitiveClass("float");
获取类信息,Float.TYPE == float.class
两者是等价的
private final float value
因为Float
是float
的包装类,所以这里就存放了对应的float
类型的值
private static final long serialVersionUID = -2671257302660747028L;
方法
构造方法
public Float(float value) {
this.value = value;
}
public Float(double value) {
this.value = (float)value;
}
public Float(String s) throws NumberFormatException {
value = parseFloat(s);
}
提供三种构造函数,可以传入String、float和double类型数据,其中String类型调用parseFloat
方法完成,double类型则直接强制类型转换(可能会出现精度丢失的问题)。
parseFloat 方法
public static float parseFloat(String s) throws NumberFormatException {
return FloatingDecimal.parseFloat(s);
}
通过调用FloatingDecimal.parseFloat
方法对字符串进行转换,FloatingDecimal
类来自于sun.misc.FloatingDecimal
,并不在 src.zip 的源码中包含,另外代码较长,主要实现了IEEE 754标准的一些计算,这里就不复制了,如果有兴趣可以去 FloatingDecimal 查看源码,介绍一下大致逻辑:
- 去除两边空格,判断是否为空字符串或者null,是则抛出异常
- 判断是否
-/+
开头,确定正负号 - 判断是否符合
NaN
字符串,是则返回 - 判断是否符合
Infinity
字符串,是则根据正负性,返回正无穷或者负无穷 - 判断是否
0x / 0X
开头的16进制数,是的话调用parseHexString
处理。然后正则匹配是否符合格式要求,是的话按照规范进行转换返回 - 处理中间的数字字符串
- 判断是否存在对应的
e
或者E
的科学计数,中间还需要考虑处理溢出的问题。 f
或F
结束标志判断,判断是否还有剩余字符,否返回处理结果
返回处理结果的过程比较有意思,当非0数字小于7位时,会直接进行结果处理;否则当符合一定要求时,会采用double
类型处理,然后强制转换成 float
类型返回结果;最复杂的就是非0数字加上指数过大,超过一个可以一步完成操作的界限时,会通过一个近似正确的答案,然后按10的幂进行缩放来递进缩小误差,当误差小于一个认可值时就返回结果,这个过程中间使用double
类型来避免产生上溢/下溢的问题。
这个过程的代码比较复杂,多是数学计算,不是很好理解,本文的叙述可能会有错误,如果有错误或者你有好的源代码分析相关资料,欢迎联系指出。
toString 方法
public static String toString(float f) {
return FloatingDecimal.toJavaFormatString(f);
}
public String toString() {
return Float.toString(value);
}
两个toString
方法,下面的方法内部实现调用了第一个方法,而第一个的实现通过FloatingDecimal
类的方法去实现。这里的代码依旧是比较复杂的🤦♂️,将浮点数转换成二进制形式,然后判断是否是正负无穷以及NaN逻辑(这比较简单),然后开始处理逻辑(这里过于复杂了,有兴趣的可以自行查看对应的源码)。NaN
会返回对应的字符串"NaN",Infinity
会返回对应的"Infinity"或者"-Infinity",当输入在10^-3 ~ 10^7
之间,会返回正常的十进制数,而不在这个范围时,会采用科学计数法表示。
toHexString 方法
public static String toHexString(float f) {
if (Math.abs(f) < FloatConsts.MIN_NORMAL
&& f != 0.0f ) {// float subnormal
// Adjust exponent to create subnormal double, then
// replace subnormal double exponent with subnormal float
// exponent
String s = Double.toHexString(Math.scalb((double)f,
/* -1022+126 */
DoubleConsts.MIN_EXPONENT-
FloatConsts.MIN_EXPONENT));
return s.replaceFirst("p-1022$", "p-126");
}
else // double string will be the same as float string
return Double.toHexString(f);
}
返回十六进制格式字符串,内部主要使用了Double.toHexString
方法实现
valueOf 方法
public static Float valueOf(String s) throws NumberFormatException {
return new Float(parseFloat(s));
}
public static Float valueOf(float f) {
return new Float(f);
}
存在两个valueOf
方法,当参数为float
类型时,直接new Float(f)
然后返回;对于字符串参数,调用parseFloat
方法转换成float
,然后new一个新的对象返回。
isNaN 方法
public boolean isNaN() {
return isNaN(value);
}
public static boolean isNaN(float v) {
return (v != v);
}
两个isNaN
的方法,用于判断传入的float
是否是NaN
,第一个方法内部调用了第二个方法实现。而第二个方法内部直接使用了(v != v)
的逻辑。这是由NaN
相关标准决定的,NaN
是无序的,所以
- 当一个或者两个操作数为
NaN
时,<
,<=
,>
, and>=
均返回false
- 如果任一操作数为
NaN
时,==
返回false
。特别是,如果x或者y是NaN
,那么(x<y) == !(x>=y)
为false
- 如果任一操作数为
NaN
时,!=
返回true
。特别是,当且仅当x为NaN
时,x != x
为true
具体也可以参考官方资料 oracel 的详细说明。
isInfinite 方法
public boolean isInfinite() {
return isInfinite(value);
}
public static boolean isInfinite(float v) {
return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
}
判断一个数是不是无穷数,包括正无穷和负无穷。
isFinite 方法
public static boolean isFinite(float f) {
return Math.abs(f) <= FloatConsts.MAX_VALUE;
}
判断输入的数是不是有限浮点数,通过判断输入参数绝对值是否小于最大浮点数。
xxxValue 方法
public byte byteValue() {
return (byte)value;
}
public short shortValue() {
return (short)value;
}
public int intValue() {
return (int)value;
}
public long longValue() {
return (long)value;
}
public float floatValue() {
return value;
}
public double doubleValue() {
return (double)value;
}
获取各种类型的值,内部直接强制转换成对应类型的值返回。
hashCode 方法
@Override
public int hashCode() {
return Float.hashCode(value);
}
public static int hashCode(float value) {
return floatToIntBits(value);
}
调用floatToIntBits
方法,也就是直接将对应浮点数转换成整数作为其hashCode
floatToRawIntBits 方法
public static native int floatToRawIntBits(float value);
/*
* Find the bit pattern corresponding to a given float, NOT collapsing NaNs
*/
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;
}
floatToRawIntBits
是个native
方法,具体实现由上面提供的c语言代码实现。union
是个数据结构,能在同一个内存空间储存不同的数据类型,也就是说同一块内存,可以表示float
,也可以表示int
。
结果会保留NaN
值。正无穷结果为0x7f800000
,负无穷结果为0xff800000
;当参数为NaN
时,结果会是实际的NaN
整数值,该方法不会像floatToIntBits
一样,对NaN进行统一值处理。
intBitsToFloat 方法
public static native float intBitsToFloat(int bits);
/*
* Find the float corresponding to a given bit pattern
*/
JNIEXPORT jfloat JNICALL
Java_java_lang_Float_intBitsToFloat(JNIEnv *env, jclass unused, jint v)
{
union {
int i;
float f;
} u;
u.i = (long)v;
return (jfloat)u.f;
}
intBitsToFloat
也是个native
方法,由上方对应的c语言代码实现,具体就不赘述了。参数为0x7f800000
时,结果为正无穷;参数为0xff800000
,结果为负无穷;当参数为0x7f800001 ~ 0x7fffffff
或者0xff800001 ~ 0xffffffff
之间时,结果为NaN
。因为对于Java而言,相同类型而不同bit模式组成的NaN数据是不可分辨的。如果你需要区分,可以使用上面的floatToRawIntBits
方法。
值得注意的是,这个方法可能无法返回与参数数据 bit pattern 一致的NaN
结果。IEEE 754
标准区分了两种类型的NaN
数据(quiet NaNs and signaling NaNs
),但是在Java中这两种的处理是不可见的,所以对于某些值floatToRawIntBits(intBitsToFloat(start)) != start
可能是会存在的。不过对于上述给出的范围已经包含了所有可能的NaN
数据的位信息。
floatToIntBits 方法
public static int floatToIntBits(float value) {
int result = floatToRawIntBits(value);
// Check for NaN based on values of bit fields, maximum
// exponent and nonzero significand.
if ( ((result & FloatConsts.EXP_BIT_MASK) ==
FloatConsts.EXP_BIT_MASK) &&
(result & FloatConsts.SIGNIF_BIT_MASK) != 0)
result = 0x7fc00000;
return result;
}
基本与floatToRawIntBits
方法一致,只是增加了对NaN
的判断,若是NaN
则直接返回0x7fc00000
(这里对NaN
做了统一处理,所有的都返回0x7fc00000
);正无穷为0x7f800000
;负无穷为0xff800000
;其他的值返回对应的结果。
具体看下两个判断条件:
(result & FloatConsts.EXP_BIT_MASK) == FloatConsts.EXP_BIT_MASK
FloatConsts.EXP_BIT_MASK
值为 0x7F800000
, 二进制为 0 11111111 00000000000000000000000
,这里就是对指数域做了判断,指数域全为1
result & FloatConsts.SIGNIF_BIT_MASK) != 0
FloatConsts.SIGNIF_BIT_MASK
值为 0x007FFFFF
,二进制为 0 00000000 11111111111111111111111
,这里就是对尾数域进行判断,尾数域不全为0
两者的结合符合我们上面所说的NaN
的标准定义。
equals 方法
public boolean equals(Object obj) {
return (obj instanceof Float)
&& (floatToIntBits(((Float)obj).value) == floatToIntBits(value));
}
首先判断obj是不是Float
对象的实例,然后通过floatToIntBits
获取两个整数值,进行比较判断是否一致;这里注意的是当误差小于精度范围时,结果是可能返回true
的,例如
Float a = 100000f;
Float b = 100000.001f;
System.out.println(Float.floatToIntBits(a)); // 1203982336
System.out.println(Float.floatToIntBits(b)); // 1203982336
System.out.println(a.equals(b)); // true
System.out.println(new Float(0.0f).equals(new Float(-0.0f))); // false
compare 方法
public int compareTo(Float anotherFloat) {
return Float.compare(value, anotherFloat.value);
}
public static int compare(float f1, float f2) {
if (f1 < f2)
return -1; // Neither val is NaN, thisVal is smaller
if (f1 > f2)
return 1; // Neither val is NaN, thisVal is larger
// Cannot use floatToRawIntBits because of possibility of NaNs.
int thisBits = Float.floatToIntBits(f1);
int anotherBits = Float.floatToIntBits(f2);
return (thisBits == anotherBits ? 0 : // Values are equal
(thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
1)); // (0.0, -0.0) or (NaN, !NaN)
}
compareTo
方法内部调用了compare
实现,所以看下compare
的具体实现。首先判断< / >
操作,如果成立直接返回;若不成立,则表示数据存在NaN
情况,对于NaN
来说,<, <=, >, and >=
判断结果都是false
,剩下的注释已经解释的很清楚了,不赘述了。不过与==
还是存在区别的,值得注意下。
System.out.println(-0.0f == 0.0f ? true : false); // true
System.out.println(-0.0f < 0.0f ? true : false); // false
System.out.println(-0.0f <= 0.0f ? true : false); // true
System.out.println(Float.compare(-0.0f, 0.0f)); // -1
System.out.println(Float.compare(0.0f, -0.0f)); // 1
System.out.println(Float.compare(-Float.NaN, Float.NaN)); // 0
System.out.println(Float.compare(Float.NaN, -Float.NaN)); // 0
System.out.println(Float.compare(1.0f, Float.NaN)); // -1
System.out.println(Float.compare(1.0f, -Float.NaN)); // -1
sum、min、max 方法
public static float sum(float a, float b) {
return a + b;
}
public static float max(float a, float b) {
return Math.max(a, b);
}
public static float min(float a, float b) {
return Math.min(a, b);
}
这个很好理解,不过还是要注意一些特殊边界值。
System.out.println(Float.max(0.0f, -0.0f)); // 0.0
System.out.println(Float.min(0.0f, -0.0f)); // -0.0
System.out.println(Float.max(Float.NaN, 1.0f)); // NaN
System.out.println(Float.min(-Float.NaN, 1.0f)); // NaN
总结
从文章各种说明也可以看出来,Float
代码比前两次相对来说复杂多了。其中比较重要的是IEEE 754
标准,因为实际代码中很多都是根据标准而来,如果对标准有所了解整体思路理解起来就会简单很多。另外其中一些方法的计算(如compare
,equals
)等也是比较有意思的,看了代码你就了解了为什么new Float(0.0f).equals(new Float(-0.0f))
是不成立的。
另外中间因为考虑浮点数的有效位数这个问题,网上搜索了很久的资料,五花八门的,各种答案都有,不过还是看英文资料叙述的详细,有兴趣的可以看看下面提供的参考资料,强烈推荐!!!
参考资料
- 维基百科
- Single-precision_floating-point_format
- Is the most significant decimal digits precision that can be converted to binary and back to decimal without loss of significance 6 or 7.225?
- What's the reason why “text-float-text” guarantee 6 digit but “float-text-float” does 9?
- Decimal Precision of Binary Floating-Point Numbers
最后
博客地址原文路径:blog.renyijiu.com/post/java源码…