2012-02-10 20 views
15

Desidero stampare un valore doppio su std::cout portabile (GCC, clang, MSVC++) in modo che l'output sia lo stesso su tutte le piattaforme.Stampa portatile di esponente di un doppio in iostreams C++

Ho un problema con la formattazione dell'esponente. Il seguente programma

#include <iostream> 
int main() 
{ 
    std::cout << 0.1e-7 << std::endl; 
    return 0; 
} 

ha questa uscita con GCC:

1e-08 

e la successiva uscita con MSVC

1e-008 

Come posso fare entrambe le uscite lo stesso?

Mi dispiace se questa è una domanda stupida ma non ho ancora trovato una risposta. Tutta la formattazione sembra evolvere intorno alla formattazione del tutto prima della mantissa ...

EDIT: L'uscita del GCC è 1e-08 non 1e-8 (come inizialmente indicato) in modo che è conforme. Dispiace per la confusione.

EDIT2: in realtà ribattezzato "mantissa" in "esponente" a seguito del commento di Dietmar. There also is a section on Wikipedia on mantissa vs. significant.

+0

Hai guardato [manipolatori] (http://www.cplusplus.com/reference/iostream/manipulators/)? – razlebe

+1

@razlebe: Non sono riuscito a trovare una risposta usando i manipolatori. – Manuel

+0

Trovo che GCC sia incoerente perché stampa '1.e-08' e' 1.e-18' (due cifre) eppure stampa '1.e-256' (tre cifre). Non sono riuscito a trovare una libreria di stream che risolva questo problema (ho provato con iostream ovviamente e Boost.Format). Quindi, se si desidera avere una larghezza fissa doppia, è necessario prenotare e aggiungere spazio per l'eventuale terza cifra dell'esponente. – alfC

risposta

11

Non c'è manipolatore controllare la formattazione dell'esponente (presumo si intende l'esponente piuttosto che la mantissa, anche, il nome "ufficiale" utilizzato per la mantissa è significativo). Per peggiorare le cose, non riesco a vedere alcuna regola nello standard C che limiti la formattazione dell'esponente. Mi rendo conto che si tratta di C++ ma ai fini dei dettagli di formattazione, lo standard C++ si riferisce allo standard C.

L'unico approccio di cui sono a conoscenza è l'utilizzo di un proprio facet std::num_put<char> che formatta i valori come desiderato. Questo aspetto verrà quindi inserito in uno std::locale che a sua volta è imbue() edstd::cout. Un'implementazione potenziale potrebbe utilizzare la faccetta predefinita std::num_put<char> (o snprintf() che è, sfortunatamente, probabilmente più semplice) per formattare il numero in virgola mobile e quindi rimuovere gli zeri iniziali dall'esponente.

+4

La formattazione C++ è definita in termini di 'printf', che dice che" L'esponente deve sempre contenere almeno due cifre. " (Quindi g ++ non è conforme.) Sto leggendo dallo standard Posix qui, che dovrebbe essere conforme allo standard C. Ma ho vaghi ricordi di testo che dicono che non può essere più di due caratteri se non necessario (il che renderebbe VC in errore); Ricordo di aver discusso di questo molto, molto tempo fa, e fu stabilito che VC non era conforme (il che non risolve il problema se questo è il compilatore che devi usare). –

+1

@JamesKanze è possibile leggerlo come se intendessero "carattere" invece di cifre, nel qual caso il segno meno conta? – Flexo

+0

Ho solo dato uno sguardo veloce allo standard C99 (non ho una versione più recente) e non ho trovato alcuna regola di formattazione per l'esponente. In ogni caso, qualsiasi cosa sia conforme o non conforme, sembra esserci una certa libertà di implementazione e c'è sicuramente una variazione nell'implementazione. Continuo a pensare che il metodo sopra descritto sia probabilmente l'approccio più semplice per modificare in modo trasparente la formattazione prodotta. –

3

Mentre la risposta di Dietmar è l'unica risposta veramente portatile pulito e probabilmente, ho trovato per caso una risposta rapida-and-dirty: MSVC fornisce la funzione _set_output_format che è possibile utilizzare per passare a "esponente di stampa come due cifre".

La seguente classe RAII può essere istanziata nella funzione main() per fornire lo stesso comportamento di GCC, CLANG e MSVC.

class ScientificNotationExponentOutputNormalizer 
{ 
public: 
    unsigned _oldExponentFormat; 

    ScientificNotationExponentOutputNormalizer() : _oldExponentFormat(0) 
    { 
#ifdef _MSC_VER 
     // Set scientific format to print two places. 
     unsigned _oldExponentFormat = _set_output_format(_TWO_DIGIT_EXPONENT); 
#endif 
    } 

    ~ScientificNotationExponentOutputNormalizer() 
    { 
#ifdef _MSC_VER 
     // Enable old exponent format. 
     _set_output_format(_oldExponentFormat); 
#endif 
    } 
}; 
+0

Ciò richiede almeno MSVCR80.dll, che è doloroso configurare per funzionare con MinGW. – Manuel

+0

Nota: come indicato nella documentazione, 'Questa funzione è obsoleta. A partire da Visual Studio 2015, non è disponibile nel CRT – BenC

2

Il problema è che Visual C++ non stava seguendo lo standard C99. In Visual C++ 2015, _set_output_format stato rimosso dal compilatore ora segue lo standard:

I %e e %E identificatori di formato formattare un numero decimale come mantissa decimale ed esponente. Gli specificatori di formato %g e %G in alcuni casi formattano anche i numeri in questo modulo. Nelle versioni precedenti, il CRT generava sempre stringhe con esponenti a tre cifre.Ad esempio, printf("%e\n", 1.0) stamperebbe 1.000000e+000. Questo non era corretto: C richiede che se l'esponente è rappresentabile usando solo una o due cifre, allora solo due cifre devono essere stampate.

In Visual Studio 2005 è stato aggiunto un commutatore di conformità globale: _set_output_format. Un programma potrebbe chiamare questa funzione con l'argomento _TWO_DIGIT_EXPONENT, per abilitare la stampa esponenziale conforme. Il comportamento predefinito è stato modificato in modalità di stampa esponenziale conforme agli standard.

Vedere Breaking Changes in Visual C++ 2015. Per le versioni precedenti, vedere la risposta di @ Manuel.

FYI, nel C99 standard, possiamo leggere:

e, E

Un doppio argomento che rappresenta un numero a virgola mobile viene convertito in stile [-] d.ddd e (+ -) dd, dove c'è una cifra (che è diversa da zero se l'argomento è diverso da zero) prima del carattere del punto decimale e del numero di cifre dopo che è uguale alla precisione; se manca la precisione, viene preso come 6; se la precisione è zero e il flag # non è specificato, non viene visualizzato alcun carattere decimale. Il valore viene arrotondato al numero appropriato di cifre. Lo specificatore di conversione E produce un numero con E invece di e introduce l'esponente. L'esponente contiene sempre almeno due cifre e solo il numero di cifre in più necessario per rappresentare l'esponente. Se il valore è zero, l'esponente è zero. Un doppio argomento che rappresenta un infinito o NaN viene convertito nello stile di un identificatore di conversione f o F.

Questa è una differenza rispetto a C90 che non ha fornito alcuna indicazione riguardante la lunghezza esponenziale richiesta.

Nota che i recenti Visual C++ modifiche riguardano anche come stampare nan, inf ecc