此次咱们来看看Float
类的源代码,基于 jdk1.8.0_181.jdk 版本 。html
由于Float
是float
数据类型的包装类,因此先介绍一下float
的一些基础知识点,咱们先看下官方文档的基本介绍。java
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.git
Java中的float
数据类型是个单精度 32bit 的 IEEE 754 标准的浮点数。而IEEE 754是目前最普遍使用的浮点数运算标准,定义了表示浮点数的格式(包括-0)与反常值,一些特殊数值(+/-∞与NaN),以及这些数值的"浮点数运算符"。另外规定了四种表示浮点数值的方式:单精度(32位)、双精度(64位)、延伸单精度(43位以上)与延伸双精度(79位以上)。具体更加详细的标准说明能够参考 IEEE 754 维基百科。github
借用维基百科的图进行说明, express
1
位。0表示正数,1表示负数。8
位。单精度的指数部分是−126~+127加上偏移值127,指数值的大小从1~254(0和255是特殊值)23位
。举个简单的例子,如今有个"01000001001100010100011110101110"字符串进行简单的分析:api
0
,表示正数10000010
,结果为130,减去偏移量127后为301100010100011110101110
,对应的值为1.0110001010001111010111
1011.0001010001111010111
,转成十进制为11.07999992370605469
,约等于11.08
更加详细的说明能够自行搜索,或者查看官方文档 Floating-Point Types, Formats, and Values数据结构
若是浮点数中指数域的编码值在0 < exponent <= 2^e - 2
,且在科学表示法的表示方式下,尾数部分的最高有效位为1,那么这个浮点数称为规约形式的浮点数
。在这种状况下,尾数有一位隐含的二进制有效数字1。oracle
若是浮点数的指数部分的编码值为0,尾数部分不为0,那么这个浮点数被称为非规约形式的浮点数
。通常是某个数字至关接近于0时才会使用非规约形式来表示。ide
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类型则直接强制类型转换(可能会出现精度丢失的问题)。
public static float parseFloat(String s) throws NumberFormatException {
return FloatingDecimal.parseFloat(s);
}
复制代码
经过调用FloatingDecimal.parseFloat
方法对字符串进行转换,FloatingDecimal
类来自于sun.misc.FloatingDecimal
,并不在 src.zip 的源码中包含,另外代码较长,主要实现了IEEE 754标准的一些计算,这里就不复制了,若是有兴趣能够去 FloatingDecimal 查看源码,介绍一下大体逻辑:
-/+
开头,肯定正负号NaN
字符串,是则返回Infinity
字符串,是则根据正负性,返回正无穷或者负无穷0x / 0X
开头的16进制数,是的话调用parseHexString
处理。而后正则匹配是否符合格式要求,是的话按照规范进行转换返回e
或者E
的科学计数,中间还须要考虑处理溢出的问题。f
或F
结束标志判断,判断是否还有剩余字符,否返回处理结果返回处理结果的过程比较有意思,当非0数字小于7位时,会直接进行结果处理;不然当符合必定要求时,会采用double
类型处理,而后强制转换成 float
类型返回结果;最复杂的就是非0数字加上指数过大,超过一个能够一步完成操做的界限时,会经过一个近似正确的答案,而后按10的幂进行缩放来递进缩小偏差,当偏差小于一个承认值时就返回结果,这个过程当中间使用double
类型来避免产生上溢/下溢的问题。
这个过程的代码比较复杂,可能是数学计算,不是很好理解,本文的叙述可能会有错误,若是有错误或者你有好的源代码分析相关资料,欢迎联系指出。
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
之间,会返回正常的十进制数,而不在这个范围时,会采用科学计数法表示。
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
方法实现
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一个新的对象返回。
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 的详细说明。
public boolean isInfinite() {
return isInfinite(value);
}
public static boolean isInfinite(float v) {
return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
}
复制代码
判断一个数是否是无穷数,包括正无穷和负无穷。
public static boolean isFinite(float f) {
return Math.abs(f) <= FloatConsts.MAX_VALUE;
}
复制代码
判断输入的数是否是有限浮点数,经过判断输入参数绝对值是否小于最大浮点数。
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;
}
复制代码
获取各类类型的值,内部直接强制转换成对应类型的值返回。
@Override
public int hashCode() {
return Float.hashCode(value);
}
public static int hashCode(float value) {
return floatToIntBits(value);
}
复制代码
调用floatToIntBits
方法,也就是直接将对应浮点数转换成整数做为其hashCode
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进行统一值处理。
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
数据的位信息。
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
的标准定义。
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
复制代码
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
复制代码
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))
是不成立的。
另外中间由于考虑浮点数的有效位数这个问题,网上搜索了好久的资料,五花八门的,各类答案都有,不过仍是看英文资料叙述的详细,有兴趣的能够看看下面提供的参考资料,强烈推荐!!!
博客地址原文路径:blog.renyijiu.com/post/java源码…