当Ex≠Ey 时,要通过
尾数的移动以改变Ex或Ey,使之相等。原则上,既可以通过Mx移位以改变Ex来达到Ex=Ey,也可以通过My移位以改变Ey来实现Ex=Ey。但是,由于浮点表示的数多是
规格化的,尾数左移会引起
最高有效位的丢失,造成很大误差。
尾数右移虽引起最低有效位的丢失,但造成误差较小。因此,
对阶操作规定使
尾数右移,尾数右移后阶码作相应增加,其数值保持不变。显然,一个增加后的阶码与另一个阶码相等,增加的阶码的一定是小阶。因此在
对阶时,总是使小阶向大阶看齐,即小阶的尾数向右移位(
⑶ 尾数求和运算
对阶结束后,即可进行尾数的求和运算。不论
加法运算还是减法运算,都按加法进行操作,其方法与定点加减法运算完全一样。
在浮点加减运算时,
尾数求和的结果也可以得到01.ф…ф或10.ф…ф,即两符号位不等,这在定点加减法运算中称为溢出,是不允许的。但在浮点运算中,它表明
尾数求和结果的绝对值大于1,向左破坏了
规格化。此时将运算结果右移以实现
规格化表示,称为向右规格化。规则是:
尾数右移1位,阶码加1。当
尾数不是1.M时需向左
规格化。
⑸ 舍入处理
在
对阶或向右规格化时,
尾数要向右移位,这样,被右移的尾数的低位部分会被丢掉,从而造成一定
误差,因此要进行舍入处理。
在
IEEE754标准中,舍入处理提供了四种可选方法:
尾数超出规定的23位的多余位数字是10010,多余位的值超过规定的最低有效位值的一半,故最低有效位应增1。若多余的5位 是01111,则简单的截尾即可。对多余的5位10000这种特殊情况:若最低有效位现为0,则截 尾;若最低有效位现为1,则向上进一位使其变为 0。
朝0舍入 即朝
数轴原点方向舍入,就是简单的截尾。无论
尾数是正数还是负数,截尾都使取值的
绝对值比原值的绝对值小。这种方法容易导致误差积累。
朝+
∞舍入 对
正数来说,只要多余位不全为0则向最低有效位进1;对
负数来说则是简单的截尾。
朝-∞舍入 处理方法正好与 朝+∞舍入情况相反。对
正数来说,只要多余位不全为0则简单截尾;对
负数来说,向最低有效位进1。
⑹ 溢出处理
浮点数的溢出是以其阶码溢出表现出来的。在加\u51cf运算过程中要检查是否产生了溢出:若阶码正常,加(减)运算正常结束;若阶码溢出,则要进行相应处理。另外对
尾数的溢出也需要处理。
阶码上溢 超过了阶码可能表示的最大值的正指数值,一般将其认为是+∞和-∞。
阶码下溢 超过了阶码可能表示的最小值的负指数值,一般将其认为是0。
尾数
上溢 两个同符号尾数相加产生了最高位向上的进位,将尾数右移,阶码增1来重新对齐。
尾数下溢 在将尾数右移时,尾数的最低有效位从尾数域右端流出,要进行舍入处理。
实例
题目
例如,一个指数范围为±4的4位十进制浮点数可以用来表示43210,4.321或0.0004321,但是没有足够的精度来表示432.123和43212.3(必须近似为432.1和43210)。当然,实际使用的位数通常远大于4。
特别数值
此外,浮点数表示法通常还包括一些特别的数值:+∞和−∞(正负
无穷大)以及NaN('Not a Number')。无穷大用于数太大而无法表示的时候,NaN则指示非法操作或者无法定义的结果。
二进制表示
众所周知,计算机中的所有数据都是以
二进制表示的,浮点数也不例外。然而浮点数的二进制表示法却不像
定点数那么简单了。
浮点数概念
先澄清一个概念,浮点数并不一定等于
小数,定点数也并不一定就是整数。所谓浮点数就是小数点在逻辑上是不固定的,而
定点数只能表示小数点固定的数值,具用浮点数或定点数表示某哪一种数要看用户赋予了这个数的意义是什么。
C++中的浮点数有6种,分别是:
unsigned float:
单精度无符号,32位
double:双精度,64位
下面仅以float(带符号,
单精度,32位)类型的浮点数说明C++中的浮点数是如何在内存中表示的。先讲一下基础知识,
纯小数的二进制表示。
纯小数要想用二进制表示,必须先进行
规格化,即化为 1.xxxxx * ( 2 ^ n ) 的形式(“^”代表乘方,2 ^ n表示2的n次方)。对于一个纯小数D,求n的公式如下:
n = 1 + log2(D); // 纯小数求得的n必为负数
再用 D / ( 2 ^ n ) 就可以得到
规格化后的小数了。接下来就是
十进制到
二进制的转化问题,为了更好的理解,先来看一下10进制的纯小数是怎么表示的,假设有纯小数D,它
小数点后的每一位数字按顺序形成一个
数列:
{k1,k2,k3,...,kn}
那么D又可以这样表示:
D = k1 / (10 ^1 ) + k2 / (10 ^ 2 ) + k3 / (10 ^ 3 ) + ... + kn / (10 ^ n )
推广到二进制中,纯小数的表示法即为:
D = b1 / (2 ^ 1 ) + b2 / (2 ^ 2 ) + b3 / (2 ^ 3 ) + ... + bn / (2 ^ n )
现在问题就是怎样求得b1,b2,b3,……,bn。算法描述起来比较复杂,还是用数字来说话吧。声明一下,1 / ( 2 ^ n )这个数比较特殊,我称之为位阶值。
例二
例如0.456,第1位,0.456小于位阶值0.5故为0;第2位,0.456大于位阶值0.25,该位为1,并将0.456减去0.25得0.206进下一位;第3位,0.206大于位阶值0.125,该位为1,并将0.206减去0.125得0.081进下一位;第4位,0.081大于0.0625,为1,并将0.081减去0.0625得0.0185进下一位;第5位0.0185小于0.03125……
最后把计算得到的足够多的1和0按位顺序组合起来,就得到了一个比较精确的用二进制表示的纯小数了,同时精度问题也就由此产生,许多数都是无法在有限的n内完全精确的表示出来的,我们只能利用更大的n值来更精确的表示这个数,这就是为什么在许多领域,
程序员都更喜欢用double而不是float。
float的内存结构,我用一个带位域的结构体描述如下:
struct MYFLOAT
{
bool bSign : 1; // 符号,表示正负,1位
char cExponent : 8; // 指数,8位
unsigned long ulMantissa : 32; //
尾数,32位
};
符号就不用多说了,1表示负,0表示正
指数是以2为底的,范围是 -128 到 127,实际数据中的指数是原始指数加上127得到的,如果超过了127,则从-128开始计,其行为和X86架构的CPU处理加减法的溢出是一样的。
比如:127 + 2 = -127;-127 - 2 = 127
尾数都省去了第1位的1,所以在还原时要先在第一位加上1。它可能包含整数和纯小数两部分,也可能只包含其中一部分,视数字大小而定。对于带有整数部分的浮点数,其整数的表示法有两种,当整数大于十进制的16777215时使用的是
科学计数法,如果小于或等于则直接采用一般的二进制表示法。
科学计数法和小数的表示法是一样的。
小数部分则是直接使用
科学计数法,但形式不是X * ( 10 ^ n ),而是X * (
0 000000000000000000000000000000
符号位 指数位 尾数位
--------------------------------------------------------------------------------
例三
判断两个浮点数是否相等。
在这个例子中我们以C++代码来判别两个浮点数是否相等。由于浮点数在存储中无法精确表示,所以 fp1==fp2 无法准确的判断float型变量fp1与fp2是否相等。应该使用 (fp1-fl2)<0.0000001 来进行判断。
示例:
bool equal(float fp1,float fp2)
{
if( abs( fp1 - fp2 ) < 0.00000001 ) return true;
else
return false;
}
--------------------------------------------------------------------------------
导数字分布
简介
作者:concreteHAM
什么是浮点数,不用我多说,这里我们要讨论的是
规格化的任意
进制浮点数的前导数字的
概率分布。
在《
计算机程序设计艺术》第二卷中做了非常深入的讨论,这里我从中精炼出要点。
实例
例如:
2.345 E 67这是一个十进制
规格化浮点数,前导数字就是2。
就只有一个“随机”的浮点数而言,讨论其分布式没有意义的,我们要讨论的是充分多个“随机”数进行的一系列运算后产生的浮点结果的前导数字分布。
假设现在有一巨大的浮点数集,依此对数集中每个浮点数都乘以2,其中有一个十进制浮点数F,它的前导数字是1,那么它底数可能的值范围就是1.000…~1.999…,乘以一个数字2,那么它的底数就变成2.000…~3.999…,很明显乘以2以前前导数字是1的浮点个数与现在前导数字是2、3的浮点个数相同。以此我们接下来分析。
对于一个b
进制的浮点数,它的前导数字x范围就是0 < x < b,设f(x)是上述数集的前导数字的
概率密度函数(注:是密度函数),那么它在前导数字u和v之间(0
∫[u,v]f(x)dx ⑴
由前面所述的,对于一个充分小增量Δx,f(x)必须满足这样一个公式:
f⑴Δx = x*f(x)Δx ⑵
因为:
f⑴Δx是f⑴微分段内的概率,根据前面所述,f⑴Δx概率等于f(1*x)*(x*Δx)
很明显:
f(x) = f⑴/x ⑶
两边在[1,b]之间进行积分,等号左边必定为1,右边等于f⑴ln(b):
1 = f⑴ln(b) ⑷
得:f⑴ = 1/ln(b) 带入⑶中:
f(x) = 1/(x*ln(b))
那么利用⑴式得:
∫[u,v]1/(x*ln(b))dx
= ln(v/u) / ln(b) ⑸
这就是求前导数字的概率分布函数。
= ln((1+1)/1) / ln⑽
≈ 0.301
前导数字为9的概率就是:
= ln((9+1)/9) / ln⑽
≈0.0458
以下是一个测试程序(Mathematica软件):
T[n_,b_]:=Block[{res={},ran,i,a},
For[i=1,i
res=Append[res,0]
];
For[i=0,i
ran=Random[]*Random[]*Random[]; 充分打乱浮点数
ran=Log[b,ran];
a=Floor[b^(ran-Floor[ran])]; 取出前导数字
res[[a]]++ 对前导数字个数统计
];
Return[res]
]
执行T[100000,10],以10
进制测试100000个浮点数,得到一个分布:
{30149,18821,13317,9674,7688,6256,5306,4655,4134}
和理论值相当接近。