- 注册时间
- 2007-12-28
- 最后登录
- 1970-1-1
- 威望
- 星
- 金币
- 枚
- 贡献
- 分
- 经验
- 点
- 鲜花
- 朵
- 魅力
- 点
- 上传
- 次
- 下载
- 次
- 积分
- 12785
- 在线时间
- 小时
|
发表于 2017-12-27 11:35:30
|
显示全部楼层
我们知道,使用牛顿迭代法求倒数属于浮点运算,或者为非完全精度运算。浮点运算和整数运算相对,整数运算属于完全精度运算,意为每一步运算都取全部精度,不丢弃任何数字,结果也一定是精确的。浮点运算的结果是非精确的,只需要达到指定的精度即可。
和整数运算相比,浮点运算最麻烦的在于精度控制,如果精度控制做的太保守(即在运算过程中保留的位数太多)则会导致运算时间过长。反之,则会导致最终结果达不到要求的精度。所以,在运算过程中需要保留多少位数字,才能恰到好处,需要精心设计和验证。
牛顿迭代法求倒数,理论上,每迭代一次,精度加倍。如果你在每次迭代时,源操作精度仅仅保持目标精度的一半,你会发现,最终精度是达不到要求的。
例如,最终精度需要10000比特,依此倒推,最后一次迭代的目标精度为10000,倒数第2次迭代的目标精度为5000。
故前几次迭代的源精度和目标精度依次为(假定x0 可通过其他指令求得,且精度可达到30比特)
30, 40
40, 79
79, 157
157, 313
313, 625
625, 1250
1250,2500
2500,5000
5000,10000
如果你真的这样做,你会发现,最终精度是达不到要求的。事实上,为了保证精度,需要的目标精度必须稍微大于理论精度
我的做法是:
首先,通过最终目标精度,推导出每次迭代过程中需要的精度,并将其存入数组。
精度用2个成员(i,b)来表示,i表示多少个limbs,b表示多少个比特。(如果采用10^k进制,i表示多少个limbs,b表示多少位)
例如采用\( 2^{30} \)进制,精度为50比特,则i=50/30=1, b=50 mod 30=20
下面是我的代码
- #define DO_FORMAT(res) \
- do { \
- if ( res.b>=BCMP_NUMB_BITS) \
- { res.i++; res.b-=BCMP_NUMB_BITS; } \
- } while (0)
- typedef struct _precision_st
- {
- int i;
- int b;
- }PRC_ST;
- #define EXTRA_BITS 2
- #define BCMP_NUMB_BITS 30
- PRC_ST tn_prc_arr[32]; // the precision for each iteration
- PRC_ST t_prc; // the target precision
- PRC_ST s_prc; // the source precision
复制代码
首先,将 tgt_prec赋值为目标精度+EXTRA_BITS 比特,然后执行下面的代码
- t_prc = tgt_prec;
- i=0; tn_prc_arr[0]=t_prc; // tgt_prec have included EXTRA_BITS and need not add EXTRA_BITS
- while (1)
- {
- m= 0-(t_prc.i & 1); // if t_prc.i is odd number, then m= 0xffffffff, else m=0
- t_prc.i /= 2;
- t_prc.b= (t_prc.b+1 + (m & BCMP_NUMB_BITS))/2;
- t_prc.b += EXTRA_BITS; // enlarge target precision to guarantee to get enough precision
- DO_FORMAT(t_prc);
- if ( t_prc.i==0 )
- break;
- tn_prc_arr[++i]=t_prc;
- }
复制代码
我的使用牛顿迭代方求x=1/a的大致流程
步骤1: s1=a * x0,
步骤2: 如果(s1>1), 那么s1=s1-1, doAdd=true, 否则s1=1-s1, doAdd=false
步骤3: s2= s1 * x0
步骤4: 如果 doAdd, 那么 x1= x0 + s2, 否则x1=x0-s2
步骤5: 将x1赋值给x0,此时x0的精度较原始值加倍
下面是迭代过程的精度控制
- for (;i>=0;i--)
- {
- bcmp_size_t SN; // the needed source precison in limb
- bcmp_size_t TN; // the needed target precison in limb
- bcmp_size_t TN1; // TN1=TN+1
- bcmp_size_t zCnt; // the lead zero count
- BOOL doAdd;
- t_prc = tn_prc_arr[i]; // t_prc, target precision
-
- m= 0-(t_prc.i & 1); // if t_prc.i is odd, m=0xffffffff, else m=0
- s_prc.i = t_prc.i/2;
- s_prc.b = ( (m & BCMP_NUMB_BITS) + t_prc.b+1)/2;
- DO_FORMAT(s_prc);
- SN = s_prc.i + (s_prc.b>0);
- TN = t_prc.i + (t_prc.b>0);
- TN1 = TN+1;
- ..... 此处略
- }
复制代码
下面是具体的精度控制方法
1. 在步骤1的计算过程中,a的精度取TN1个limb,x0的精度取SN个limb,结果精度值保留TN1个limb,丢弃低位部分
2. 在步骤2中,高位部分是多个连续的0,需要统计连续0的个数zCnt,和剩余部分的长度s1_n,其总长度为TN1
3. 在步骤3中,s1的精度为s1_n, x0的精度取SN,结果精度取TN-zCnt,丢弃低位部分。
4. 在我的代码中x1和x0共用相同的缓冲区,故步骤4是需要将x0的长度扩展至TN1,低位部分补0,然后加上(或者减去)s2
其他的注意事项。
1. a要做预处理,使得1/a的值>=0.5且小于1,这样,在迭代的过程中,参与运算的非0比特位尽可能多。
2. 在步骤4中,如果发现x1>1.0, 要将其重新格式化为0.9999999...., 如果是2进制表示,将所有bit位置1
|
|