一、浮点数
2、浮点计算
是指浮点数参与的运算,这种运算通常伴随着因为无法精确表示而进行的近似或舍入。 一个浮点数a由两个数m和e来表示:a = m × b^e。在任意一个这样的系统中,我们选择一个基数b(记数系统的基)和精度p(即使用多少位来存储)。m(即尾数)是形如±d.ddd...ddd的p位数(每一位是一个介于0到b-1之间的整数,包括0和b-1)。如果m的第一位是非0整数,m称作规格化的。有一些描述使用一个单独的符号位(s 代表+或者-)来表示正负,这样m必须是正的。e是指数。
3、结构
由此可以看出,在计算机中表示一个浮点数,其结构如下: 尾数部分( 定点小数) 阶码部分(定点整数)
这种设计可以在某个固定长度的存储空间内表示定点数无法表示的更大范围的数。
[ 编辑本段]
二、实例
1、题目
例如,一个指数范围为±4的4位十进制浮点数可以用来表示43210,4.321或0.0004321,但是没有足够的精度来表示432.123和43212.3(必须近似为432.1和43210)。当然,实际使用的位数通常远大于4。
2、特别数值
此外,浮点数表示法通常还包括一些特别的数值:+∞和−∞(正负无穷大)以及NaN('Not a Number')。无穷大用于数太大而无法表示的时候,NaN则指示非法操作或者无法定义的结果。
3、二进制表示
众所周知,计算机中的所有数据都是以二进制表示的,浮点数也不例外。然而浮点数的二进制表示法却不像定点数那么简单了。
4、浮点数的概念
先澄清一个概念,浮点数并不一定等于小数,定点数也并不一定就是整数。所谓浮点数就是小数点在逻辑上是不固定的,而定点数只能表示小数点固定的数值,具用浮点数或定点数表示某哪一种数要看用户赋予了这个数的意义是什么。 C++中的浮点数有6种,分别是: float:单精度,32位 unsigned float:单精度无符号,32位 double:双精度,64位 unsigned double:双精度无符号,64位 long double:高双精度,80位 unsigned long double:高双精度无符号,80位(嚯,应该是C++中最长的内置类型了吧!) 然而不同的编译器对它们的支持也略有不同,据我所知,很多编译器都没有按照IEEE规定的标准80位支持后两种浮点数的,大多数编译器将它们视为double,或许还有极个别的编译器将它们视为128位?!对于128位的long double我也仅是听说过,没有求证,哪位高人知道这一细节烦劳告知。 下面我仅以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 )这个数比较特殊,我称之为位阶值。
5、例二
例如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 : 23; // 尾数,23位 }; 符号就不用多说了,1表示负,0表示正 指数是以2为底的,范围是 -128 到 127,实际数据中的指数是原始指数加上127得到的,如果超过了127,则从-128开始计,其行为和X86架构的CPU处理加减法的溢出是一样的。 比如:127 + 2 = -127;-127 - 2 = 127 尾数都省去了第1位的1,所以在还原时要先在第一位加上1。它可能包含整数和纯小数两部分,也可能只包含其中一部分,视数字大小而定。对于带有整数部分的浮点数,其整数的表示法有两种,当整数大于十进制的16777215时使用的是科学计数法,如果小于或等于则直接采用一般的二进制表示法。科学计数法和小数的表示法是一样的。 小数部分则是直接使用科学计数法,但形式不是X * ( 10 ^ n ),而是X * ( 2 ^ n )。拆开来看。 0 00000000 0000000000000000000000 符号位 指数位 尾数位 ----------------------------------------------------------------------------------
[ 编辑本段]
三、浮点前导数字的分布
1、简介
作者:
concreteHAM 什么是浮点数,不用我多说,这里我们要讨论的是规格化的任意进制浮点数的前导数字的概率分布。 在《计算机程序设计艺术》第二卷中做了非常深入的讨论,这里我从中精炼出要点。
2、实例
例如: 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<b)的概率就是: ∫[u,v]f(x)dx (1) 由前面所述的,对于一个充分小增量Δx,f(x)必须满足这样一个公式: f(1)Δx = x*f(x)Δx (2) 因为: f(1)Δx是f(1)微分段内的概率,根据前面所述,f(1)Δx概率等于f(1*x)*(x*Δx) 很明显: f(x) = f(1)/x (3) 两边在[1,b]之间进行积分,等号左边必定为1,右边等于f(1)ln(b): 1 = f(1)ln(b) (4) 得:f(1) = 1/ln(b) 带入(3)中: f(x) = 1/(x*ln(b)) 那么利用(1)式得: ∫[u,v]1/(x*ln(b))dx =
ln(v/u) / ln(b) (5) 这就是求前导数字的概率分布函数。 例如b = 10进制时,前导数字为1的概率就是: = ln((1+1)/1) / ln(10) ≈ 0.301 前导数字为9的概率就是: = ln((9+1)/9) / ln(10) ≈0.0458 以下是一个测试程序(Mathematica软件): T[n_,b_]:=Block[{res={},ran,i,a}, For[i=1,i<b,i++; res=Append[res,0] ]; For[i=0,i<n,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}
和理论值相当接近。
整数怎样转2进制,小数怎样转2进制就不说了。 12.5: 1. 整数部分12,二进制为1100; 小数部分0.5, 二进制是.1,先把他们连起来,从第一个1数起取24位(后面补0): 1100.10000000000000000000 这部分是有效数字。(把小数点前后两部分连起来再取掉头前的1,就是尾数) 2. 把小数点移到第一个1的后面,需要左移3位, 加上偏移量127:127+3=130,二进制是10000010,这是阶码。 3. -12.5是负数,所以符号位是1。把符号位,阶码和尾数连起来。注意,尾数的第一位总是1,所以规定不存这一位的1,只取后23位: 1 10000010 10010000000000000000000 把这32位按8位一节整理一下,得: 11000001 01001000 00000000 00000000 就是十六进制的 C1480000. 2.025675 1. 整数部分2,二进制为10; 小数部分0.025675, 二进制是.0000011010010010101001,先把他们连起来,从第一个1数起取24位(后面补0): 10.0000011010010010101001 这部分是有效数字。把小数点前后两部分连起来再取掉头前的1,就是尾数: 00000011010010010101001 2. 把小数点移到第一个1的后面,左移了1位, 加上偏移量127:127+1=128,二进制是10000000,这是阶码。 3. 2.025675是正数,所以符号位是0。把符号位,阶码和尾数连起来: 0 10000000 00000011010010010101001 把这32位按8位一节整理一下,得: 01000000 00000001 10100100 10101001 就是十六进制的 4001A4A9. -1.99744 还需要详细说吗? 如果只有小数部分,那么需要右移小数点. 比如右移3位才能放到第一个1的后面, 阶码就是127-3=124. 补充一个浮点二进制数手工转换成十进制数的例子: 假设浮点二进制数是 1011 1101 0100 0000 0000 0000 0000 0000 按1,8,23位分成三段: 1 01111010 10000000000000000000000 最后一段是尾数。前面加上"1.", 就是 1.10000000000000000000000 下面确定小数点位置。阶码是01111010,加上00000101才是01111111(127), 所以他减去127的偏移量得-5。(或者化成十进制得122,122-127=-5)。 因此尾数1.10(后面的0不写了)是小数点右移5位的结果。要复原它就要左移5位小数点,得0.0000110, 即十进制的0.046875 最后是符号:1代表负数,所以最后的结果是 -0.046875 还要注意其他机器的浮点数表示方法可能与此不同. 不能任意移植.