2015-09-23 18 views
5

La libreria standard C fornisce la famiglia di funzioni round, lround e llround in C99. Tuttavia, queste funzioni non sono conformi allo standard IEEE-754, in quanto non implementano l'arrotondamento del "banchiere" da metà a pari come richiesto da IEEE. L'arrotondamento da metà a pari richiede che il risultato sia arrotondato al valore pari più vicino se il componente frazionario è esattamente 0,5. Lo standard C99 mandati invece mezzo lontano da zero come indicato sul cppreference.comconforme a IEEE-754 round-half-to-even

1-3) calcola il valore intero più vicino to arg (in formato in virgola mobile), arrotondamento casi metà strada lontano da zero, indipendentemente la modalità di arrotondamento corrente.

Il solito modo ad-hoc per implementare arrotondamento in C è l'espressione (int)(x + 0.5f) che, pur essendo in stretta incorrect IEEE-754 matematica, di solito è tradotto da compilatori nella corretta cvtss2si istruzioni. Tuttavia, questo non è certamente un presupposto portatile.

Come è possibile implementare una funzione che consente di arrotondare qualsiasi valore in virgola mobile con semantica semipermanente? Se possibile, la funzione dovrebbe basarsi solo sul linguaggio e sulla libreria semantica standard, in modo che possa operare su tipi a virgola mobile non IEEE. Se ciò non è possibile, è accettabile anche una risposta definita in termini di rappresentazioni bit IEEE-754. Si prega di caratterizzare le costanti in termini di <limits.h> o <limits>.

+0

Sono conformi allo standard, ma non implementano tutto."Arrotonda al più vicino, si lega da zero" è una delle modalità di arrotondamento IEEE-754. –

+2

IEEE-754 rileva che la modalità di arrotondamento scolastico esiste, ma è intesa solo per i decimali. 'Un'attuazione di questo standard deve fornire roundTiesToEven e i tre arrotondamenti diretti attributi. Un'implementazione in formato decimale di questo standard deve fornire roundTiesToAway come attributo selezionabile per gli arrotondamenti . L'attributo di arrotondamento roundTiesToAway non è richiesto per un'implementazione del formato binario . L'arrotondamento dei legami in pareggio è l'unica modalità portatile (e sensata). – 68ejxfcj5669

+3

Invece di 'round()' guarda 'rint()' usato con una modalità di arrotondamento predefinita di "round to closest or even". – njuffa

risposta

5

Arrotonda il numero x e se la differenza tra xe round (x) è esattamente +0,5 o -0,5 e round (x) è dispari, quindi round (x) è stato arrotondato nella direzione sbagliata, quindi sottrarre la differenza da x.

+0

È garantito che '(round (x) - x)' restituirà 0.5/-0.5 se il componente frazionario di x è esattamente la metà, cioè questa è un'operazione esatta? – 68ejxfcj5669

+2

Sì. Il risultato di 'round (x) - x' sarà esattamente rappresentabile (assumendo un formato IEEE 754). –

+1

Un trucco malvagio è usare '2.0 * round (0.5 * x)' nel caso in cui 'fabs (x - round (x)) == 0.5', che salva il compito un po 'disordinato di capire se' round (x) 'è pari o dispari. –

2

Utilizzare remainder(double x, 1.0) dalla libreria standard C. Questo è indipendente dalla modalità di arrotondamento corrente.

Le funzioni rimanenti calcolare la restante x REM y richiesto dalla IEC 60559

remainder() è utile qui come è incontra legami OP per persino requisito.


double round_to_nearest_ties_to_even(double x) { 
    x -= remainder(x, 1.0); 
    return x; 
} 

Codice di prova

void rtest(double x) { 
    double round_half_to_even = round_to_nearest_ties_to_even(x); 
    printf("x:%25.17le z:%25.17le \n", x, round_half_to_even); 
} 

void rtest3(double x) { 
    rtest(nextafter(x, -1.0/0.0)); 
    rtest(x); 
    rtest(nextafter(x, +1.0/0.0)); 
} 

int main(void) { 
    rtest3(-DBL_MAX); 
    rtest3(-2.0); 
    rtest3(-1.5); 
    rtest3(-1.0); 
    rtest3(-0.5); 
    rtest(nextafter(-0.0, -DBL_MAX)); 
    rtest(-0.0); 
    rtest(0.0); 
    rtest(nextafter(0.0, +DBL_MAX)); 
    rtest3(0.5); 
    rtest3(1.0); 
    rtest3(1.5); 
    rtest3(2.0); 
    rtest3(DBL_MAX); 
    rtest3(0.0/0.0); 
    return 0; 
} 

uscita

x:      -inf z:      -inf 
x:-1.79769313486231571e+308 z:-1.79769313486231571e+308 
x:-1.79769313486231551e+308 z:-1.79769313486231551e+308 
x: -2.00000000000000044e+00 z: -2.00000000000000000e+00 
x: -2.00000000000000000e+00 z: -2.00000000000000000e+00 
x: -1.99999999999999978e+00 z: -2.00000000000000000e+00 
x: -1.50000000000000022e+00 z: -2.00000000000000000e+00 
x: -1.50000000000000000e+00 z: -2.00000000000000000e+00 tie to even 
x: -1.49999999999999978e+00 z: -1.00000000000000000e+00 
x: -1.00000000000000022e+00 z: -1.00000000000000000e+00 
x: -1.00000000000000000e+00 z: -1.00000000000000000e+00 
x: -9.99999999999999889e-01 z: -1.00000000000000000e+00 
x: -5.00000000000000111e-01 z: -1.00000000000000000e+00 
x: -5.00000000000000000e-01 z: 0.00000000000000000e+00 tie to even 
x: -4.99999999999999944e-01 z: 0.00000000000000000e+00 
x:-4.94065645841246544e-324 z: 0.00000000000000000e+00 
x: -0.00000000000000000e+00 z: 0.00000000000000000e+00 
x: 0.00000000000000000e+00 z: 0.00000000000000000e+00 
x: 4.94065645841246544e-324 z: 0.00000000000000000e+00 
x: 4.99999999999999944e-01 z: 0.00000000000000000e+00 
x: 5.00000000000000000e-01 z: 0.00000000000000000e+00 tie to even 
x: 5.00000000000000111e-01 z: 1.00000000000000000e+00 
x: 9.99999999999999889e-01 z: 1.00000000000000000e+00 
x: 1.00000000000000000e+00 z: 1.00000000000000000e+00 
x: 1.00000000000000022e+00 z: 1.00000000000000000e+00 
x: 1.49999999999999978e+00 z: 1.00000000000000000e+00 
x: 1.50000000000000000e+00 z: 2.00000000000000000e+00 tie to even 
x: 1.50000000000000022e+00 z: 2.00000000000000000e+00 
x: 1.99999999999999978e+00 z: 2.00000000000000000e+00 
x: 2.00000000000000000e+00 z: 2.00000000000000000e+00 
x: 2.00000000000000044e+00 z: 2.00000000000000000e+00 
x: 1.79769313486231551e+308 z: 1.79769313486231551e+308 
x: 1.79769313486231571e+308 z: 1.79769313486231571e+308 
x:      inf z:      inf 
x:      nan z:      nan 
x:      nan z:      nan 
x:      nan z:      nan 
5

La libreria standard C fornisce la round, lround e llround famiglia di funzioni in C99. Tuttavia, queste funzioni non sono IEEE-754 compatibile, perché non attuare il "arrotondamento" di mezzo-a-even come previsto dalla IEEE ...

Non ha senso parlare di se o non una singola funzione è "conforme allo standard IEEE-754". La conformità IEEE-754 richiede che sia disponibile una serie di operazioni sui tipi di dati con semantica definita. Non richiede che quei tipi o operazioni abbiano nomi specifici, né richiede che solo siano disponibili. Un'implementazione può fornire qualsiasi funzione addizionale che desidera e deve ancora essere conforme.Se un'implementazione vuole fornire round-to-odd, round-random, round-away-from-zero e trap-if-ines, può farlo.

Nei IEEE-754 richiede in realtà per l'arrotondamento è che le seguenti sei operazioni sono disponibile:

convertToIntegerTiesToEven (x)

convertToIntegerTowardZero (x)

convertToIntegerTowardPositive (x)

convertToIntegerTowardNegative (x)

convertToIntegerTiesToAway (x)

convertToIntegerExact (x)

In C e C++, gli ultimi cinque di queste operazioni sono tenuti a il trunc, ceil, floor, round e rint funzioni, rispettivamente. C11 e C++ 14 non hanno un legame per il primo, ma le revisioni future useranno roundeven. Come puoi vedere, round è effettivamente una delle operazioni richieste.

Tuttavia, roundeven non è disponibile nelle implementazioni attuali, che ci porta alla prossima parte della tua domanda:

Il solito modo ad-hoc per implementare arrotondamento in C è l'espressione (int)(x + 0.5f) che, pur essendo errato nella matematica IEEE-754, viene solitamente tradotto dai compilatori nell'istruzione cvtss2si corretta. Tuttavia, questo non è certamente un presupposto portatile.

I problemi con questa espressione vanno ben oltre il "rigore della matematica IEEE-754". È totalmente errato per il valore negativo x, fornisce la risposta errata per nextDown(0.5) e trasforma tutti gli interi dispari nel bin 2 ** 23 in numeri interi pari. Qualsiasi compilatore che lo traduca in cvtss2si è orribilmente, orribilmente rotto. Se hai un esempio di ciò, mi piacerebbe vederlo.

Come è possibile implementare una funzione che consente di arrotondare qualsiasi valore in virgola mobile con semantica pari o inferiore alla metà?

Come njuffa notato in un commento, è possibile garantire che la modalità predefinita di arrotondamento è impostata e l'uso rint (o lrint, come sembra, come si vuole realmente un risultato intero), oppure è possibile implementare il proprio funzione di arrotondamento chiamando round e quindi risolvendo i casi a metà strada come gnasher729 suggerisce. Una volta adottate le associazioni n1778 per C, sarà possibile utilizzare le funzioni roundeven o fromfp per eseguire questa operazione senza dover controllare la modalità di arrotondamento.

+0

Sono d'accordo che (int) (x + 0.5f) è sbagliato per molte ragioni. Tuttavia, MSVC in particolare rileva questo idioma e lo sostituisce con cvtss2si, che suppongo sia il motivo per cui viene comunemente utilizzato. – 68ejxfcj5669

+0

@ 68ejxfcj5669: dubito che sia il caso, ma mi piacerebbe essere smentito. Non ho una macchina Windows per testare, e la ricerca sul web non genera alcun account di questa trasformazione. Hai un esempio reale di un programma in cui MSVC lo ha fatto? –

1

Il tipo di dati float può rappresentare tutti i numeri interi, ma non frazioni, nell'intervallo da 8388608.0f a 16777216.0f. I numeri float superiori a 8388607.5f sono numeri interi e non è necessario arrotondare. Aggiungendo 8388608.0f a qualsiasi valore non negativo float che è inferiore a quello produrrà un numero intero che verrà arrotondato in base alla modalità di arrotondamento corrente (in genere round-half-even). La sottrazione 8388608.0f produrrà quindi una versione correttamente arrotondata dell'originale (supponendo che si trovasse in un intervallo adatto).

Quindi, dovrebbe essere possibile fare qualcosa di simile:

float round(float f) 
{ 
    if (!(f > -8388608.0f && f < 8388608.0f)) // Return true for NaN 
    return f; 
    else if (f > 0) 
    return (float)(f+8388608.0f)-8388608.0f; 
    else 
    return (float)(f-8388608.0f)+8388608.0f; 
} 

e sfruttare il comportamento di arrotondamento naturale di addizione, senza dover utilizzare qualsiasi altra struttura "round-to-integer".

0

Quanto segue è una semplice implementazione di a metà anche del programma che segue lo standard di arrotondamento IEEE.

Logic: errore = 0,00001

  1. numero = 2.5
  2. temp = floor (2,5)% 2 = 2% 2 = 0
  3. x = -1 + temperatura = -1
  4. x * errore + numero = 2,40009
  5. rotondo (2,40009) = 2

Nota: L'errore qui è di 0,00001, cioè, se si verifica 2,500,001 mila, allora sarà arrotondare 2 invece di 3

Python 2.7 applicazione:

temp = (number)  
rounded_number = int(round(-1+ temp%2)*0.00001 + temp) 

Implementazione C++: (Utilizzare matematica.h per funzione piano)

float temp = (number)  
int rounded_number = (int)((-1+ temp%2)*0.00001 + temp + 0.5) 

L'output che ciò darebbe sarebbe come segue acc. agli standard:

(3.5) -> 4

(2.5) -> 2


Modifica 1: Come sottolineato da @ Mark Dickinson nei commenti. L'errore può essere modificato come richiesto nel codice per standardizzarlo. Per python, per trasformarlo nel più piccolo valore float possibile, puoi fare quanto segue.

import sys 
error = sys.float_info.min 
+1

Se 2.500001 arrotonda a 2 invece di 3, allora questo non è un round conforme agli standard. –

+0

Il programma è scritto per qualcuno che vuole implementarlo nel proprio codice. L'errore può essere modificato in qualsiasi punto di precisione necessario nel codice, vale a dire, può anche essere il più piccolo valore float possibile. 'error = sys.float_info.min' (per Python), e un equivalente smiliar per C++ –

+0

Ok, ma non risponde alla domanda: l'OP sta cercando un'operazione di arrotondamento conforme allo standard IEEE 754. Il codice in questa risposta non lo fornisce. E l'uso di 'error = sys.float_info.min' non è una soluzione. Se verifichi il tuo codice, scoprirai che anche il codice corrente non dà "4" da un input di '3.5'; dà '3'. –

Problemi correlati