今天去参加腾讯笔试,其中有一道选答题:大数相乘问题。在编写代码的过程,我突然发现以前写的原始的大数相乘是一个很简陋的源码。所以,下午找个时间重新写了一份。

  大数相乘:两个超出整型限制的两个数相乘,例如,两个50位的正数相乘。

  最简陋的方式,就是按照乘法的计算过程来模拟计算:

       1 2

    × 3 6

   ---------- ---- 其中,上标数字为进位数值。

     71 2  --- 在这个计算过程中,2×6=12。本位保留2,进位为1.这里是一个简单的计算过程,如果在高位也需要进位的情况下,如何处理?

    3 6

    -----------

    413  2

 

  开始比较简陋的源码就是基本模拟上述的乘法过程:

1 int compute_value( const char *lhs, int lhs_start_index,
 2                    const char *rhs, int rhs_start_index,
 3                    char *result )
 4 {
 5     int i = 0, j = 0, res_i = 0;
 6     int tmp_i = 0;
 7     int carry = 0;
 8 
 9     for ( i = lhs_start_index; lhs[i] != '\0'; ++i, ++tmp_i )
10     {
11         res_i = tmp_i;  // 在每次计算时,结果存储的位需要增加。如上述模拟过程中,第二行。
12         carry = 0;
13 
14         for ( j = rhs_start_index; rhs[j] != '\0'; ++j )
15         {
16             int tmp_lhs = lhs[i] - '0';
17             int tmp_rhs = rhs[j] - '0';
18             carry += ( result[res_i] - '0' );       // 这里需要注意,因为每次计算并不能保证以前计算结果的进位都消除,因此这里是加号。
19             carry += ( tmp_lhs * tmp_rhs );      // 并且以前的计算结果也需要考虑。
20             result[res_i++] = ( carry % 10 + '0' );
21             carry /= 10;
22         }
23 
24         while ( carry )  // 当乘数的一次计算完成,可能存在有的进位没有处理。因此,这里对其进行处理。
25         {
26             result[res_i++] = ( carry % 10 + '0' );
27             carry /= 10;
28         }
29     }
30     result[res_i] = '\0';
31 
32     return res_i;
33 }

  上述源码能够完成基本的运算,比如非负数,非小数等情况。如果在传递的参数是不规则或不正确,例如,“  -1234”, "+1234", “- 123”。这里的处理有点类似于atoi函数()的处理。

  因此,大数相乘是一个陷阱多多的函数,其中一个容易被忽略的陷阱就是:按照正常人的习惯,高数位存放在最左端,个位放在右端,例如 1223的字符串为“1223”,但是在上述函数计算过程中,是从左到右计算的。这是一个非常容易被忽略的错误,也是最致命的错误之一。

  整体思路如下:

  1. 检查参数的合理性;

  2. 判断传递参数是正负数;

  3. 判断传递参数是否为小数;

  4. 翻转并计算数值;

  5. 如果存在小数,需要将结果中小数点的放置;

  6. 如果计算结果为负值,则将结果设置为负值。

  全部源码如下:

1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <assert.h>
  5 #include <ctype.h>
  6 
  7 // 翻转data[start...end-1]
  8 void reverse_data( char *data, int start, int end  )
  9 {
 10     char temp = '0';
 11 
 12     assert( data != NULL && start < end );
 13     while ( start < end )
 14     {
 15         temp = data[start];
 16         data[start] = data[--end];
 17         data[end] = temp;
 18         ++start;
 19     }
 20 }
 21 
 22 /**< 判断数据中数值的合法性,以及非空格字符的起始位置。
 23  *  1. 是否均有正确的正负号,例如"+123", "-123"等
 24  *  2. 字符串中字符是否均是数字字符。
 25  *  3. 字符串开始部分放入空格是合法的,例如,”  +123“等
 26  *  4. 只有一个'.'标记
 27  **< 参数:
 28  * @data: 表示传入字符;
 29  * @nonspace_index:非空格起点
 30  **< 返回值:若数据是合法数值,则返回1;否则返回0;
 31  */
 32 int check_logic( const char *data, int *nonspace_index )
 33 {
 34     int flag = 1;
 35     int start = 0;
 36     int point_cnt = 0;
 37 
 38     assert( data != NULL );
 39     /* PS. if data is not space(' ', '\n'), isspace() return 0. */
 40     for ( ; isspace( data[start] )!= 0
 41             && data[start] != '\0'; ++start );
 42 
 43     // 判断数据是否为负数
 44     *nonspace_index = start;
 45     if ( data[start] == '-' || data[start] == '+' )
 46     {
 47         ++start;
 48     }
 49 
 50     /* PS. if ch is digit character, isdigit() return 1; otherwise return 0. */
 51     for ( ; data[start] != '\0'; ++start )
 52     {
 53         if ( isdigit( data[start] ) || data[start] == '.' )
 54         {
 55             // 判断数据为小数的格式是否正确。
 56             if ( data[start] == '.' && point_cnt == 0 )
 57             {
 58                 ++point_cnt;
 59             }
 60             else if ( point_cnt > 1 )
 61             {
 62                 break;
 63             }
 64         }
 65     }
 66 
 67     // 若小数点后面无数据,则不合法
 68     if ( data[start] != '\0' )
 69     {
 70         flag = 0;
 71     }
 72 
 73     return flag;
 74 }
 75 
 76 /**< notice: 传入到该函数的数据已经被翻转后的数值。即,最左边为个位,最右边为最高位
 77  **< return: 结果数据的长度。
 78  */
 79 int compute_value( const char *lhs, int lhs_start_index,
 80                    const char *rhs, int rhs_start_index,
 81                    char *result )
 82 {
 83     int i = 0, j = 0, res_i = 0;
 84     int tmp_i = 0;
 85     int carry = 0;
 86 
 87     for ( i = lhs_start_index; lhs[i] != '\0'; ++i, ++tmp_i )
 88     {
 89         res_i = tmp_i;
 90         carry = 0;
 91 
 92         for ( j = rhs_start_index; rhs[j] != '\0'; ++j )
 93         {
 94             int tmp_lhs = lhs[i] - '0';
 95             int tmp_rhs = rhs[j] - '0';
 96             carry += ( result[res_i] - '0' );
 97             carry += ( tmp_lhs * tmp_rhs );
 98             result[res_i++] = ( carry % 10 + '0' );
 99             carry /= 10;
100         }
101 
102         while ( carry )
103         {
104             result[res_i++] = ( carry % 10 + '0' );
105             carry /= 10;
106         }
107     }
108     result[res_i] = '\0';
109 
110     return res_i;
111 }
112 
113 int has_point( char *data, int index, int *point_index )
114 {
115     int start = index;
116 
117     for ( ; data[start] != '\0'; ++start )
118     {
119         if ( data[start] == '.' )
120         {
121             *point_index = start;
122             break;
123         }
124     }
125 
126     return ( data[start] != '\0' );
127 }
128 
129 int is_neg( char *data, int *index )
130 {
131     int flag = 0;
132     int start = *index;
133     if ( data[start] == '-' || data[start] == '+' )
134     {
135         if ( data[start] == '-' )
136             flag = 1;
137         ++start;
138     }
139 
140     *index = start;
141     return flag;
142 }
143 
144 void copy_c( char * dest, const char *src )
145 {
146     while ( *src != '\0' )
147     {
148         if ( *src != '.' )
149             *dest++ = *src;
150         src++;
151     }
152 }
153 
154 int compute_decimals( char *lhs, int lhs_point_index,
155                       char *rhs, int rhs_point_index,
156                       char *result  )
157 {
158     int lhs_length = strlen( lhs );
159     int rhs_length = strlen( rhs );
160     int result_point_index = lhs_length + rhs_length;
161     int result_length = 0, i = 0;
162     char *tmp_lhs = NULL;
163     char *tmp_rhs = NULL;
164 
165     // 计算在结果中放置小数点的位置,根据的是两个小数部分长度之和
166     // 例如,rhs = "12.345", lhs = "3.45", result = "xxx.xxxxx"
167     result_point_index -= ( lhs_point_index + rhs_point_index );
168 
169     // 分配并拷贝
170     if ( lhs_point_index )
171     {
172         tmp_lhs = (char *)malloc( sizeof(char) * lhs_length );
173         assert( tmp_lhs != NULL );
174         copy_c( tmp_lhs, lhs );
175         tmp_lhs[lhs_length - 1] = '\0';
176     }
177     else
178     {
179         tmp_lhs = lhs;
180     }
181 
182     if ( rhs_point_index )
183     {
184         tmp_rhs = (char *)malloc( sizeof(char) * rhs_length );
185         assert( tmp_rhs != NULL );
186         copy_c( tmp_rhs, rhs );
187         tmp_rhs[rhs_length - 1] = '\0';
188     }
189     else
190     {
191         tmp_rhs = rhs;
192     }
193 
194     // tmp_lhs比lhs少一个小数点
195     reverse_data( tmp_lhs, 0, lhs_length - 1 );
196     reverse_data( tmp_rhs, 0, rhs_length - 1 );
197     result_length = compute_value( tmp_lhs, 0, tmp_rhs, 0, result );
198     for ( i = result_length; i > result_point_index; --i )
199     {
200         result[i] = result[i - 1];
201     }
202 
203     result[result_point_index] = '.';
204     ++result_length;
205     result[result_length] = '\0';
206 
207     // 释放资源
208     if ( lhs_point_index )
209     {
210         free( tmp_lhs ), tmp_lhs = NULL;
211     }
212 
213     if ( rhs_point_index )
214     {
215         free( tmp_rhs ), tmp_rhs = NULL;
216     }
217 
218     return result_length;
219 }
220 
221 // 返回结果数值的长度
222 int big_number_multiply( char *lhs, char *rhs, char *result )
223 {
224     int lhs_start_index = 0, lhs_point_index = 0;
225     int rhs_start_index = 0, rhs_point_index = 0;
226     int result_is_neg = 0;
227     int result_length = 0;
228 
229     assert( lhs != NULL && rhs != NULL && result != NULL );
230     // 检查数据的合法性
231     if ( !(check_logic( lhs, &lhs_start_index )
232             && check_logic( rhs, &rhs_start_index )) )
233     {
234         return -1;
235     }
236 
237     // 检查数据是否为负数
238     result_is_neg = is_neg( lhs, &lhs_start_index );
239     if ( is_neg( rhs, &rhs_start_index) )
240     {
241         result_is_neg = result_is_neg == 1 ?  0 : 1;
242     }
243 
244     // 检查是否两个数值中存在一个小数或两者都是小数
245     if ( !( has_point( lhs, lhs_start_index, &lhs_point_index )
246             && has_point( rhs, rhs_start_index, &rhs_point_index ) ) )
247     {
248         reverse_data( lhs, lhs_start_index, strlen(lhs) );
249         reverse_data( rhs, rhs_start_index, strlen(rhs) );
250         result_length = compute_value( lhs, lhs_start_index,
251                                        rhs, rhs_start_index, result );
252         reverse_data( lhs, lhs_start_index, strlen(lhs) );
253         reverse_data( rhs, rhs_start_index, strlen(rhs) );
254     }
255     else // 一个数值中有小数部分
256     {
257         result_length = compute_decimals(
258                           lhs + lhs_start_index, lhs_point_index - lhs_start_index + 1,
259                           rhs + rhs_start_index, rhs_point_index - rhs_start_index + 1,
260                           result );
261     }
262     if ( result_is_neg )
263         result[result_length++] = '-';
264     reverse_data( result, 0, result_length );
265     result[result_length] = '\0';
266 
267     return result_length;
268 }
269 
270 int main()
271 {
272     char lhs[] = "-1.235";
273     char rhs[] = "    3.456";
274     char result[40];
275 
276     memset( result, '0', sizeof(result) );
277 
278     big_number_multiply( lhs, rhs, result );
279     printf( "%s\n", result );
280     return 0;
281 }

 

  这里我仅仅提供了一个简单的测试,欢迎大家来指导!!