C语言 不使用乘法求一个数的阶乘

wwwo4jvm  于 2023-06-21  发布在  其他
关注(0)|答案(2)|浏览(136)

我需要计算一个数的阶乘而不使用乘法运算符。由于这个限制,我直接尝试使用重复加法。挺管用的。然而,我的程序很难得到更大数字的阶乘。有没有更好的方法来解决这个问题?
下面是我的代码:

  1. void main(){
  2. unsigned long num = 0, ans = 1, temp = 1;
  3. printf("Enter a number: ");
  4. scanf("%lu", &num);
  5. while (temp <= num){
  6. int temp2 = 0, ctr = 0;
  7. while (ctr != temp){
  8. temp2 += ans;
  9. ctr ++;
  10. }
  11. ans = temp2;
  12. temp ++;
  13. }
  14. printf("%lu! is %lu\n", num, ans);
  15. }
7lrncoxx

7lrncoxx1#

您可以使用位移位和加法来实现更快(比重复加法更快)的乘法函数,以执行二进制的“长乘法”。

  1. unsigned long long mul_ull(unsigned long long a, unsigned long long b)
  2. {
  3. unsigned long long product = 0;
  4. unsigned int shift = 0;
  5. while (b)
  6. {
  7. if (b & 1)
  8. {
  9. product += a << shift;
  10. }
  11. shift++;
  12. b >>= 1;
  13. }
  14. return product;
  15. }

编辑:使用单个比特移位和加法的上述替代实现:

  1. unsigned long long mul_ull(unsigned long long a, unsigned long long b)
  2. {
  3. unsigned long long product = 0;
  4. while (b)
  5. {
  6. if (b & 1)
  7. {
  8. product += a;
  9. }
  10. a <<= 1;
  11. b >>= 1;
  12. }
  13. return product;
  14. }

实际上,这是否比重复加法快取决于编译器所做的任何优化。优化编译器可以分析重复的加法并将其替换为乘法。优化编译器也可以分析上面的mul_ull函数的代码,并将其替换为乘法,但优化器可能更难发现。所以在实际中,优化后的重复加法算法比移位加法算法更快是完全合理的!
此外,如果第二个参数b是相乘的数字中的较小者,则mul_ull函数的上述实现将倾向于执行得更好,此时数字中的一个比另一个大得多(这对于阶乘计算是典型的)。执行时间大致与b的对数成正比(当b非零时),但也取决于b的二进制值中1位的数量。因此,对于阶乘计算,将旧的运行乘积放在第一个参数a中,将新的因子放在第二个参数b中。
使用上述乘法函数的阶乘函数:

  1. unsigned long long factorial(unsigned int n)
  2. {
  3. unsigned long long fac = 1;
  4. unsigned int i;
  5. for (i = 2; i <= n; i++)
  6. {
  7. fac = mul_ull(fac, i);
  8. }
  9. return fac;
  10. }

上面的factorial函数很可能由于算术溢出而产生n> 20的错误结果。需要66位来表示21!但是unsigned long long仅需要64位宽(并且这通常是大多数实现的实际宽度)。

编辑2023-06-07

一个稍微长一点的factorial()函数,如果对n的值有相同的限制,那么它所使用的乘法次数还不到一半。此方法基于2023年3月21日编辑后的https://scicomp.stackexchange.com/q/42510中所示的方法。

  1. unsigned long long factorial(unsigned int n)
  2. {
  3. unsigned long long fac;
  4. unsigned int m;
  5. unsigned int i;
  6. if (n <= 1)
  7. {
  8. fac = 1;
  9. }
  10. else
  11. {
  12. if ((n & 1) == 0)
  13. {
  14. m = n;
  15. n -= 2;
  16. }
  17. else
  18. {
  19. m = n + n;
  20. n -= 3;
  21. }
  22. fac = m;
  23. while (n > 0)
  24. {
  25. m += n;
  26. fac = mul_ull(fac, m);
  27. n -= 2;
  28. }
  29. }
  30. return fac;
  31. }

例如,它计算9!= 18·24·28·30 = 362880,10!= 10·18·24·28·30 = 3628800。

展开查看全部
20jt8wwn

20jt8wwn2#

对于较大的n值,需要使用较大的格式。
因为你不能使用乘法,所以你必须自己实现它似乎是合乎逻辑的。
在实践中,由于只需要加法,所以如果我们不寻求高效率,实现起来并不困难。
一个小问题,无论如何:你必须把输入的整数转换成一个数字数组。由于模是不允许的,我猜,我实现了它的帮助下,snprintf函数。
结果:

  1. 100! is 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

注:此结果是即时提供的。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #define NDIGITS 1000 // maximum number of digits
  5. struct MyBig {
  6. int digits[NDIGITS + 2]; // "+2" to ease overflow control
  7. int degree;
  8. };
  9. void reset (struct MyBig *big) {
  10. big->degree = 0;
  11. for (int i = 0; i <= NDIGITS; ++i) big->digits[i] = 0;
  12. }
  13. void create_with_div (struct MyBig *big, int n) { // not used here
  14. reset (big);
  15. while (n != 0) {
  16. big->digits[big->degree++] = n%10;
  17. n /= 10;
  18. }
  19. if (big->degree != 0) big->degree--;
  20. }
  21. void create (struct MyBig *big, int n) {
  22. const int ND = 21;
  23. char dig[ND];
  24. snprintf (dig, ND, "%d", n);
  25. int length = strlen (dig);
  26. reset (big);
  27. big->degree = length - 1;
  28. for (int i = 0; i < length; i++) {
  29. big->digits[i] = dig[length - 1 - i] - '0';
  30. }
  31. }
  32. void print (struct MyBig *big) {
  33. for (int i = big->degree; i >= 0; --i) {
  34. printf ("%d", big->digits[i]);
  35. }
  36. }
  37. void accumul (struct MyBig *a, struct MyBig *b) {
  38. int carry_out = 0;
  39. for (int i = 0; i <= b->degree; ++i) {
  40. int sum = carry_out + a->digits[i] + b->digits[i];
  41. if (sum >= 10) {
  42. carry_out = 1;
  43. a->digits[i] = sum - 10;
  44. } else {
  45. carry_out = 0;
  46. a->digits[i] = sum;
  47. }
  48. }
  49. int degree = b->degree;
  50. while (carry_out != 0) {
  51. degree++;
  52. int sum = carry_out + a->digits[degree];
  53. carry_out = sum/10;
  54. a->digits[degree] = sum % 10;
  55. }
  56. if (a->degree < degree) a->degree = degree;
  57. if (degree > NDIGITS) {
  58. printf ("OVERFLOW!!\n");
  59. exit (1);
  60. }
  61. }
  62. void copy (struct MyBig *a, struct MyBig *b) {
  63. reset (a);
  64. a->degree = b->degree;
  65. for (int i = 0; i <= a->degree; ++i) {
  66. a->digits[i] = b->digits[i];
  67. }
  68. }
  69. void fact_big (struct MyBig *ans, unsigned int num) {
  70. create (ans, 1);
  71. int temp = 1;
  72. while (temp <= num){
  73. int ctr = 0;
  74. struct MyBig temp2;
  75. reset (&temp2);
  76. while (ctr != temp){
  77. accumul (&temp2, ans);
  78. ctr ++;
  79. }
  80. copy (ans, &temp2);
  81. temp ++;
  82. }
  83. return;
  84. }
  85. unsigned long long int fact (unsigned int num) {
  86. unsigned long long int ans = 1;
  87. int temp = 1;
  88. while (temp <= num){
  89. int ctr = 0;
  90. unsigned long long int temp2 = 0;
  91. while (ctr != temp){
  92. temp2 += ans;
  93. ctr ++;
  94. }
  95. ans = temp2;
  96. temp ++;
  97. }
  98. return ans;
  99. }
  100. void main(){
  101. unsigned long long int ans;
  102. unsigned int num;
  103. printf("Enter a number: ");
  104. scanf("%u", &num);
  105. ans = fact (num);
  106. printf("%u! is %llu\n", num, ans);
  107. struct MyBig fact;
  108. fact_big (&fact, num);
  109. printf("%u! is ", num);
  110. print (&fact);
  111. printf ("\n");
  112. }
展开查看全部

相关问题