Calcolare [a*2^n/b]

di il
6 risposte

Calcolare [a*2^n/b]

Come da titolo mi serve calcolare l'espressione [a*2^n/b], dove a, b e n sono interi tali che 0<a<b< 2^32 e 32<=n<64, mentre le parentesi quadre indicano l'operazione di troncamento.

Volendo rimanere nell'ambito degli interi, ma senza scomodare classi/librerie sui big_int, avrei pensato a qualcosa del genere:

#include <stdio.h>
#include <inttypes.h>

uint64_t a_mul_2_to_n_div_b(const uint32_t a, const uint32_t b, uint8_t n)
{
    n -= 32;
    uint64_t a_ = (uint64_t)a << 32;
    return (a_ / b << n) + (a_ % b << n) / b;
}

int main()
{
    uint32_t a = 16845074;
    uint32_t b = 2762592030;
    uint8_t n = 40;
    
    uint64_t d = a_mul_2_to_n_div_b(a, b, n);
    printf("%"PRIu64"\n", d);
}

E' corretto?

Volendo invece utilizzare float/double suppongo si possa fare qualcosa del genere:

#include <stdio.h>
#include <inttypes.h>

int main()
{
    uint32_t a = 16845074;
    uint32_t b = 2762592030;
    uint8_t n = 40;

    uint64_t d = a * (double)((uint64_t)1 << n) / b;
    printf("%"PRIu64"\n", d);
}

Ma ottengo un risultato sbagliato, e quindi, non avendo mai affrontato l'analisi numerica relativa ai numeri in virgola mobile, non so quanto un approccio del genere convenga (anche in termini di costo computazionale rispetto al precedente codice con gli interi).

In definitiva qual è secondo voi il modo più efficiente di calcolare quell'espressione?

6 Risposte

  • Re: Calcolare [a*2^n/b]

    Puoi spiegare il motivo dei vari passaggi?

    Io farei una cosa così

    uint64_t a_ = (uint64_t)a << n;
    return a_ / b;

    Al massimo puoi spostare a sinistra di (n - b) e risparmiarti la divisione se b è una potenza di 2; il dominio di n dovrebbe essere (0, 31) per evitare di scartare bit a sinistra.

  • Re: Calcolare [a*2^n/b]

    11/03/2024 - Alexv ha scritto:


    Puoi spiegare il motivo dei vari passaggi?

    Innanzitutto mi sono dimenticato di specificare che a<b, il che ci assicura che il risultato possa essere contenuto in un uint64_t (ora aggiorno anche il post iniziale).

    Per quanto riguarda la logica dietro la funzione: 

    11/03/2024 - Alexv ha scritto:


    Io farei una cosa così

    uint64_t a_ = (uint64_t)a << n;
    return a_ / b;

    Essendo a un uint32_t ed essendo 32<=n<64, il rischio di overflow è dietro l'angolo, e infatti avverrebbe anche con i valori riportati nel codice.

    P.S.
    Per scrivere quelle formule ho utilizzato l'editor di un altro forum, questo non supporta né latex né asciimath, giusto?

  • Re: Calcolare [a*2^n/b]

    Up

  • Re: Calcolare [a*2^n/b]

    La formula a_mul_2_to_n_div_b() è corretta. Capire se è quella più efficiente possibile è un altro paio di maniche: magari no perché c'è un modulo di mezzo, ma vallo a capire. I double li eviterei a prescindere

    p.s.: il metodo di Alexv si può usare con i tipi __uint128_t che ormai sono supportati da quasi tutti i compilatori, ma quasi sicuramente sarebbe meno efficiente di quello proposto

    EDIT: dal C23 uint128_t e int128_t diventano ufficiali

  • Re: Calcolare [a*2^n/b]

    15/03/2024 - Weierstrass ha scritto:


    La formula a_mul_2_to_n_div_b() è corretta. Capire se è quella più efficiente possibile è un altro paio di maniche: magari no perché c'è un modulo di mezzo, ma vallo a capire. I double li eviterei a prescindere

    In realtà penso che se il compilatore applica un minimo di ottimizzazione, il modulo dovrebbe essere a costo zero o quasi, visto che nella stessa espressione viene calcolata anche la divisione con gli stessi operandi.

    Per quanto riguarda i double, anche io non li uso quasi mai, tantomeno in casi del genere, in ogni caso ero curioso di sapere perché ottengo un risultato errato e se dal punto di vista dell'efficienza valeva la pena approfondire la questione. 

    15/03/2024 - Weierstrass ha scritto:


    EDIT: dal C23 uint128_t e int128_t diventano ufficiali

    Buono a sapersi!

  • Re: Calcolare [a*2^n/b]

    Perché prima di dividere hai un numero maggiore di 2^53, sei fuori dalla mantissa e la precisione va a farsi friggere

Devi accedere o registrarti per scrivere nel forum
6 risposte