从JDK源码角度看Float

关于IEEE 754

在看Float前需要先了解IEEE 754标准,该标准定义了浮点数的格式还有一些特殊值,它规定了计算机中二进制与十进制浮点数转换的格式及方法。规定了四种表示浮点数值的方法,单精确度(32位)、双精确度(64位)、延伸单精确度(43位以上)与延伸双精确度(79位以上)。多数编程语言支持单精确度和双精确度,这里讨论的Float就是Java的单精确度的实现。

浮点数的表示

浮点数由三部分组成,如下图,符号位s、指数e和尾数f。

对于求值我们是有一个公式对应的,根据该公式来看会更简单点,某个浮点数的值为:

(−1)s∗(1.f)∗2(e−127)

可以看到32位的最高位为符号标识符,1表示负数,0表示正数。指数部分为8位,其实可以是0到255,但是为了可正可负,这里需要减去127后才是真正的指数,而底数固定为2。剩下的23位表示尾数,但默认前面都会加上1.。所以通过上面就可以将一个浮点数表示出来了。

我们举个例子来看,二进制的“01000001001101100000000000000000”表示的浮点数是啥?

  1. 符号位为0,表示正数。
  2. 指数为“10000010”,减去127后为3。
  3. 尾数对应的值为“1.011011”。
  4. 于是最终得到浮点数为“1011.011”,转成十进制为“11.375”。

Float概况

Java的Float类主要的作用就是对基本类型float进行封装,提供了一些处理float类型的方法,比如float到String类型的转换方法或String类型到float类型的转换方法,当然也包含与其他类型之间的转换方法。

继承结构

--java.lang.Object
  --java.lang.Number
    --java.lang.Float

主要属性

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;
public static final float MAX_VALUE = 0x1.fffffeP+127f;
public static final float MIN_NORMAL = 0x1.0p-126f;
public static final float MIN_VALUE = 0x0.000002P-126f;
public static final int MAX_EXPONENT = 127;
public static final int MIN_EXPONENT = -126;
public static final int SIZE = 32;
public static final int BYTES = SIZE / Byte.SIZE;
public static final Class<Float> TYPE = (Class<Float>) Class.getPrimitiveClass("float");
  • POSITIVE_INFINITY 用来表示正无穷大,按照IEEE 754浮点标准规定,任何有限正数除以0为正无穷大,正无穷的值为0x7f800000。
  • NEGATIVE_INFINITY 用来表示负无穷大,任何有限负数除以0为负无穷的,负无穷的值为0xff800000。
  • NaN 用来表示处理计算中出现的错误情况,比如0除以0或负数平方根。对于单精度浮点数,IEEE 标准规定 NaN 的指数域全为 1,且尾数域不等于零的浮点数。它并没有要求具体的尾数域,所以 NaN 实际上不非是一个,而是一族。Java这里定义的值为0x7fc00000。
  • MAX_VALUE 用来表示最大的浮点数值,它定义为0x1.fffffeP+127f,这里0x表示十六进制,1.fffffe表示十六进制的小数,P表示2,+表示几次方,这里就是2的127次方,最后的f是转成浮点型。所以最后最大值为3.4028235E38。
  • MIN_NORMAL 用来表示最小标准值,它定义为0x1.0p-126f,这里其实就是2的-126次方的了,值为1.17549435E-38f。
  • MIN_VALUE 用来表示浮点数最小值,它定义为0x0.000002P-126f,最后的值为1.4e-45f。
  • MAX_EXPONENT 用来表示指数的最大值,这里定为127,这个也是按照IEEE 754浮点标准的规定。
  • MIN_EXPONENT 用来表示指数的最小值,按照IEEE 754浮点标准的规定,它为-126。
  • SIZE 用来表示二进制float值的比特数,值为32,静态变量且不可变。
  • BYTES 用来表示二进制float值的字节数,值为SIZE除于Byte.SIZE,结果为4。
  • TYPE的toString的值是float
    Class的getPrimitiveClass是一个native方法,在Class.c中有个Java_java_lang_Class_getPrimitiveClass方法与之对应,所以JVM层面会通过JVM_FindPrimitiveClass函数根据”float”字符串获得jclass,最终到Java层则为Class<Float>
JNIEXPORT jclass JNICALL
Java_java_lang_Class_getPrimitiveClass(JNIEnv *env,
                                       jclass cls,
                                       jstring name)
{
    const char *utfName;
    jclass result;

    if (name == NULL) {
        JNU_ThrowNullPointerException(env, 0);
        return NULL;
    }

    utfName = (*env)->GetStringUTFChars(env, name, 0);
    if (utfName == 0)
        return NULL;

    result = JVM_FindPrimitiveClass(env, utfName);

    (*env)->ReleaseStringUTFChars(env, name, utfName);

    return result;
}

TYPE执行toString时,逻辑如下,则其实是getName函数决定其值,getName通过native方法getName0从JVM层获取名称,

public String toString() {
        return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
            + getName();
    }

getName0根据一个数组获得对应的名称,JVM根据Java层的Class可得到对应类型的数组下标,比如这里下标为6,则名称为”float”。

const char* type2name_tab[T_CONFLICT+1] = {
  NULL, NULL, NULL, NULL,
  "boolean",
  "char",
  "float",
  "double",
  "byte",
  "short",
  "int",
  "long",
  "object",
  "array",
  "void",
  "*address*",
  "*narrowoop*",
  "*conflict*"
};

主要方法

parseFloat

public static float parseFloat(String s) throws NumberFormatException {
        return FloatingDecimal.parseFloat(s);
    }

通过调用FloatingDecimal的parseFloat方法来实现对字符串的转换,FloatingDecimal类主要提供了对 IEEE-754,该方法的实现代码实在是太长,这里不再贴出了,说下它的处理思想及步骤。
1. 判断开头是否为“-”或“+”符号,即正数或负数。
2. 判断是不是一个NaN,如果是则返回NaN。
3. 判断是不是一个Infinity,如果是则返回Infinity。
4. 判断是不是一个0x开头的十六进制的Java浮点数,如果是则将该十六进制浮点数按照IEEE-754标准转换成十进制浮点数。比如字符串为 0x12.3512p+11f ,则转换后为37288.562。
5. 判断字符串中是否有包含了E字符,即是否是科学计数法,如果有则需要处理。比如字符串为 10001.222E+2 ,则转换后为1000122.2。
6. 还要处理浮点数精度问题,这个处理比较复杂,我们知道浮点数的精度是7位有效数字,这里为什么是7呢?还要回到IEEE-754标准上,32位二进制转换成浮点数是根据公式$(-1)^s*(1.f)*2^{(e-127)}$转换的,可以看到它的精度由尾数来决定,尾数有23位,那么$2^{23}=8388608$,该值介于$10^{6}$$10^{7}$,所以它能保证6位精确的数,但是7位就不一定了,这里是相对小数点来说的,所以对应整个浮点型的精确值为有效位数就是7位,8位的不一定能准确表示。这里对比几个例子,字符串30.200019转换后为30.20002,一共七位有效位;字符串30.200001转换后为30.2,一共七位有效位,但后面都为0,所以省略;字符串30000.2196501转换后为30000.219,一共八位有效位,刚好能准确表示八位。

构造函数

public Float(String s) throws NumberFormatException {
        value = parseFloat(s);
    }
public Float(float value) {
        this.value = value;
    }
public Float(double value) {
        this.value = (float)value;
    }

提供三种构造函数,都比较简单,可传入String、float和double类型值,其中String类型会调用parseFloat方法进行转换,double则直接转成float类型。

toString

public String toString() {
        return Float.toString(value);
    }
public static String toString(float f) {
        return FloatingDecimal.toJavaFormatString(f);
    }

两个toString方法,主要看第二个,通过FloatingDecimal类的toJavaFormatString方法转成字符串。这个转换过程也是比较复杂,这里不再贴代码,它处理的过程是先将浮点数转成IEEE-754标准的二进制形式,并且还要判断是否是正负无穷大,是否是NaN。然后再按照IEEE-754标准从二进制转换成十进制,此过程十分复杂,需要考虑的点相当多。最后生成浮点数对应的字符串。

valueOf方法

public static Float valueOf(float f) {
        return new Float(f);
    }
public static Float valueOf(String s) throws NumberFormatException {
        return new Float(parseFloat(s));
    }

有两个valueOf方法,对于float型的直接new一个Float对象返回,而对于字符串则先调用parseFloat方法转成float后再new一个Float对象返回。

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;
    }

包括byteValue、shortValue、intValue、longValue、floatValue和doubleValue等方法,其实就是转换成对应的类型。

floatToRawIntBits方法

public static native int floatToRawIntBits(float value);

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是一个本地方法,该方法主要是将一个浮点数转成IEEE 754标准的二进制形式对应的整型数。对应的本地方法的处理逻辑简单而且有效,就是通过一个union实现了int和float的转换,最后再转成java的整型jint。

floatToIntBits方法

public static native float intBitsToFloat(int bits);

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;
}

该方法与floatToRawIntBits方法对应,floatToIntBits同样是一个本地方法,该方法主要是将一个IEEE 754标准的二进制形式对应的整型数转成一个浮点数。可以看到其本地实现也是通过union来实现的,完成int转成float,最后再转成java的浮点型jfloat。

floatToIntBits方法

public static int floatToIntBits(float value) {
        int result = floatToRawIntBits(value);
        if ( ((result & FloatConsts.EXP_BIT_MASK) ==
              FloatConsts.EXP_BIT_MASK) &&
             (result & FloatConsts.SIGNIF_BIT_MASK) != 0)
            result = 0x7fc00000;
        return result;
    }

该方法主要先通过调用floatToRawIntBits获取到IEEE 754标准对应的整型数,然后再分别用FloatConsts.EXP_BIT_MASK和FloatConsts.SIGNIF_BIT_MASK两个掩码去判断是否为NaN,0x7fc00000对应的即为NaN。

hashCode方法

public int hashCode() {
        return Float.hashCode(value);
    }
public static int hashCode(float value) {
        return floatToIntBits(value);
    }

主要看第二个hashCode方法即可,它是通过调用floatToIntBits来实现的,所以它返回的哈希码其实就是某个浮点数的IEEE 754标准对应的整型数。

isFinite 和 isInfinite

public static boolean isFinite(float f) {
        return Math.abs(f) <= FloatConsts.MAX_VALUE;
    }
public static boolean isInfinite(float v) {
        return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
    }

这两个方法分别用于判断一个浮点数是否为有穷数或无穷数。逻辑很简单,绝对值小于FloatConsts.MAX_VALUE的数则为有穷数,FloatConsts.MAX_VALUE的值为3.4028235e+38f,它其实与前面Float类中定义的MAX_VALUE相同。而是否为无穷数则通过POSITIVE_INFINITY和NEGATIVE_INFINITY进行判断。

isNaN方法

public static boolean isNaN(float v) {
        return (v != v);
    }

用于判断是个浮点数是否为NaN,该方法逻辑很简单,直接(v != v),为啥能这样做?因为规定一个NaN与任何值都不相等,包括它自己。所以这部分逻辑在JVM或本地中会做,于是可以直接通过比较来判断。

max 和 min方法

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);
    }

用于获取两者较大或较小值,直接交由Math类完成。

compare方法

public static int compare(float f1, float f2) {
        if (f1 < f2)
            return -1;
        if (f1 > f2)
            return 1;            

        int thisBits    = Float.floatToIntBits(f1);
        int anotherBits = Float.floatToIntBits(f2);

        return (thisBits == anotherBits ?  0 :
                (thisBits < anotherBits ? -1 :
                 1));
    }

f1小于f2则返回-1,反之则返回1。无法通过上述直接比较时则使用floatToIntBits方法分别将f1和f2转成IEEE 754标准对应的整型数,然后再比较。相等则返回0,否则返回-1或1。

以下是广告相关阅读

========广告时间========

鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有需要的朋友可以到 https://item.jd.com/12185360.html 进行预定。感谢各位朋友。

为什么写《Tomcat内核设计剖析》

=========================

相关阅读:

从JDK源码角度看Object
从JDK源码角度看Long
从JDK源码角度看Integer
volatile足以保证数据同步吗
谈谈Java基础数据类型
从JDK源码角度看并发锁的优化
从JDK源码角度看线程的阻塞和唤醒
从JDK源码角度看并发竞争的超时
从JDK源码角度看java并发线程的中断
从JDK源码角度看Java并发的公平性
从JDK源码角度看java并发的原子性如何保证
从JDK源码角度看Byte
从JDK源码角度看Boolean
从JDK源码角度看Short

有帮助,可打赏:

欢迎关注:

时间: 2024-09-15 23:25:28

从JDK源码角度看Float的相关文章

从JDK源码角度看Byte

Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型之间的转换方法. 主要实现代码如下: public final class Byte extends Number implements Comparable<Byte> { public static final byte MIN_VALUE = -128; public static fina

从JDK源码角度看Long

概况 Java的Long类主要的作用就是对基本类型long进行封装,提供了一些处理long类型的方法,比如long到String类型的转换方法或String类型到long类型的转换方法,当然也包含与其他类型之间的转换方法.除此之外还有一些位相关的操作. 继承结构 --java.lang.Object --java.lang.Number --java.lang.Long 主要属性 public static final long MIN_VALUE = 0x8000000000000000L;

从JDK源码角度看Integer

概况 Java的Integer类主要的作用就是对基本类型int进行封装,提供了一些处理int类型的方法,比如int到String类型的转换方法或String类型到int类型的转换方法,当然也包含与其他类型之间的转换方法.除此之外还有一些位相关的操作. 继承结构 --java.lang.Object --java.lang.Number --java.lang.Integer 主要属性 第一部分 public static final int MIN_VALUE = 0x80000000; pub

从JDK源码角度看Short

概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然也包含与其他类型之间的转换方法. 继承结构 --java.lang.Object --java.lang.Number --java.lang.Short 主要属性 public static final short MIN_VALUE = -32768; public static final s

从JDK源码角度看Boolean

Java的Boolean类主要作用就是对基本类型boolean进行封装,提供了一些处理boolean类型的方法,比如String类型和boolean类型的转换. 主要实现源码如下: public final class Boolean implements java.io.Serializable, Comparable<Boolean> { private final boolean value; public static final Boolean TRUE = new Boolean(

从JDK源码角度看java并发的公平性

        JAVA为简化开发者开发提供了很多并发的工具,包括各种同步器,有了JDK我们只要学会简单使用类API即可.但这并不意味着不需要探索其具体的实现机制,本文从JDK源码角度简单讲讲并发时线程竞争的公平性.         所谓公平性指所有线程对临界资源申请访问权限的成功率都一样,不会让某些线程拥有优先权.我们知道CLH Node FIFO等待队列是一个先进先出的队列,那么是否就可以说每条线程获取锁时就是公平的呢?关于公平性这里分拆成三个点分别阐述:         ① 准备入队列的节

从JDK源码角度看线程池原理

        "池"技术对我们来说是非常熟悉的一个概念,它的引入是为了在某些场景下提高系统某些关键节点性能,最典型的例子就是数据库连接池,JDBC是一种服务供应接口(SPI),具体的数据库连接实现类由不同厂商实现,数据库连接的建立和销毁都是很耗时耗资源的操作,为了查询数据库中某条记录,最原始的一个过程是建立连接.发送查询语句.返回查询结果.销毁连接,假如仅仅是一个很简单的查询语句,那么可能建立连接与销毁连接两个步骤就已经占所有资源时间消耗的绝大部分,如此低下的效率显然让人无法接受.针

从JDK源码角度看并发竞争的超时

        JDK中的并发框架提供的另外一个优秀机制是锁获取超时的支持,当大量线程对某一锁竞争时可能导致某些线程在很长一段时间都获取不了锁,在某些场景下可能希望如果线程在一段时间内不能成功获取锁就取消对该锁的等待以提高性能,这时就需要用到超时机制.在JDK1.5之前并没有对此支持,当时的并发控制职能通过JVM内置的synchronized关键词实现锁,但对一些特殊要求却力不从心,例如超时取消控制.JDK1.5开始引入并发工具完美解决了此问题,JDK对并发线程开始提供超时的支持.       

从JDK源码角度看线程的阻塞和唤醒

        目前在Java语言层面能实现阻塞唤醒的方式一共有三种:suspend与resume组合.wait与notify组合.park与unpark组合.其中suspend与resume因为存在无法解决的竟态问题而被Java废弃,同样,wait与notify也存在竟态条件,wait必须在notify之前执行,假如一个线程先执行notify再执行wait将可能导致一个线程永远阻塞,如此一来,必须要提出另外一种解决方案,就是park与unpark组合,它位于JDK的juc包下,应该也是因为当时