2012-02-10 26 views
15

Chcę wydrukować podwójną wartość do std::cout przenośnie (GCC, clang, MSVC++) w taki sposób, że wynik jest taki sam na wszystkich platformach.Przenośne drukowanie wykładnika podwójnego do C++ iostreams

Mam problem z formatowaniem wykładnika. Poniższy program

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

Ma to wyjście z GCC:

1e-08 

oraz następujące dane wyjściowe z MSVC

1e-008 

Jak mogę zrobić oba wyjścia są takie same?

Przykro mi, jeśli to głupie pytanie, ale nie znalazłem dotąd odpowiedzi. Wszystko formatowanie wydaje się rozwijać wokół formatowania wszystko przed mantysa ...

EDIT: Wyjście z GCC jest 1e-08 nie 1e-8 (jak pierwotnie podano), więc jest zgodne. Przepraszam za zamieszanie.

EDIT2: Właściwie zmieniono nazwę "mantysa" na "wykładnik" po uwadze Dietmara. There also is a section on Wikipedia on mantissa vs. significant.

+0

Pan spojrzał na [manipulatorów] (http://www.cplusplus.com/reference/iostream/manipulators/)? – razlebe

+1

@razlebe: Nie mogłem znaleźć odpowiedzi używając manipulatorów. – Manuel

+0

Uważam, że GCC jest niekonsekwentny, ponieważ wypisuje '1.e-08' i' 1.e-18' (dwie cyfry), a mimo to wypisuje '1.e-256' (trzy cyfry). Nie mogłem znaleźć biblioteki strumieniowej, która rozwiązuje to (próbowałem oczywiście za pomocą iostream i Boost.Format). Więc jeśli ktoś chce mieć ustalone podwójne szerokości, to musi zarezerwować i dodatkowe miejsce dla trzeciej cyfry wykładnika. – alfC

Odpowiedz

11

Nie ma manipulatora kontrolującego formatowanie wykładnika (zakładam, że masz na myśli wykładnik zamiast mantysy, a także "oficjalna" nazwa używana dla mantysy to znaczące). Co gorsza, nie widzę żadnej reguły w standardzie C, która ogranicza formatowanie wykładnika. Zdaję sobie sprawę, że chodzi o C++, ale dla celów szczegółów formatowania standard C++ odnosi się do standardu C.

Jedyne podejście, o którym wiem, to użycie własnego aspektu std::num_put<char>, który formatuje wartości według potrzeb. Ten aspekt zostałby następnie umieszczony w std::locale, który z kolei jest imbue() wydany w . Potencjalna implementacja może użyć domyślnego aspektu std::num_put<char> (lub snprintf(), który jest, niestety, prawdopodobnie prostszy) do sformatowania liczby zmiennoprzecinkowej, a następnie usunięcia zer wiodących z wykładnika.

+4

Formatowanie C++ jest zdefiniowane w terminach 'printf', który mówi, że" wykładnik zawsze zawiera co najmniej dwie cyfry. " (Więc g ++ nie jest zgodny.) Czytam tutaj ze standardu Posix, który powinien być zgodny ze standardem C. Ale mam niejasne wspomnienia z tekstu mówiącego, że nie może on być więcej niż dwoma znakami, chyba że jest to konieczne (co sprawiłoby, że VC również byłby zły); Pamiętam, że dyskutowałem o tym dawno, dawno temu i ustalono, że VC nie jest zgodny (co nie rozwiązuje problemu, jeśli jest to kompilator, którego musisz użyć). –

+1

@JamesKanze to możliwe, aby przeczytać go tak, jakby miał na myśli "znak" zamiast cyfry, w którym to przypadku liczony byłby znak minus? – Flexo

+0

Spojrzałem tylko na standard C99 (nie mam nowszej wersji) i nie znalazłem żadnych reguł formatowania dla wykładnika. W każdym razie, niezależnie od tego, co jest zgodne lub niezgodne, wydaje się, że istnieje pewna swoboda implementacji i zdecydowanie występują różnice we wdrażaniu. Nadal uważam, że opisana powyżej metoda jest prawdopodobnie najprostszym sposobem na przezroczystą zmianę formatowania. –

3

Chociaż odpowiedź Dietmar jest czysty i prawdopodobnie tylko naprawdę przenośny odpowiedź, przypadkowo znalazł szybki i brzydka odpowiedź: MSVC udostępnia funkcję _set_output_format których można użyć, aby przejść do „wykładnik druku jako dwie cyfry”.

Następującą klasę RAII można utworzyć w swojej funkcji main(), aby uzyskać takie samo zachowanie GCC, CLANG i 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

Wymaga to co najmniej MSVCR80.dll, co jest trudne do skonfigurowania do pracy z MinGW. – Manuel

+0

Uwaga: jak stwierdzono w dokumentacji, 'Ta funkcja jest przestarzała. Zaczynając od Visual Studio 2015, nie jest dostępny w CRT. " – BenC

2

Problem polega na tym, że Visual C++ nie spełnia standardu C99. W Visual C++ 2015 _set_output_format został usunięty ponieważ kompilator teraz następujące normy:

W %e i %E formatu specyfikatory formatowania liczbę zmiennoprzecinkową jako mantysy i wykładnika dziesiętnego. Specyfikatory formatu %g i %G również formatują liczby w tym formularzu w niektórych przypadkach. W poprzednich wersjach CRT zawsze generował ciągi z trzycyfrowymi wykładnikami.Na przykład printf("%e\n", 1.0) wydrukuje 1.000000e+000. To było niepoprawne: C wymaga, aby wykładnik był reprezentowany za pomocą tylko jednej lub dwóch cyfr, a następnie drukowane były tylko dwie cyfry:.

W Visual Studio 2005 dodano globalną zmianę zgodności: _set_output_format. Program może wywołać tę funkcję z argumentem _TWO_DIGIT_EXPONENT, aby umożliwić dostosowanie wykładniczego wykładnika. Domyślne zachowanie zostało zmienione na zgodny ze standardem tryb wykładnika wykładniczego.

Zobacz Breaking Changes in Visual C++ 2015. W przypadku starszych wersji zobacz odpowiedź @ Manuela.

FYI w C99 standard możemy odczytać:

e, e

podwójnie argumentów reprezentuje liczbę zmiennoprzecinkową przekształca się w stylu [-] d.ddd e (+ -) dd, gdzie występuje jedna cyfra (która jest niezerowa jeśli argument jest niezerowy) przed znakiem dziesiętnym i liczbą cyfr po niej jest równa dokładności; w przypadku braku dokładności przyjmuje się 6; jeśli dokładność wynosi zero, a flaga # nie jest określona, ​​nie pojawia się znak dziesiętny. Wartość jest zaokrąglana do odpowiedniej liczby cyfr. Specyfikator konwersji E generuje liczbę z E zamiast e przedstawiającą wykładnik. Wykładnik zawsze zawiera co najmniej dwie cyfry i tyle samo cyfr, ile potrzeba, aby reprezentować wykładnik. Jeśli wartość wynosi zero, wykładnik wynosi zero. Podwójny argument reprezentujący nieskończoność lub NaN jest konwertowany w stylu specyfikatora konwersji f lub F.

Jest to różnica w porównaniu do C90, która nie dała żadnych wskazówek dotyczących wymaganej długości wykładnika.

Zauważ, że ostatnie zmiany Visual C++ dotyczą również sposobu drukowania nan, inf itp