MAPM代码:任意有效位数的除法实现解析

先看看MAPM代码中包含的ARTICLE.PDF,对除法设计的描述是这样的:

使用了两个除法函数。对于少于250位的数字,我使用Knuth在The Art of Computer Programming, Volume 2一书中写的算法,稍有一些改动。

MAPM的基本数据结构定义如下:

   1:  typedef struct  {
   2:      UCHAR    *m_apm_data;
   3:      long    m_apm_id;
   4:      int     m_apm_refcount;       /* <- used only by C++ MAPM class */
   5:      int    m_apm_malloclength;
   6:      int    m_apm_datalength;
   7:      int    m_apm_exponent;
   8:      int    m_apm_sign;
   9:  } M_APM_struct;
  10:   
  11:  typedef M_APM_struct *M_APM;

其中的成员定义如下:

名称 定义
m_apm_data 以10进制的方式按字节存储数值。例如1750(0x6D6)的存储方式是0x11 0x32,而不是0x06 0xD6。
m_apm_id 常数,定义为:#define    M_APM_IDENT 0x6BCC9AE5
m_apm_refcount for MAPM C++ class
m_apm_malloclength 第一次初始化为80字节。
m_apm_datalength 指significant digits有效数
m_apm_exponent 指数,又称阶码。
m_apm_sign 正数为0x00000001,负数为0xFFFFFFFF,零数时为0x00000000。

在Windows x86(little endian)系统中,典型的内存结构是:

   1:  0x003D3260  b8 32 3d 00 e5 9a cc 6b 01 00 00 00 50 00 00 00  .2=....k....P...
   2:  0x003D3270  09 00 00 00 05 00 00 00 01 00 00 00 fd fd fd fd  ................

其中m_apm_data指向的内存信息是,此时通过m_apm_set_string(a, "99831.5355")给变量a赋值:

   1:  0x003D32B8  63 53 0f 23 32 00 cd cd cd cd cd cd cd cd cd cd  cS.#2...........
   2:  0x003D32C8  cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd  ................
   3:  0x003D32D8  cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd  ................
   4:  0x003D32E8  cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd  ................
   5:  0x003D32F8  cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd  ................
   6:  0x003D3308  cd cd cd cd

从中我们可以看出,a的阶码是5,在内存中的表示为:0x63 53 0f 23 32,对应的10进制值为:99 83 15 35 50 00。由此我们可以看出,MAPM将有效位全部作为小数部分,与阶码一起定义数值。这样在计算乘法的时候,只需要把小数部分当作整型数值来对待就可以了。

下面是除法函数:

   1: /****************************************************************************/
   2: void    m_apm_divide(M_APM rr, int places, M_APM aa, M_APM bb)
   3: {
   4: M_APM   tmp0, tmp1;
   5: int     sn, nexp, dplaces;
   6:  
   7: sn = aa->m_apm_sign * bb->m_apm_sign;
   8:  
   9: if (sn == 0)                  /* one number is zero, result is zero */
  10:   {
  11:    if (bb->m_apm_sign == 0)
  12:      {
  13:       M_apm_log_error_msg(M_APM_RETURN, "\'m_apm_divide\', Divide by 0");
  14:      }
  15:  
  16:    M_set_to_zero(rr);
  17:    return;
  18:   }
  19:  
  20: /*
  21:  *    Use the original 'Knuth' method for smaller divides. On the
  22:  *    author's system, this was the *approx* break even point before
  23:  *    the reciprocal method used below became faster.
  24:  */
  25:  
  26: if (places < 250)
  27:   {
  28:    M_apm_sdivide(rr, places, aa, bb);
  29:    return;
  30:   }
  31:  
  32: /* mimic the decimal place behavior of the original divide */
  33:  
  34: nexp = aa->m_apm_exponent - bb->m_apm_exponent;
  35:  
  36: if (nexp > 0)
  37:   dplaces = nexp + places;
  38: else
  39:   dplaces = places;
  40:  
  41: tmp0 = M_get_stack_var();
  42: tmp1 = M_get_stack_var();
  43:  
  44: m_apm_reciprocal(tmp0, (dplaces + 8), bb);
  45: m_apm_multiply(tmp1, tmp0, aa);
  46: m_apm_round(rr, dplaces, tmp1);
  47:  
  48: M_restore_stack(2);
  49: }
  50: /****************************************************************************/
  51: void    m_apm_reciprocal(M_APM rr, int places, M_APM aa)
  52: {
  53: M_APM   last_x, guess, tmpN, tmp1, tmp2;
  54: char    sbuf[32];
  55: int    ii, bflag, dplaces, nexp, tolerance;
  56:  
  57: if (aa->m_apm_sign == 0)
  58:   {
  59:    M_apm_log_error_msg(M_APM_RETURN, "\'m_apm_reciprocal\', Input = 0");
  60:  
  61:    M_set_to_zero(rr);
  62:    return;
  63:   }
  64:  
  65: last_x = M_get_stack_var();
  66: guess  = M_get_stack_var();
  67: tmpN   = M_get_stack_var();
  68: tmp1   = M_get_stack_var();
  69: tmp2   = M_get_stack_var();
  70:  
  71: m_apm_absolute_value(tmpN, aa);
  72:  
  73: /* 
  74:     normalize the input number (make the exponent 0) so
  75:     the 'guess' below will not over/under flow on large
  76:     magnitude exponents.
  77: */
  78:  
  79: nexp = aa->m_apm_exponent;
  80: tmpN->m_apm_exponent -= nexp;
  81:  
  82: m_apm_to_string(sbuf, 15, tmpN);
  83: m_apm_set_double(guess, (1.0 / atof(sbuf)));
  84:  
  85: tolerance = places + 4;
  86: dplaces   = places + 16;
  87: bflag     = FALSE;
  88:  
  89: m_apm_negate(last_x, MM_Ten);
  90:  
  91: /*   Use the following iteration to calculate the reciprocal :
  92: 
  93: 
  94:          X     =  X  *  [ 2 - N * X ]
  95:           n+1
  96: */
  97:  
  98: ii = 0;
  99:  
 100: while (TRUE)
 101:   {
 102:    m_apm_multiply(tmp1, tmpN, guess);
 103:    m_apm_subtract(tmp2, MM_Two, tmp1);
 104:    m_apm_multiply(tmp1, tmp2, guess);
 105:  
 106:    if (bflag)
 107:      break;
 108:  
 109:    m_apm_round(guess, dplaces, tmp1);
 110:  
 111:    /* force at least 2 iterations so 'last_x' has valid data */
 112:  
 113:    if (ii != 0)
 114:      {
 115:       m_apm_subtract(tmp2, guess, last_x);
 116:  
 117:       if (tmp2->m_apm_sign == 0)
 118:         break;
 119:  
 120:       /* 
 121:        *   if we are within a factor of 4 on the error term,
 122:        *   we will be accurate enough after the *next* iteration
 123:        *   is complete.
 124:        */
 125:  
 126:       if ((-4 * tmp2->m_apm_exponent) > tolerance)
 127:         bflag = TRUE;
 128:      }
 129:  
 130:    m_apm_copy(last_x, guess);
 131:    ii++;
 132:   }
 133:  
 134: m_apm_round(rr, places, tmp1);
 135: rr->m_apm_exponent -= nexp;
 136: rr->m_apm_sign = aa->m_apm_sign;
 137: M_restore_stack(5);
 138: }
 139: /****************************************************************************/

其中调用了m_apm_reciprocal(), m_apm_multiply()和m_apm_round()等函数。其中m_apm_reciprocal()用于取倒数,m_apm_round()用于

   1: void    m_apm_round(M_APM btmp, int places, M_APM atmp) 
   2: {
   3: int    ii;
   4:  
   5: if (M_util_firsttime)
   6:   {
   7:    M_util_firsttime = FALSE;
   8:  
   9:    M_work_0_5 = m_apm_init();
  10:    m_apm_set_string(M_work_0_5, "5");
  11:   }
  12:  
  13: ii = places + 1;
  14:  
  15: if (atmp->m_apm_datalength <= ii)
  16:   {
  17:    m_apm_copy(btmp,atmp);
  18:    return;
  19:   }
  20:  
  21: M_work_0_5->m_apm_exponent = atmp->m_apm_exponent - ii;
  22:  
  23: if (atmp->m_apm_sign > 0)
  24:   m_apm_add(btmp, atmp, M_work_0_5);
  25: else
  26:   m_apm_subtract(btmp, atmp, M_work_0_5);
  27:  
  28: btmp->m_apm_datalength = ii;
  29: M_apm_normalize(btmp);
  30: }
posted @ 2009-09-11 10:34  黄汉  阅读(376)  评论(0编辑  收藏  举报