[C] Calcolo del fattoriale

Nexus991
Ho dei dubbi su questo esercizio sulle union:

Il risultato del calcolo del fattoriale $n!$ si può rappresentare in una variabile intera a 64 bit solo se $n \leq 20$. Scrivete una funzione che, se chiamata con un argomento $n \leq 20$, restituisca un valore di tipo unsigned long int, che contiene il fattoriale calcolato esattamente, mentre per $n > 20$ restituisca un valore di tipo double che contiene il logaritmo naturale del fattoriale calcolato usando l'approssimazione di Stirling:
$$ n! = \sqrt{2 \pi} n^{n + 1/2} \exp(-n) $$
La funzione, che restituisce una union, dovrà essere chiamata tenendo conto di queste 2 possibilità.

Io ho pensato di fare cosi:

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#define MY_RUIN -9

union factorial {
  double N;
  unsigned long int n;
};

union factorial fact;

union factorial CalcoloFattoriale (unsigned int numero);

int main(int argc, char *argv[]) {
  unsigned long int n;

  if (argc != 2) {
    fprintf(stderr, "usage %s <n>\n",argv[0]);
    exit(MY_RUIN);
  }

  n = (unsigned int) abs(atoi(argv[1]));

  fact = CalcoloFattoriale(n);
  
  if (n > 20) {
    printf("Il calcolo del logaritmo del fattoriale di %u da come risultato: %lg\n",n,fact.N);
    printf("\nLa union contiene il tipo double\n");
  }
  else {
    printf("Il calcolo del fattoriale di %u da come risultato: %lu\n",n,fact.n);
    printf("\nLa union contiene il tipo unsigned long int\n");
  }
}

union factorial CalcoloFattoriale (unsigned int numero) {
  long int fattoriale = 1;

  if (numero <= 20) {
    if (numero == 0) {
      fact.n = 1;
      return fact;
    }
    else {
      while (numero > 0) {
        fattoriale *= numero; 
	numero--;
      }
      fact.n = fattoriale;
      return fact;
    }
  }

  else {
    fact.N = log(sqrt(2*M_PI) * pow((double)numero,numero + 0.5)*exp(-(double)numero));
    return fact;
  } 
}
		


Secondo voi è ciò che si aspettava l'esercizio o ci sarebbe da aggiungere dell'altro?
Inoltre, c'è un'alternativa ad atoi() per gli unsigned int?

Risposte
apatriarca
Ho letto il codice velocemente, ma credo sia più o meno quello che si aspettava il professore. Che io sappia non esiste alcuna funzione standard per convertire una stringa in un intero senza segno. Va bene usare atoi.

vict85
Mi sfugge perché il risultato dovrebbe essere una union, mi sembra più pratico usare 2 funzioni distinte. Ma se te lo chiede il professore :smt102

Comunque, a cosa ti serve la variabile [inline]fact[/inline]? La funzione deve tornare il valore e quindi non dovresti usare variabili globali.

L'uso delle union senza "variabili di controllo" che ne detemino il tipo, non è molto consigliabile. Insomma, il tipo dovrebbe essere qualcosa come:
enum factorialType
{
    FACT_TYPE_UNKNOWN,
    FACT_TYPE_INT,
    FACT_TYPE_DOUBLE
};

struct factorial
{
    enum factorialType type;
    union /* unione anonima, richiede lo standard C11 */ {
        double double_value;
        unsigned long int int_value;
    };
};


Non capisco perché usi [inline]exit[/inline] con quel valore casuale. [inline]return EXIT_FAILURE[/inline] non va bene?

Secondo me non ha molto senso usare abs, sinceramente ritornerei un errore in caso il valore sia negativo.

[inline]M_PI[/inline] non è standard quindi dovresti farci un po' di attenzione.

Venendo alla matematica, si ha che \(\log(ab) = \log(a) + \log(b)\). Quindi \(\log(\sqrt{2\pi}n^{n+\frac12}\exp(-n)) = \log(\sqrt{2\pi})\log(n^{n+\frac12})\log(\exp(-n))\). A questo punto capirai che usando il logaritmo della potenza e il fatto che log e exp sono inversi hai che:
\begin{align*} \log(\sqrt{2\pi}n^{n+\frac12}\exp(-n)) &= \log(\sqrt{2\pi})\log(n^{n+\frac12})\log(\exp(-n)) \\
&= \frac12\log(2\pi) + \biggl(n+\frac12\biggr)\log(n) -n
\end{align*}
A meno di miei errori di calcolo ovviamente...

Ricapitolando
#define _USE_MATH_DEFINES  // necessario in visual studio per abilitare il non-standard M_PI
#include <math.h>

#if !defined( M_PI )  // aggiunto per scrupolo
#define M_PI 3.14159265358979323846
#endif

enum factorialType
{
    FACT_TYPE_UNKNOWN,
    FACT_TYPE_INT,
    FACT_TYPE_DOUBLE
};

struct factorial
{
    enum factorialType type;
    union /* unione anonima, richiede lo standard C11 */ {
        double double_value;
        unsigned long int int_value;
    };
};

struct factorial CalcoloFattoriale( unsigned int numero );

int
main( int argc, char* argv[] )
{
    if ( argc != 2 )
    {
        fprintf( stderr, "uso %s <n>\n", argv[ 0 ] );
        return EXIT_FAILURE;
    }

    int n = atoi( argv[ 1 ] );
    if ( n < 0 )
    {
        fprintf( stderr, "Il valore richiesto non e' valide %s\n", argv[ 1 ] );
        return EXIT_FAILURE;
    }

    struct factorial fact = CalcoloFattoriale( n );

    switch ( fact.type )
    {
    case FACT_TYPE_INT:
        printf( "Il calcolo del fattoriale di %d da come risultato: %lu\n", n, fact.int_value );
        puts( "La union contiene il tipo unsigned long int" );
        break;
    case FACT_TYPE_DOUBLE:
        printf( "Il calcolo del logaritmo del fattoriale di %d da come risultato: %g\n", n,
                fact.double_value );
        puts( "La union contiene il tipo double" );
        break;
    default:
        fprintf( stderr, "factorial of unknown type\n" );
        return EXIT_FAILURE;
    }
}

struct factorial
CalcoloFattoriale( unsigned int numero )
{
    struct factorial fact;
    if ( numero <= 20 )
    {
        fact.type = FACT_TYPE_INT;
        fact.int_value = 1;

        while ( numero > 1 )
        {
            fact.int_value *= numero;
            numero--;
        }
        return fact;
    }

    fact.type = FACT_TYPE_DOUBLE;

    fact.double_value = ( numero + 0.5 ) * log( numero ) - numero + 0.5 * log( 2. * M_PI );
    return fact;
}

Nexus991
Ho capito, grazie delle correzioni, ma perchè usare puts anzichè printf?
Comunque non composto enum.
Inoltre non so perchè ma se inserisco da terminale valori negativi mi fa lo stesso il fattoriale :/

vict85
"Nexus99":
Ho capito, grazie delle correzioni, ma perchè usare puts anzichè printf?
Comunque non composto enum.
Inoltre non so perchè ma se inserisco da terminale valori negativi mi fa lo stesso il fattoriale :/


Ti succede con il mio codice? A me non succede e il codice mi pare corretto. L'uso di puts è per sfizio, non è che nella programmazione esista la risposta corretta, esistono vari codici che risolvono il problema. Per quanto riguarda puts, stampa la stringa e ci aggiunge un a capo alla fine (al contrario di fputs che non lo fa). Rispetto a printf non fa alcuna operazione sulla stringa quindi per stampare stringe lo preferisco.

Nexus991
Ah ho capito. Per quanto riguarda il fatto dei valori negativi ho risolto, avevo sbagliato io nell'implementazione del mio codice. Grazie dell'aiuto

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.