[Java源码]Float

此次咱们来看看Float类的源代码,基于 jdk1.8.0_181.jdk 版本 。html

前言

由于Floatfloat数据类型的包装类,因此先介绍一下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 for byte and short, use a float (instead of double) 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 covers BigDecimal and other useful classes provided by the Java platform.git

Java中的float数据类型是个单精度 32bitIEEE 754 标准的浮点数。而IEEE 754是目前最普遍使用的浮点数运算标准,定义了表示浮点数的格式(包括-0)与反常值,一些特殊数值(+/-∞与NaN),以及这些数值的"浮点数运算符"。另外规定了四种表示浮点数值的方式:单精度(32位)、双精度(64位)、延伸单精度(43位以上)与延伸双精度(79位以上)。具体更加详细的标准说明能够参考 IEEE 754 维基百科github

单精度(32bit)

借用维基百科的图进行说明, express

  • sign: 符号位,1位。0表示正数,1表示负数。
  • exponent: 指数位,8位。单精度的指数部分是−126~+127加上偏移值127,指数值的大小从1~254(0和255是特殊值)
  • fraction: 尾数位,23位

举个简单的例子,如今有个"01000001001100010100011110101110"字符串进行简单的分析:api

  1. 符号位为0,表示正数
  2. 指数位为10000010,结果为130,减去偏移量127后为3
  3. 尾数位为01100010100011110101110,对应的值为1.0110001010001111010111
  4. 因而获得浮点数为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,尾数域全为0
  • NEGATIVE_INFINITY 表示负无穷大,对应的值为1 1111 1111 000 0000 0000 0000 0000 0000标准定义为指数域全为1,尾数域全为0
  • NaN 英文缩写,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;
复制代码

定义了Floatbit位数

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
复制代码

由于Floatfloat的包装类,因此这里就存放了对应的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 查看源码,介绍一下大体逻辑:

  1. 去除两边空格,判断是否为空字符串或者null,是则抛出异常
  2. 判断是否-/+开头,肯定正负号
  3. 判断是否符合NaN字符串,是则返回
  4. 判断是否符合Infinity字符串,是则根据正负性,返回正无穷或者负无穷
  5. 判断是否0x / 0X开头的16进制数,是的话调用parseHexString处理。而后正则匹配是否符合格式要求,是的话按照规范进行转换返回
  6. 处理中间的数字字符串
  7. 判断是否存在对应的e或者E的科学计数,中间还须要考虑处理溢出的问题。
  8. fF结束标志判断,判断是否还有剩余字符,否返回处理结果

返回处理结果的过程比较有意思,当非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无序的,因此

  1. 当一个或者两个操做数为NaN时,<, <=,>, and >=均返回false
  2. 若是任一操做数为NaN时,==返回false。特别是,若是x或者y是NaN,那么(x<y) == !(x>=y)false
  3. 若是任一操做数为NaN时,!= 返回true。特别是,当且仅当x为NaN时,x != xtrue

具体也能够参考官方资料 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标准,由于实际代码中不少都是根据标准而来,若是对标准有所了解总体思路理解起来就会简单不少。另外其中一些方法的计算(如compareequals)等也是比较有意思的,看了代码你就了解了为何new Float(0.0f).equals(new Float(-0.0f))是不成立的。

另外中间由于考虑浮点数的有效位数这个问题,网上搜索了好久的资料,五花八门的,各类答案都有,不过仍是看英文资料叙述的详细,有兴趣的能够看看下面提供的参考资料,强烈推荐!!!

参考资料

  1. 维基百科
  2. Single-precision_floating-point_format
  3. 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?
  4. What's the reason why “text-float-text” guarantee 6 digit but “float-text-float” does 9?
  5. Decimal Precision of Binary Floating-Point Numbers

最后

博客地址原文路径:blog.renyijiu.com/post/java源码…

相关文章
相关标签/搜索