3.3 浮点数表示法
内容导视:
- 小数对应的二进制
- 科学计数法
- 浮点数表示法
- 最大值、最小值
- 特殊值
3.3.1 小数对应的二进制
之前漏掉了小数对应的二进制,现补上。
二进制转十进制
从个位数开始向左计算,个位数乘以 2 的 0 次方,十位数乘以 2 的 1 次方,百位数乘以 2 的 2 次方…
从十分位开始向右计算,十分位乘以 2 的 -1 次方,百分位乘以 2 的 -2 次方…
然后将每个式子的结果相加。
例 1:0b101.11 转为十进制
最高位是百位,从 1 * 2 ^ 2 开始
最低位是百分位,到 1 * 2 ^ -2 结束
0b101.11 =
1 * 2 ^ 2
+ 0 * 2 ^ 1
+ 1 * 2 ^ 0
+ 1 * 2 ^ -1
+ 1 * 2 ^ -2
= 4 + 1 + 0.5 + 0.25 = 5.75
例 2:0b111.01 转为十进制
0b111.01 =
1 * 2 ^ 2
+ 1 * 2 ^ 1
+ 1 * 2 ^ 0
+ 0 * 2 ^ -1
+ 1 * 2 ^ -2
= 4 + 2 + 1 + 0 + 0.25 = 7.25
可以看到小数部分都是由 0.5、0.25、0.125、0.0625… 等数组合表示,前面的系数 1 或 0,0.625 = 1 * 0.5 + 0 * 0.25 + 1 * 0.125,所以 0.625 对应的二进制为 0.101。
例 3:0100.4 转为十进制,八进制转为十进制,同上过程,将 2 换成 8。
0100.4 =
1 * 8 ^ 2
+ 4 * 8 ^ -1
= 64 + 0.5 = 64.5
100 = 0000100,1.1 = 1.10000,补 0 是为了方便转换为其它进制,参考 3.1.2 二进制与八进制互转。
4 = 04 = 0b100 = 0b0100 = 0x4,1 * 80 + 4 * 8-1 = 1.5 = 01.4 = 0b1.100 = 0b1.1000 = 0x1.8
十进制转二进制
整数部分除以 2 得到商,再将商除以 2,如此反复,直到商为 0,然后将每步得到的余数倒过来,就是对应的二进制。
小数部分乘以 2 得到积,取整数部分,余下的部分再次乘以 2,重复步骤,直到余数为 0,取出的整数合并再添上前缀 0. 就是小数对应的二进制。
再将整数和小数部分对应的二进制合并。
使用二进制表示小数可能会有精度损失。
例 1:0.75 转为二进制
0.75 * 2 = 1.5,取 1,余 0.5
0.5 * 2 = 1,取 1,余 0
所以 0.75 对应的二进制为 0b0.11
例 2:3.4 转为二进制
整数部分 3 转为二进制为 0b11
小数部分 0.4
0.4 * 2 = 0.8,取 0,余 0.8
0.8 * 2 = 1.6,取 1,余 0.6
0.6 * 2 = 1.2,取 1,余 0.2
0.2 * 2 = 0.4,取 0,余 0.4
0.4 * 2 = 0.8,取 0,余 0.8
...
所以小数部分 0.4 对应的二进制为 0b0.0110 0110 0110... 的循环
3.4 对应的二进制为 0b11.0110 0110 0110...
例 3:123.123 转为二进制
123/2,商 61,余 1
61/2,商 30,余 1
30/2,商 15,余 0
15/2,商 7,余 1
7/2,商 3,余 1
3/2,商 1,余 1
1/2,商 0,余 1
所以 123 对应的二进制为 0b1111011
0.123 * 2 = 0.246,取 0,余 0.246
0.246 * 2 = 0.492,取 0,余 0.492
0.492 * 2 = 0.984,取 0,余 0.984
0.984 * 2 = 1.968,取 1,余 0.968
0.968 * 2 = 1.936,取 1,余 0.936
0.936 * 2 = 1.872,取 1,余 0.872
0.872 * 2 = 1.744,取 1,余 0.744
0.744 * 2 = 1.488,取 1,余 0.488
0.488 * 2 = 0.976,取 0,余 0.976
...
0.123 对应的二进制为 0b0.000111110...
所以 123.123 对应的二进制为 0b1111011.000111110…
3.3.2 科学计数法
十进制表示法(E)
1.4E-45 = 1.4 * 10-45,1.4 称为尾数(Mantissa),10 为基数、底数(Base),-45 为指数(Exponent),都是十进制表示。
尾数在 [1.0,10) 之间的表示方法称为 modified normalized form,正是 Java 所使用的。
尾数在 [0.0,1.0) 之间的表示方法称为 true normalized form。
例:
-
3.4028235E38 = 3.4028235 * 1038
-
999999 = 9.99999 * 105。
-
0b10010101 = 0b1.0010101 * 27(对于二进制而言,每乘以 2 就相当于小数点往右移动一位)
Java 中,当小数超出 [-9999999,9999999] 范围时,会使用科学计数法表示。
System.out.println(-10000000.0);// -1.0E7
思考控制台输出什么结果?
System.out.println(500e-2);
答:500e-2 = 500 * 10-2 = 500 / 102 = 5.0
科学计数法默认被当作 double 类型来处理,所以小数不能掉。
System.out.println(-10000000);
System.out.println(500E-7);
第 1 个不是小数,原样输出。第 2 个超过上述所说的范围,那使用科学计数法表示,尾数应在 [1.0,10) 之间,输出 5.0E-5
。
十六进制表示(P)
在十六进制表示中,2 为底数。
例 1:0xa.0p-1 转为十进制
0xa.0 转为十进制为 10
0xa.0p-1 = 10 * 2 ^ -1 = 10 / 2 = 5.0
例 2:0x19.0p-2 转为十进制
0x19.0 转十进制
0x19.0 =
1 * 16 ^ 1
+ 9 * 16 ^ 0
= 16 + 9 = 25
0x19.0p-2 = 25 * 2 ^ -2 = 25 / 4 = 6.25
例 3:0x0.12p2 转为十进制
0x0.12 转十进制
0x0.12 =
1 * 16 ^ -1
+ 2 * 16 ^ -2
= 1/16 + 2/256 = 0.0703125
0x0.12p2 = 0.0703125 * 2 ^ 2 = 0.28125
例 4:0b1.0010101 * 27 转为十进制
0b1.0010101 * 2 ^ 7 = 0b10010101 = 1 + 4 + 16 + 128 = 149
0b1.0010101 = 0b1 + 0b0.0010101 = 1 + 0.125 + 0.03125 + 0.0078125 = 1.1640625
1.1640625 * 2 ^ 7 = 1.1640625 * 128 = 149
0b1.00101010 = 0x1.2a = 1 + 2/16 + 10/256 = 1 + 0.125 + 0.0390625 = 1.1640625
1.1640625 * 2 ^ 7 = 149
3.3.3 浮点数表示法
浮点数如何表示小数?
分为四部分表示:符号、尾数、基数和指数。
例:0b1010.11101 * 210
符号:+,尾数:1010.11101,基数:2,指数:10。
浮点数有两种类型:单精度浮点数(float:32bit)和双精度浮点数(double:64bit)。
类型 | 总长度 | 符号 | 阶码 | 尾数 |
---|---|---|---|---|
float | 32 bit | 1 | 8 | 23 |
double | 64 bit | 1 | 11 | 52 |
符号
1 表示负,0 表示正。
阶码
也称指数位,因为指数有正、负,为了避免使用符号位,方便比较、排序,采用了 The Biased exponent(有偏指数)。[1]
IEEE754 规定,2e-1 – 1 的值是 0(e 是阶码部分的位数),小于这个值表示负数,大于这个值表示正数。因此,对于单精度浮点数而言,127 是 0;双精度浮点数中 1023 是 0。
假设指数为 -5,使用单精度浮点数表示时,应存储 -5 + 127 = 122 的二进制 0111 1010。
指数为 1023,使用双精度浮点数表示时,应存储 1023 + 1023 = 2046 的二进制 11111111110。
8 位可以表示的值:00000000 ~ 11111111 即 0 ~ 255,其中 00000000 和 11111111 被用作特殊情况,所以可用范围只有 1 ~ 254,用来表示负数,如果取 128 为 0,可以表示 -127 ~ 126,取 127 为 0,可以表示 -126 ~ 127,表示的数更大。
11 位可用范围:00000000001 ~ 11111111110 即 1 ~ 2046,取 1023 为 0,可以表示 -1022 ~ 1023。
尾数
一个小数既可以使用 0b1010.11101 * 210 表示,也可以使用 0b1.01011101 * 213 表示。
通过移位,将小数点前面的值固定为 1。IEEE754 称这种形式的浮点数为规范化浮点数(normal number)。
如 0b0.00101 = 0b1.01 * 2-3
因为规定第 1 位永远为 1,因此可以省略不存,这样尾数部分多了 1 位,只需存 01。
因此对于规范化浮点数,尾数其实比实际的多 1 位,也就是说单精度的是 24 位,双精度是 53 位。为了作区分,IEEE754 称这种尾数为 significand。
基数
默认为 2,不参与存储。
[1] 为什么使用移码表示阶码
工具网址
- 进制转换
转载文章
- https://baijiahao.baidu.com/s?id=1679268252794098534
3.3.4 一些例子
加空格只是为了更好的观察。
例 1:用 float 表示 329.301
329.301 的二进制为 0b101001001.01001101000011100101011000000100000110001001001101111… = 0b1.0100100101001101000011100101011000000100000110001001001101111… * 28
符号位:0
阶码:8 + 127 = 135,对应的二进制为:10000111
尾数取出 23 位:01001001 01001101 0000111(规格化表示,1 省略不存)
所以单精度浮点数 329.301 对应的二进制内存表示是:0 10000111 01001001010011010000111
// 329.301 的二进制内存表示对应的整数
int i = Float.floatToIntBits(329.301f);
// 329.301 的二进制内存表示
Object s = Integer.toBinaryString(i);
System.out.println(i);// 1134864007
System.out.println(s);// 1000011 10100100 10100110 10000111
// -1 的补码为 11111111 11111111 11111111 11111111
System.out.println(Integer.toBinaryString(-1));
// -1 的补码为 ffffffff
System.out.println(Integer.toHexString(-1));
// 加下划线不影响实际数值
System.out.println(0b11111111_11111111_11111111_11111111);
System.out.println(0xffffffff);
例 2:用 double 表示 -666.875
666.875 实际对应的二进制为 0b1010011010.111 = 0b1.010011010111 * 29
符号位:1
阶码:9 + 1023 = 1032,对应的二进制为:10000001000
尾数位:010011010111,不够 52 位补 0。
所以双精度浮点数 -666.875 对应的二进制内存表示是:1 10000001000 0100110101110000000000000000000000000000000000000000
long l = Double.doubleToLongBits(-666.875);
Object s = Long.toBinaryString(l);
// -4574294926501609472
System.out.println(l);
// 1100000010000100110101110000000000000000000000000000000000000000
System.out.println(s);
例 3:单精度浮点数内存表示:00111101110011001100110011001101,求它对应的小数
符号位:0,正数
阶码取 8 位:01111011,转为十进制 123,123 – 127 = -4,指数为 -4
尾数取 23 位:10011001100110011001101,尾数为 1.10011001100110011001101
小数的二进制表示为 0b1.10011001100110011001101 * 2-4 = 0b0.000110011001100110011001101
0b0.000110011001100110011001101 对应的小数:0.10000000149011612…,由于 float 精度最多只能表示 8 位,所以对应的小数是 0.1。
float v = Float.intBitsToFloat(0b00111101110011001100110011001101);
System.out.println(v);// 0.1
3.3.5 浮点数精度
浮点数的精度是指浮点数的有效数字的最大位数,从左边第一个不为 0 的数字开始的个数开始算起。
System.out.println(0.1111111111111f);//0.11111111
可以发现超过了 8 位后的数字都被舍弃,float 最多只能表示 8 位有效数字。
例 0b0.01010010 11101001 01010100 10101010 = 0b1.010010 11101001 01010100 10101010 * 2-2
使用单精度浮点数表示,只能存储 23 位尾数,也就是 010010 11101001 01010100 1,丢掉了一部分,导致精度损失。
大部分文章的解释
单精度浮点数尾数有 23 位,加上默认的 1,2 ^ (23+1)= 16777216。因为 10^7 < 16777216 < 10^8,所以说单精度浮点数的有效位数是 7 位,部分可以达到 8 位。
双精度浮点数尾数有 52 位,2 ^ (52+1)= 9007199254740992,10^15 < 9007199254740992 < 10^16,所以双精度的有效位数是 15 位,部分可以达到 16 位。
说实话,这个解释不大满意,之前的二、十进制转换,求余得到二进制,勉强可以不探究原理;这里就不太好理解,难道是指 24 位全取 1,是最大值,再大就表示不了的意思?只能被迫舍去一些位?
没办法只能自己做实验了,以单精度浮点数为例。
整数部分
23 = 0b10111 = 0b1.0111 * 24
如果尾数部分只能存 1 位,即 0,后面的 111 被舍弃,实际存储的值为 0b1.0 * 24 = 0b10000 = 16,那么连一位有效数字都保证不了。
9 = 0b1001 = 0b1.001 * 23,如果尾数只能存 1 位,即 0,后面的 01 被舍弃,实际存储的值为 0b1000 = 8;如果尾数有 3 位,精度才不会损失。
15 = 0b1111 = 0b1.111 * 23,要想精确表示,应存储 111,尾数至少 3 位。
要想表示 1 ~ 9 范围内的所有整数:0001 ~ 1001,需要 4 位二进制,尾数至少需要 3 位。
表示 10 ~ 99:1010 ~ 1100011,至少需要 6 位尾数,如 1100011 = 1.100011 * 26,存储 100011 可以保证精度不损失。
…
表示 106 ~ 107:11110100001001000000 ~ 100110001001011010000000,至少需要 23 位尾数。
24 位二进制能表示的最大值:16777215,超过了此数,如 16777217,0b1000000000000000000000001 = 0b1.000000000000000000000001,存储 23 位 0,第 24 位的 1 被略去,精度损失。
小数部分可参考:https://blog.csdn.net/pkxpp/article/details/103059502
意思大概是小数由 0.5、0.25、0.125、0.0625、0.003125、0.015625、…等单位组合而成。
如 0.625 = 1 * 0.5 + 0 * 0.25 + 1 * 0.125,则 0.625 = 0b0.101
如果要表示的数比最小单位还小,如用 0.5、0.25、0.125、0.0675、0.03125、0.015625 组合表示不了 0.001,精度只能到十分位 0.1,有时可以到百分位。
例,六位二进制表示 0.175,最贴近的是 0b0.001011 = 0.171875。
要想保证精确到小数点后面 3 位,2x < 10-3,求得 x的最大值为 -10,最小单位 0.0009765625,用十位二进制表示 0b0.0010110100 = 0.17578125。
23 位尾数,能够表示的最小单位 2-24 = 0.0000000590444775 < 10-7,可以精确到小数点后 7 位,部分 8 位。
小数部分的解释,我不大满意,因为有负指数的存在。
0.1 + 0.2 = ?
使用单精度浮点数表示;
0.1 = 0b0.0001 10011001 10011001 1001100 1100110011001100110011001101…
第 23 位是否进位,看后面的位数是否大于 10000000 00000000 00000000 0000010 00000000…剩下都是 0,设此二进制为 b。
这是小数部分,可以无限往后填充 0,按位比就行。
例,比较 11 与 b,第 1 位相等,都是 1;第 2 位的 1 大于 0,所以 11 更大。
如 0b1.11101010 10101010 1010111 1011,1011 > b,所以第 23 位进位 + 1,应存储 11101010 10101010 1011000,多余的 1011 被舍弃。
如 0b1.10100101 10101111 1111010 01,01 < b,应存储 10100101 10101111 1111010。
尾数存储 23 位,实际值为 0b0.0001 10011001 10011001 1001101 = 0.10000000149011612
0.2 = 0b0.001 10011001 10011001 1001100 1100110011001100110011001101…
实际值 0b0.001 10011001 10011001 1001101 = 0.20000000298023224
所以 0.1 + 0.2 = 0.10000000149011612 + 0.20000000298023224 = 0.30000000447034836
得到的结果大于 0.3,不过单精度浮点数由于精度不够,表示不出来。
System.out.println(0.1f + 0.2f);// 0.3
System.out.println(0.1 + 0.2);// 0.30000000000000004
3.3.6 最大值、最小值
单精度浮点数最大值
8 位可以表示的值:00000000 ~ 11111111 即 0 ~ 255,其中 00000000 和 11111111 被用作特殊情况,所以可用范围只有 1 ~ 254,用来表示负数,取 127 为 0,可以表示 -126 ~ 127。
单精度最大指数为 127,最小指数 -126。
双精度最大指数为 1023,最小指数 -1022。
由于阶码 0b11111111 被用作特殊情况,最大指数应为 0b11111110 – 127 = 254 – 127 = 127。
在内存中的表示:0 11111110 11111111111111111111111
1/2 + 1/4 = 1 – 1/4
1/2 + 1/4 + 1/8 = 1 – 1/8
…
1/2 + 1/4 + … + 1/223 = 1 – 1/223
单精度浮点数最大值为 0b1.11111111 11111111 1111111 * 2127 = 2127 + 2126 + … + 2104 = 2127 * [1 + 1/2 + 1/4 + … + 1/223] = 2127 * [1 + 1 – 1/223] = 2128 – 2104 = 3.4028235E38
0b1.11111111 11111111 1111111 * 2127 = 0x1.fffffe * 2127。
单精度浮点数最小正值
使用规范化浮点数时,尾数默认省略了前导数 1,导致 0 无法表示。
即使尾数全部取 0,0b1.0000… * 2n = 1 * 2n,也无法精确表示 0。
所以规定了另一种浮点数:
当指数位全是 0 时,尾数部分的前导数为 0,同时指数部分的偏移值比规范形式的偏移值小 1。如单精度浮点数取 126 为 0。
最小指数 0b00000000 – 126 = -126。
最小正值在内存中的表示:0 00000000 00000000000000000000001
单精度浮点数最小正值为 0b0.0000 0000 0000 0000 0000 001 * 2-126 = 2-23 * 2-126 = 2-149 = 1.4e-45
0b0.0000 0000 0000 0000 0000 001 * 2-126 = 0x0.000002 * 2-126
双精度浮点数最大值
内存表示:0 11111111110 1111111111111111111111111111111111111111111111111111
指数的最大值为 0b11111111110 – 1023 = 2046 – 1023 = 1023
这里使用二进制表示太长,转为十六进制表示
最大值 0x1.fffffffffffff * 21023 = 21023 * [1 + 1 – 1/252] = 21024 – 2971 = 1.7976931348623157e308
双精度浮点数最小正值
内存表示:0 00000000000 0000000000000000000000000000000000000000000000000001
指数的最小值为 0b00000000000 – 1022 = -1022
最小正值 0x0.0000000000001 * 2-1022 = 2-52 * 2-1022 = 2-1074 = 4.9e-324
3.3.7 特殊值
为了表示 -1.0 / 0.0、1.0 / 0.0、0.0 / 0.0 等特殊数值,定义了三种类型 -Infinity(负无穷大)、Infinity(正无穷大)、NaN(非数字)。
无穷大
当指数位全为 1,尾数位全为 0,表示为无穷大,当一个数超出了浮点数的表示范围,就可以使用 Infinity 表示。符号为 0,是正无穷大,符号为 1,负无穷大。
单精度浮点数 Infinity 内存表示 0 11111111 0000000000000
双精度浮点数 -Infinity 内存表示 1 11111111111 0000000000000000000000000000000000000000000000000000
0b1111 1111 1111 0000000000000000000000000000000000000000000000000000 = 0xfff00…
// 0111 1111 1111 0000000...,是正无穷大
double d1 = Double.longBitsToDouble(0x7FF0000000000000L);
// 1111 1111 1000 00000...,是负无穷大
float f1 = Float.intBitsToFloat(0xFF800000);
非数字
符号位任意,指数位全为 1,尾数位不全为 0(至少有 1 位是 1),表示不是数,比如用 NaN 表示 -1 的平方根。
特性:
-
不等于自身,可以利用此特性判断一个数是否是 NaN
// 1 11111111 1110... // fff00000 float f1 = Float.intBitsToFloat(0xfff00000); System.out.println(f1 == f1);// false
public static boolean isNaN(float v) { return (v != v); }
-
NaN 参与运算,最终结果还是 NaN
今天的文章单精度浮点数和双精度浮点数举例_float默认保留几位小数「建议收藏」分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:http://bianchenghao.cn/82355.html