【引言】
前天我有个朋友(字面意思)问了我关于字节码的一道题目,我发现这着实触及到了我的知识盲区,便找了些资料来看,看完了整型变量在计算机中的存储方式后我又顺藤摸瓜看了浮点数在计算机中的存储,然后在很多细节上就懵了,在看了不少人的博客后,整理了一些东西,以作备忘。
一、十进制的浮点数如何转化为二进制表示 1.方法整数部分正常转化为二进制表示,小数点不动,小数部分不断乘以2取整数部分,然后将小数部分重复上述步骤直到小数部分为0为止。
2.例子例:十进制17.625如何表示为二进制?
解1:首先是整数部分,十进制的17转化为二进制表示为 1 0001,小数部分为0.625,接下来计算小数部分的二进制表示。
0.625×2=1.25[1]
0.625 \times 2 = 1.25 \qquad [1]
0.625×2=1.25[1]
0.250×2=0.50[0]
0.250 \times 2 = 0.50 \qquad [0]
0.250×2=0.50[0]
0.500×2=1.00[1]
0.500 \times 2 = 1.00 \qquad [1]
0.500×2=1.00[1]
然后自上而下取得101,这就是小数部分的二进制表示。
因此最后的结果表示为 1 0001.101
解2:也可以用这样一种思想来进行二进制的转换,还是以17.625为例,可以小数点想象成数轴的零点,不过这个数轴是以左为正方向的,向左表示2的正数次幂,向右表示2的负数次幂。
那小数点右边的第一位就是2−12^{-1}2−1,也就是0.5。第二位就是2−22^{-2}2−2,也就是0.25,以此类推。我们又知道0.625=0.5+0.1250.625=0.5+0.1250.625=0.5+0.125,也就是可以得到0.625可以用二进制表示为0.1+0.001,因此0.625用二进制就可以表示为0.101。
因此17.625用二进制表示也就是 1 0001.101
二、float的表示方法在这里会说明float的表示方法,如何将一个浮点型转换为计算机内的存储形式,指数位和尾数位的求法会分开说明。
1.float的存储形式float类型的浮点数在计算机中用四个字节,也就是32个比特位来存储。不过这32位并不是每一位都用来表示数字大小的,而是将其分成了1+8+231+8+231+8+23三部分来表示,其表示法如下:
S⏟SignEEEEEEEE⏟ExponentMMMM⋅⋅⋅MMMM⏟Mantissa
\underbrace{S}_{Sign}\underbrace{EEEEEEEE}_{Exponent}\underbrace{MMMM \cdot\cdot\cdot MMMM}_{Mantissa}
SignSExponentEEEEEEEEMantissaMMMM⋅⋅⋅MMMM
至于指数位和尾数位是干什么用的,如何表示一个浮点数,请继续往下看。
2.如何得到一个浮点数的指数位float的指数位公式为浮点数的真指数(十进制表示)+127。
仍然以17.625为例,由前面我们已经知道了17.625的二进制表示为 1 0001.101,现在我们让这个数字的小数点不动,整体向右移动4位,这样小数点就位于第一位有效数字的右边。整体所移动的位数也就是这个浮点数的真指数,所以10001.101的真指数也就是4。由此,这个数字可以表示为1.0001101×241.0001101 \times 2^41.0001101×24。
其中,整体向右移为正数,向左移为负数。
然后将4加上127后再转换为二进制,即为浮点数的指数位表示。
4+127=131
4+127=131
4+127=131
131[10]=>1000 0011[2]
131_{[10]}=>1000 \ 0011_{[2]}
131[10]=>1000 0011[2]
因此17.625的指数位即为 1000 0011。
3.为什么是127?我在看视频的时候看到人家说把真指数加上127再转换为二进制就是这个浮点数的指数位,这个时候我小小的脑袋里出现了大大的疑惑:为什么要加上127?这个数字难道长得比其他数字好看吗?于是我又继续去看博客和答疑,看过之后仍是一头雾水,反而多出了更多不明白的地方。我万万没想到的是最后竟然在我不屑一顾的百度百科上找到了一丝头绪,然后又顺藤摸瓜弄懂了一点东西,写下来希望能够帮我和我一样菜的同学,已经明白的朋友或者是不想明白的朋友可以跳过此节继续往下看。
在说这个神奇的127之前,就不得不提到IEEE 754这个东西,首先这个东西是一个标准,全称为IEEE二进制浮点数算数标准。它规定了四种表示浮点数值的方式,单精确度(32位),双精确度(64)位和其他两种我没有听说过的类型,所以这个不明觉厉127就是这个IEEE 754规定的。
那么问题来了:挖掘机 它为什么要做如此规定呢?
这样做的好处就是可以使得浮点数指数大小的比较更为简单。
那如果没有采用这样的形式会产生什么样的结果呢?
如果浮点数的指数位就是这个浮点数的真指数的话,那么8个比特位所能表示的范围就是-127到+128,也就是说这是一个有符号的表示,那么我们知道在计算机的存储当中是用补码来存储二进制数据的,因此如果用有符号数来表示指数的话就会使得指数之间的比较很麻烦 两个符号位再加上补码想想都觉得麻烦,所以IEEE 754规定了一个东西叫做指数偏移值(exponent bias),且该值为固定值2e−1−12^{e-1}-12e−1−1,其中eee为存储指数的比特长度,在单精度中e=8e=8e=8,因此单精度表示的浮点数的指数偏移值就是
28−1−1=27−1=128−1=127
2^{8-1}-1=2^7-1=128-1=127
28−1−1=27−1=128−1=127
这样子,当真指数加上一个偏移值所得到的就是一个无符号的正整数,通过这种方式得到的指数部分,称作为阶码。
此处打个比方,假如我们有一把尺子,总长为2e2^e2e。尺子的中点被标定为0刻度线,向左为负刻度值,最小为−2e−1+1-2^{e-1}+1−2e−1+1。向右为正刻度值,最大为2e−12^{e-1}2e−1。现在我们想重新标定这把尺子使其刻度全为正数,那么显而易见的一个修正方法就是把负刻度值全都加上一个固定的数值,使其最小的负数−2e−1+1-2^{e-1}+1−2e−1+1刚好被修正为0刻度,那么这个数很明显就是这个最小负数的相反数 2e−1−12^{e-1}-12e−1−1 啦!
那么当修正之后,阶码所能表示的范围是多少呢?
既然是8位无符号整数,那不就是0~255吗?
实际上它表示的范围为1~254,IEEE 754把其中的0和255拿出来另作他用,用来表示为浮点数中的两个特殊值,关于这两个特殊值的介绍会在下面提到,继续往下看吧!
4.浮点数的尾数位我们在刚才已经求出了17.625的指数部分,那么现在我们再来看一下浮点数最后的部分——尾数部分。
尾数的求法就简单了,刚才我们不是把17.625这个数也就是10001.101整体向右移了嘛,也就变成了1.0001101,它的小数部分就是浮点数的尾数部分啦!
刚刚我们分别讨论的浮点数指数部分和尾数部分的求法,现在我们把他们合起来再回顾一下,仍然以17.625为例:
【二进制表示】
17.625[10]=10001.101[2]17.625_{[10]}=10001.101_{[2]}17.625[10]=10001.101[2]
【符号位】
很明显,它是个正数,因此符号位为0。
【指数部分】
10001.101的第一位有效数字是最左边的1,因此右移4位,然后把4加上127得到131,131转换为二进制是 1000 0011,这就是指数部分。
【尾数部分】
1 0001.101右移4位后得到1.000 1101,因此尾数部分为000 1101。
尾数部分不是一共有23位嘛,剩下的位数咋办?
很简单,用0补齐。
用0不用1并不是因为歧视1
【综上】
17.625[10]=0⏟Sign10000011⏟Exponent00011010000000000000000⏟Mantissa [2]
17.625_{[10]} =
\underbrace{0}_{Sign}
\underbrace{10000011}_{Exponent}
\underbrace{0001 1010 0000 0000 0000000}_{Mantissa} \ _{[2]}
17.625[10]=Sign0Exponent10000011Mantissa00011010000000000000000 [2]
此处给出公式:
(−1)S×2E−127×(M+1)
(-1)^S \times 2^{E-127} \times (M+1)
(−1)S×2E−127×(M+1)
写二进制太麻烦了就不举例子了
【0】
当阶码全为0且尾数也全为0时,表示的真值即为0。这里的阶码全为0也就是我们刚刚提到的被拿走了的0。
【∞\infty∞】
当阶码全为1且尾数全为0时,表示的真值为无穷大,即+∞\infin∞。
在这里阶码全为1,就是刚才也被拿走的255。
再加上前面的符号位,便可以表示+∞\infin∞ 和-∞\infin∞。
【特殊值】
符号位 | 指数位 | 尾数位 | 真值 | 含义 |
---|---|---|---|---|
0 | 全为0 | 全为0 | +0 | +0 |
1 | 全为0 | 全为0 | -0 | -0 |
0 | 全为1 | 全为0 | +∞\infin∞ | 正无穷大 |
1 | 全为1 | 全为0 | -∞\infin∞ | 负无穷大 |
0/1 | 全为1 | 不全为0 | NaN | 非数字特殊值 |
我们所要求的float的表示范围,即是要求float所能表示的最大的绝对值,想要绝对值最大,那么阶码和尾数位都要最大。
上面已经讨论过,阶码的范围为1到254,因此真指数的值的范围是-126到127。所以最大的真指数为127。
我们想要一个最大的数值的话,那也就将尾数都变成1。
那么由上面的公式我们可以得到最大的绝对值为
0.11111111111111111111111×2127
0.11111111111111111111111 \times 2^{127}
0.11111111111111111111111×2127
这个值为
340282346638528859811704183484516925440
340282346638528859811704183484516925440
340282346638528859811704183484516925440
一般表示为
3.4028235E38
3.4028235E38
3.4028235E38
因此,float可表示的范围是:
[−340282346638528859811704183484516925440,340282346638528859811704183484516925440]
[-340282346638528859811704183484516925440, 340282346638528859811704183484516925440]
[−340282346638528859811704183484516925440,340282346638528859811704183484516925440]
或表示为
[−3.4028235E38,3.4028235E38]
[-3.4028235E38, 3.4028235E38]
[−3.4028235E38,3.4028235E38]
至此,我想知道的就到这儿了。
四、后记花了三节课的时间,总算把我人生中第一篇正儿八经的博客写完了。
markdown是挺好用的,敲二进制数看的我眼都快没了。
我知道里面肯定有理解不到位或是不对的地方,希望各位看官能提出意见,帮我指出问题,不胜感激。
这里特别感谢小爬同学用她不太好使的小脑瓜给我提出宝贵的疑惑,触及到我无穷尽的知识盲区在查阅资料学习的时候有一点我深有体会:
所知越多,越能发现自己的无知。
理解浅显,所知有限,望各位指点。 2020.2.25 wsz