2016-01-25 28 views
10

mi viene paranoico che una di queste funzioni possono dare un risultato errato in questo modo:Il cast di `std :: floor()` e `std :: ceil()` nel tipo intero dà sempre il risultato corretto?

std::floor(2000.0/1000.0) --> std::floor(1.999999999999) --> 1 
or 
std::ceil(18/3) --> std::ceil(6.000000000001) --> 7 

può succedere una cosa del genere? Se esiste effettivamente un rischio come questo, ho intenzione di utilizzare le funzioni seguenti per lavorare in sicurezza. Ma è davvero necessario?

constexpr long double EPSILON = 1e-10; 

intmax_t GuaranteedFloor(const long double & Number) 
{ 
    if (Number > 0) 
    { 
     return static_cast<intmax_t>(std::floor(Number) + EPSILON); 
    } 
    else 
    { 
     return static_cast<intmax_t>(std::floor(Number) - EPSILON); 
    } 
} 

intmax_t GuaranteedCeil(const long double & Number) 
{ 
    if (Number > 0) 
    { 
     return static_cast<intmax_t>(std::ceil(Number) + EPSILON); 
    } 
    else 
    { 
     return static_cast<intmax_t>(std::ceil(Number) - EPSILON); 
    } 
} 

(Nota: sto assumendo che il dato 'long double' argomento si inserisce nel 'intmax_t' tipo di ritorno.)

+1

Penserei che quegli esempi sarebbero sicuri (numeri interi, entro la precisione del punto di virgola mobile) ma, per esempio, "3.3/1.1" potrebbe concepibilmente dare "non precisamente 3". – TripeHound

+1

'EPSILON' non ti salverà. È la più piccola differenza significativa di ** a 1,0 **, ovvero il valore più piccolo che è possibile aggiungere a 1,0 per ottenere un valore diverso. Se il risultato potrebbe essere più grande o più piccolo di 1.0, sarà necessario un diverso EPSILON. Se pensi di aver bisogno di 'EPSILON' per qualcosa, è probabile che stai per introdurre un bug molto sottile nel tuo software. – DevSolar

risposta

17

La gente spesso avere l'impressione che le operazioni in virgola mobile producono risultati con errori piccoli, imprevedibili, quasi casuali. Questa impressione non è corretta.

I calcoli a virgola mobile sono il più precisi possibile. 18/3 produrrà sempre esattamente 6. Il risultato di 1/3 non sarà esattamente un terzo, ma sarà il numero più vicino a un terzo che è rappresentabile come numero a virgola mobile.

Quindi gli esempi che hai mostrato sono garantiti per funzionare sempre. Per quanto riguarda il tuo "pavimento/ceil garantito" suggerito, non è una buona idea. Certe sequenze di operazioni possono facilmente far saltare l'errore molto al di sopra di 1e-10, e alcuni altri casi d'uso richiedono che lo 1e-10 venga riconosciuto correttamente (e bloccato) come diverso da zero.

Come regola empirica, i valori di epsilon codificati sono errori nel codice.

3

Negli esempi specifici che stai elencando, non penso che questi errori si verifichino mai.

std::floor(2000.0 /*Exactly Representable in 32-bit or 64-bit Floating Point Numbers*//1000.0 /*Also exactly representable*/) --> std::floor(2.0 /*Exactly Representable*/) --> 2 
std::ceil(18/3 /*both treated as ints, might not even compile if ceil isn't properly overloaded....?*/) --> 6 
std::ceil(18.0 /*Exactly Representable*//3.0 /*Exactly Representable*/) --> 6 

Detto questo, se si dispone di matematica che dipende da queste funzioni si comportano esattamente correttamente per numeri in virgola mobile, che può illuminare un difetto di progettazione è necessario riconsiderare/Riesaminare.

-7

È probabile che tali risultati vengano visualizzati quando si lavora con i doppi. È possibile utilizzare round o è possibile sottrarre 0,5 quindi utilizzare la funzione std :: ceil.

+0

La domanda riguarda i calcoli con operandi * e risultati * che sono rappresentabili come numeri interi. – DevSolar

Problemi correlati