[算法 笔记]大数相乘

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

  大数相乘:两个超出整型限制的两个数相乘,例如,两个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函数(http://blog.csdn.net/v_july_v/article/details/9024123)的处理。

  因此,大数相乘是一个陷阱多多的函数,其中一个容易被忽略的陷阱就是:按照正常人的习惯,高数位存放在最左端,个位放在右端,例如 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 }
View Code

 

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

你可能感兴趣的:(算法)