1. 小数
二进制小数:abc.def
十进制小数:a * 2^2 + b * 2^1 + c * 2^0 + d * 2^(-1) + e * 2^(-2) + f * 2^(-3)
2. 浮点表示
IEEE浮点标准用 V=(-1)^S * M * 2^E 的形式来表示一个数。
符号S决定这个数是正数(S=0)还是负数(S=1)
尾数M是一个二进制小数,范围是[0,2)。
阶码E是权重,表示2的E次幂(可以是负数)。
它们的二进制编码方式如下:
用一个单独的符号位s编码S,
用k位阶码字段exp来编码E,
用n位小数字段frac编码M。
在单精度浮点格式中(C语言的float),s=1位,exp=8位,frac=23位。
双精度浮点格式中(C语言的double),s=1位,exp=11位,frac=52位。
3. 对浮点表示的解释
(1)如果阶码exp不全为0(数值0),也不全为1(单精度255,双精度2047)时
设偏置值Bias=2^(k-1) - 1,(单精度127,双精度1023)
且exp看做无符号整数的值为e,
则exp表示阶码E的值为,E=e-Bias。
小数字段frac解释为,尾数M=1.frac,
这里隐含以1开头,小数部分是frac中的各个二进制位。
(2)当阶码exp全为0时
阶码值E=1-Bias,尾数M=0.frac,
这里不包含隐含的1。
如果此时尾数frac也全为0,s=0表示这+0,s=1表示-0。
(3)当阶码exp全为1时
若小数域frac全为0,则表示无穷,s=0表示正无穷,s=1表示负无穷。
若小数域frac非零,则表示NaN。
4. 例子
单精度格式的浮点表示:00111111101000000000000000000000
按位切分一下,0,01111111,01000000000000000000000
则s=0,exp=01111111,frac=01000000000000000000000
S=0
e=127,Bias=127,则E=e-Bias=0
M=1.01000000000000000000000=1.01
所以,V=(-1)^S * M * 2^E=1.01
转换为十进制,1 * 2^0 + 0 * 2^-1 + 1 * 2^-2 = 1.25
5. 舍入方式
IEEE浮点格式定了4种不同的舍入方式。
默认的方式是找到最近接的匹配,如果没有最接近的值则向偶数舍入。
例如,我们想将十进制数舍入到最接近的百分位,
则1.2349999舍入到1.23,1.2350001舍入到1.24(最接近匹配)
而1.2350000舍入到1.24,1.2450000也舍入到1.24(向偶数舍入)
6. 双精度格式,0.1+0.2=?
十进制0.1转换成二进制小数为0.0[0011]...
十进制0.2转换成二进制小数位0.[0011]...
其中[]表示循环小数部分。
表示成双精度浮点格式,
(1)0.1
0.0[0011]...=(-1)^0 * 1.[1001]... * 2^(-4)
S=0,M=1.1001...1001,E=-4
1.1001...1001 1001 1,舍入(最接近匹配+向偶数舍入)
=1.1001...1001 1010,(这里进位了,关键)
则s=0,frac=1001...1001 1010,exp=-4+1023=01111111011
其中frac共52位
浮点格式为:0,01111111011,1001...1001 1010
(2)0.2
0.[0011]...=(-1)^0 * 1.[1001]... * 2^(-3)
S=0,M=1.[1001]...,E=-3
则s=0,frac=1001...1001 1010,exp=-3+1023=01111111100
其中frac共52位
浮点格式为:0,01111111100,1001...1001 1010
(3)相加
(-1)^0 * 1.1001...1001 1010 * 2^(-4) + (-1)^0 * 1.1001...1001 1010 * 2^(-3)
=(-1)^0 * 0.1 1001...1001 1010 * 2^(-3) + (-1)^0 * 1.1001...1001 1010 * 2^(-3)
0.1 1001...1001 1010
=0.1100...1100 1101 0,舍入(最接近匹配+向偶数舍入)
=0.1100...1100 1101
相加
0.1100...1100 1101 + 1.1001...1001 1010
=10.0110...0110 0111
=1.0 0110...0110 0111 * 2^1
=1.0011...0011 0011 1 * 2^1,舍入(最接近匹配+向偶数舍入)
=1.0011...0011 0100 * 2^1,(又一次进位了,关键)
最终结果为,
(-1)^0 * 1.0011...0011 0100 * 2^1 * 2^(-3)
=(-1)^0 * 1.0011...0011 0100 * 2^(-2),小数部分共52位
=0.01 0011...0011 0100,(这里小数部分就变成了54位了)
转换成十进制,
0.300000000000000044408920985006261616945266723632812500
注:
为了能计算精确的十进制表示,
我们将(1/2)^n 表示成了5^n /10^n ,然后用10^54通分,
得到0.01 0011...0011 0100的十进制表示为,
(0 * 5^1 * 10^53 + 1 * 5^2 * 10^52 +...) / 10^54
找一个可以计算长整数的编程语言,例如Racket,可得如上结果。
#lang racket
(define str "010011001100110011001100110011001100110011001100110100")
(define num-lst
(map (lambda (chr)
(- (char->integer chr)
(char->integer #\0)))
(string->list str)))
(define num-len
(length num-lst))
(define (sum lst cur)
(if (= (length lst) 0)
0
(let [(head (car lst))
(tail (cdr lst))]
(let [(res (* head
(expt 5 cur)
(expt 10 (- num-len cur))))]
(+ res (sum tail (+ cur 1)))))))
(sum num-lst 1)