为什么这种因子算法不准确

时间:2017-01-06 16:56:49

标签: c algorithm

抱歉,我觉得这很愚蠢,我准备失去一半的问题,但为什么这个算法不起作用?它起到了一定的作用。数字13后,阶乘有点偏。例如,数字在数十万个地方和之后并不完全匹配。

#include <stdio.h>

float factorial(unsigned int i) {

    if (i <= 1) {
        return 1;
    }
    return i * factorial(i - 1);
}

int  main() {
    int i = 13;
    printf("Factorial of %d is %f\n", i, factorial(i));
    return 0;
}

这是输出:

Factorial of 13 is 6227020800.000000

以下是不准确输出的示例:

Factorial of 14 is 87178289152.000000

数字14的输出实际上应该是这个(来自mathisfun.com)

  

14 87,178,291,200

我将返回类型更改为float以获得更准确的输出,但我从这里获得了大部分代码:https://www.tutorialspoint.com/cprogramming/c_recursion.htm

编辑:如果我更改为返回类型以使输出翻倍,则准确到21。我正在使用%Lf 字符串格式化程序来输出< em> printf 功能。

5 个答案:

答案 0 :(得分:11)

简单。 float无法在不损失精度的情况下准确存储16777216以上的整数。

int比浮动更好。但请尝试long long,以便正确存储19位数字。

答案 1 :(得分:3)

OP遇到float的精度限制。对于typical float16777216.0f以上的整数值并非所有都可以完全表示。 此点之上的某些阶乘结果可以准确表示。

让我们尝试不同的类型 在11!float结果超出16777216.0f且完全正确 在14!,由于精度有限,float结果不准确。
23!,由于精度有限,double结果不准确。

21!,答案超出我的uintmax_t 范围35!,答案超出了float 范围171!,答案超出了double 范围

string 表示无穷无尽,直到达到缓冲区限制。

#include <stdint.h>
#include <string.h>
#include <stdio.h>

uintmax_t factorial_uintmax(unsigned int i) {
  if (i <= 1) {
    return 1;
  }
  return i * factorial_uintmax(i - 1);
}

float factorial_float(unsigned int i) {
  if (i <= 1) {
    return 1;
  }
  return i * factorial_float(i - 1);
}

double factorial_double(unsigned int i) {
  if (i <= 1) {
    return 1;
  }
  return i * factorial_double(i - 1);
}

char * string_mult(char *y, unsigned base, unsigned x) {
  size_t len = strlen(y);
  unsigned acc = 0;
  size_t i = len;
  while (i > 0) {
    i--;
    acc += (y[i] - '0') * x;
    y[i] = acc % base + '0';
    acc /= base;
  }
  while (acc) {
    memmove(&y[1], &y[0], ++len);
    y[0] = acc % base + '0';
    acc /= base;
  }
  return y;
}

char *factorial_string(char *dest, unsigned int i) {
  strcpy(dest, "1");
  for (unsigned m = 2; m <= i; m++) {
    string_mult(dest, 10, m);
  }
  return dest;
}

void factorial_test(unsigned int i) {
  uintmax_t u = factorial_uintmax(i);
  float f = factorial_float(i);
  double d = factorial_double(i);
  char s[2000];
  factorial_string(s, i);
  printf("factorial of %3d is uintmax_t: %ju\n", i, u);
  printf("                    float:     %.0f %s\n", f, "*" + (1.0 * f == u));
  printf("                    double:    %.0f %s\n", d, "*" + (d == u));
  printf("                    string:    %s\n", s);
}

int main(void) {
  for (unsigned i = 11; i < 172; i++)
    factorial_test(i);
  return 0;
}

输出

factorial of  11 is uintmax_t: 39916800
                    float:     39916800 
                    double:    39916800 
                    string:    39916800
factorial of  12 is uintmax_t: 479001600
                    float:     479001600 
                    double:    479001600 
                    string:    479001600
factorial of  13 is uintmax_t: 6227020800
                    float:     6227020800 
                    double:    6227020800 
                    string:    6227020800
factorial of  14 is uintmax_t: 87178291200
                    float:     87178289152 *
                    double:    87178291200 
                    string:    87178291200
factorial of  20 is uintmax_t: 2432902008176640000
                    float:     2432902023163674624 *
                    double:    2432902008176640000 
                    string:    2432902008176640000
factorial of  21 is uintmax_t: 14197454024290336768
                    float:     51090940837169725440 *
                    double:    51090942171709440000 *
                    string:    51090942171709440000
factorial of  22 is uintmax_t: 17196083355034583040
                    float:     1124000724806013026304 *
                    double:    1124000727777607680000 *
                    string:    1124000727777607680000
factorial of  23 is uintmax_t: 8128291617894825984
                    float:     25852017444594485559296 *
                    double:    25852016738884978212864 *
                    string:    25852016738884976640000
factorial of  34 is uintmax_t: 4926277576697053184
                    float:     295232822996533287161359432338880069632 *
                    double:    295232799039604119555149671006000381952 *
                    string:    295232799039604140847618609643520000000
factorial of  35 is uintmax_t: 6399018521010896896
                    float:     inf *
                    double:    10333147966386144222209170348167175077888 *
                    string:    10333147966386144929666651337523200000000
factorial of 170 is uintmax_t: 0
                    float:     inf *
                    double:    72574156153079940453996357155895914678961840000000... *
                    string:    72574156153079989673967282111292631147169916812964...
factorial of 171 is uintmax_t: 0
                    float:     inf *
                    double:    inf *
                    string:    12410180702176678234248405241031039926166055775016...

答案 2 :(得分:2)

  

为什么这种因子算法不准确

你的algorithm就没有错。只是您使用的数据类型对它们可以存储的最大数量有限制。无论您选择哪种算法,这都是一个问题。您可以将数据类型从float更改为类似long double的内容,以保存更大的内容。但是一旦阶乘值超过该数据类型的容量,它最终仍会失败。在我看来,如果传入的参数大于你选择的数据类型可以支持的值,你应该在你的阶乘函数中放一个条件而不计算任何东西。

答案 3 :(得分:2)

float可以表示比int更宽的范围数字,但它不能代表该范围内的所有值 - 当您接近范围的边缘时(即,随着值的大小增加),可表示值之间的差距变得更大。

例如,如果您不能表示0.123和0.124之间的值,那么您也不能表示123.0和124.0之间,或1230.0和1240.0,或12300.0和12400.0等之间的值(当然,IEEE-754单精度{ {1}}比 更精确。

话虽如此,float应该能够准确地表示最多2 24 的所有整数值,所以我打赌这个问题在{{1}调用 - float参数是&#34;促进&#34;至printf,因此涉及到代理更改,这可能会导致精确度下降。

尝试将float的返回类型更改为double,看看是否有帮助。

&lt; gratuitous rant&gt;

每次我看到一个递归因子函数我都想尖叫。在这种特殊情况下,递归在代码清晰度或性能方面都没有改进迭代解决方案:

factorial

并且由于这么多函数调用的开销,实际上会导致更差的性能。

是的,阶乘的定义是递归的,但是阶乘函数的实现并不是必须的。 Fibonacci序列相同。甚至还有斐波那契数字的封闭形式解决方案

double

首先不需要任何循环。

递归非常适合将数据划分为相对较少的,相等大小的子集(Quicksort,树遍历等)的算法。对于这样的事情,分区是1个元素的N-1个子集?没那么多。

&lt; / gratuitous rant&gt;

答案 4 :(得分:2)

有人在前一段时间发布了类似的问题。大家的共识是,如果你正在为工作而使用大量的库(如GMP),并且如果它是编程练习,则使用字符数组编写解决方案。

例如:

/* fact50.c

   calculate a table of factorials from 0! to 50! by keeping a running sum of character digits
*/

#include <stdio.h>
#include <string.h>

int main (void)
{
    printf ("\n                            Table of Factorials\n\n");

    // length of arrays = 65 character digits

    char str[] =
    "00000000000000000000000000000000000000000000000000000000000000000"; 
    char sum[] =
    "00000000000000000000000000000000000000000000000000000000000000001"; 

    const int len = strlen (str);
    int index;

    for ( int i = 0; i <= 50; ++i ) {

        memcpy (str, sum, len);

        for ( int j = 1; j <= i - 1; ++j ) {

            index = len - 1;        
            int carry = 0;

            do {
                int digit = (sum[index] - '0') + (str[index] - '0') + carry;            
                carry = 0;
                if ( digit > 9 ) {
                    carry = 1;
                    digit %= 10;
                }            
                sum[index] = digit + '0';
                --index;
            }
            while ( index >= 0 );

        }

        printf ("%2i! = ", i);
        for ( index = 0; sum[index] == '0'; ++index )
            printf ("%c", '.');
        for ( ; index < len; ++index )
            printf ("%c", sum[index]);
        printf ("\n");        

    }

    return 0;
}