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: }