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