2015-04-21 10 views
7

Se assegno un valore a un calcolo a virgola mobile su una variabile per primo, quindi assegno a un int non firmato con casting implicito, ottengo una risposta. Ma se assegno lo stesso calcolo direttamente all'int unsigned, sempre con il tipo implicito, ottengo una risposta diversa.Interpretato da un diverso risultato dello stesso cast, float to int

Qui di seguito è il codice di esempio ho compilato e corse per dimostrare:

#include <iostream> 



int 
main(int argc, char** argv) 
{ 
    float payloadInTons = 6550.3; 


    // Above, payloadInTons is given a value. 
    // Below, two different ways are used to type cast that same value, 
    // but the results do not match. 
    float tempVal = payloadInTons * 10.0; 
    unsigned int right = tempVal; 
    std::cout << " right = " << right << std::endl; 


    unsigned int rawPayloadN = payloadInTons * 10.0; 
    std::cout << " wrong = " << rawPayloadN << std::endl; 


    return 0; 
} 

Qualcuno ha informazioni sul perché "giusto" è giusto, e "sbagliato" è sbagliato?

A proposito, sto usando gcc 4.8.2 su Ubuntu 14.04 LTS, se è importante.

+2

Sarebbe un po 'più ... impressionante se includessi anche l'output che stai vedendo. Su [ideone.com] (http://ideone.com/0SMdPN) ho ottenuto 'giusto = 65503 sbagliato = 65502'. – unwind

+3

Questo ha il tag C, ma sembra C++. –

+2

È a causa della precisione. Se moltiplichi sia per '100.0' che per' 10.0', lo capirai. –

risposta

7

Si utilizzano valori letterali double. Con i letterali float corretti, va tutto bene.

int 
main(int argc, char** argv) 
{ 
    float payloadInTons = 6550.3f; 
    float tempVal = payloadInTons * 10.0f; 

    unsigned int right = tempVal; 
    std::cout << "  right = " << right << std::endl; 

    unsigned int rawPayloadN = payloadInTons * 10.0f; 
    std::cout << "also right = " << rawPayloadN << std::endl; 


    return 0; 
} 

uscita:

 right = 65503 
also right = 65503 
+0

In alternativa, si potrebbe usare il doppio ovunque, penso. –

+0

@ SamuelEdwinWard, ovviamente. Finché è * ovunque *, tali discrepanze non avverranno. – Quentin

4

Dopo accettare risposta

Questo non è un double vs float problema. È un binario in virgola mobile e una conversione al numero int/unsigned.

Tipico float utilizza la rappresentazione binary32 con non fornisce una rappresentazione esatta di valori come 6550.3.

float payloadInTons = 6550.3; 
// payloadInTons has the exact value of `6550.2998046875`. 

Moltiplicando per 10.0, sotto, assicura il calcolo è fatto con almeno double precisione con un risultato esatto di 65502.998046875. Il prodotto viene quindi riconvertito in float. Il valore double non è esattamente rappresentabile in float e viene arrotondato al migliore float con un valore esatto di 65503.0. Quindi tempVal converte right come desiderato con un valore di 65503.

float tempVal = payloadInTons * 10.0; 
unsigned int right = tempVal; 

Moltiplicando per 10.0, sotto, assicura il calcolo è fatto con almeno double precisione con un risultato esatto di 65502.998046875 come prima. Questa volta, il valore viene convertito direttamente in unsigned rawPayloadN con l'indesiderato con un valore di 65502. Questo perché il valore è troncato e non arrotondato.

unsigned int rawPayloadN = payloadInTons * 10.0; 

Il primo “lavorato” a causa della conversione era double a float a unsigned. Questo comporta 2 conversioni con è di solito cattivo. In questo caso, 2 torti hanno dato ragione.


Soluzione

Aveva provato codice float payloadInTons = 6550.29931640625; (il prossimo più piccolo float numero) sia risultato sarebbe stato 65502.

Il modo "giusto” per convertire un valore in virgola mobile a qualche tipo intero è spesso turno il risultato e quindi eseguire la conversione di tipo

float tempVal = payloadInTons * 10.0; 
unsigned int right = roundf(tempVal); 

Nota:. L'intero problema è complicazione dal valore di FLT_EVAL_METHOD. Se il valore dell'utente è diverso da zero, floating calcolo punto può verificarsi in una maggiore precisione del previsto.

printf("FLT_EVAL_METHOD %d\n", (int) FLT_EVAL_METHOD); 
+0

Capisco la tua spiegazione e mi piace l'uso deterministico di roundf() per un uso futuro. – donjuedo

+0

@donjuedo Il punto è che l'uso di letterali 'float' appropriati potrebbe non risolvere il problema. A seconda di 'FLT_EVAL_METHOD',' payloadInTons * 10.0f' e 'payloadInTons * 10.0' possono causare lo stesso codice esatto. Il problema principale è che i valori vicini a '6550.3' hanno un risultato di' 65503'. Ciò non si verificherà con l'uso di "letterali corretti" ma usando l'arrotondamento. – chux

+0

Giusto. Per me, ci sono due punti, quello che ho affermato (perché è diverso) e quello che non ho fatto, ma tu mi hai aiutato con (come renderlo affidabile). Il primo aveva a che fare con la mia premessa errata, che tutto il calcolo era fatto con float. Ho potuto vedere come la conversione in int potrebbe essere imperfetta, ma ho perso il modo in cui due modi di usare float hanno ottenuto 2 risposte. Uno in realtà usava doppi di cui non ero a conoscenza, quindi la differenza. Sono d'accordo con te, che usare "letterali appropriati" non è il modo più efficace per ottenere sempre il risultato giusto. – donjuedo