2015-12-21 13 views
8

W poniższym kodzie:Czy standard C++ dopuszcza takie zachowanie zmiennoprzecinkowe?

#include <cstdint> 
#include <cinttypes> 
#include <cstdio> 

using namespace std; 

int main() { 
    double xd = 1.18; 
    int64_t xi = 1000000000; 

    int64_t res1 = (double)(xi * xd); 

    double d = xi * xd; 
    int64_t res2 = d; 

    printf("%" PRId64"\n", res1); 
    printf("%" PRId64"\n", res2); 
} 

Korzystanie v4.9.3 g++ -std=c++14 kierowania 32-bitowy system Windows dostaję wyjściowa:

1179999999 
1180000000 

Czy te wartości mogą być różne?

Spodziewałem się, że nawet jeśli kompilator użyje wyższej wewnętrznej precyzji niż double do obliczenia xi * xd, powinien to robić konsekwentnie. Utrata precyzji w konwersji zmiennoprzecinkowej to zdefiniowana implementacja, a także precyzja tego obliczenia to zdefiniowana implementacja - [c.limits]/3 mówi, że FLT_EVAL_METHOD powinno zostać zaimportowane z C99. IOW Spodziewałem się, że nie wolno używać innej precyzji dla xi * xd w jednej linii niż w innej linii.

Uwaga: Jest to celowo pytanie C++, a nie pytanie C - uważam, że oba języki mają różne zasady w tym zakresie.

+1

Zauważ, że musisz oboje opracować 'xi * xd' jako' long double' i wykonać konwersję na liczbę całkowitą z 'long double', aby uzyskać to błędnie; 1e9 * 1,18 kończy się o około 1/4 ulp od 1,18e9. – tmyklebu

+0

Powiązane: https://gc.gnu.org/bugzilla/show_bug.cgi?id=323#c127 – Nemo

Odpowiedz

3

nawet jeśli kompilator używa większą precyzję niż wewnętrzne podwójne dla obliczania xi * xd, powinien to robić konsekwentnie

czy wymagana czy nie (omówione poniżej), to z pewnością nie stało się: Stackoverflow jest zaśmiecony pytaniami od ludzi, którzy widzieli podobne pozorne obliczenia, zmieniając się bez żadnego rzekomego powodu w ramach tego samego programu.

C++ standardowy projekt n3690 mówi (Kopalnia nacisk):

Wartości pływających argumentów i wyników pływających wyrażeń może być reprezentowane w większej precyzji i zakresie niż wymagany przez rodzaj; typy nie zostały w ten sposób zmienione.62

62) Operatorzy odlewu i przydziałów muszą nadal wykonywać określone konwersje zgodnie z opisem w punktach 5.4, 5.2.9 i 5.17.

Tak - w porozumieniu z komentarzem i wbrew moim wcześniejszym edycji MM - to wersja z (double) obsadzie że musi zaokrągla się do 64-bitowej double - który ewidentnie dzieje się> = 1180000000 w biegu udokumentowanym w pytaniu - przed obcięciem do liczby całkowitej. Bardziej ogólna liczba sans 62) pozostawia kompilatorowi wolność, aby nie zaokrąglać wcześniej w drugim przypadku.

[c.limits]/3 mówi, że FLT_EVAL_METHOD powinno zostać zaimportowane z C99. IOW Oczekiwałem, że nie powinno być dozwolone używanie innej precyzji dla xi * xd w jednej linii niż w innej linii.

Sprawdź cppreference page:

Niezależnie od wartości FLT_EVAL_METHOD, każde wyrażenie zmiennoprzecinkowe mogą być zlecane, że jest obliczana tak, jakby wszystkie wyniki pośrednie mają nieskończony zasięg i precyzję (chyba #pragma STDC FP_CONTRACT jest wyłączony)

Jak tmyklebu komentarzach, to nadal:

Odlewanie i przydział usuwają wszelki dodatkowy zakres i precyzję: modeluje to działanie polegające na zapisaniu wartości z precyzyjnie rozszerzonego rejestru FPU do pamięci o standardowej wielkości.

Ten ostatni zgadza się z częścią "62)" standardu.

M.M. komentarzy:

STDC FP_CONTRACT nie wydaje się pojawiać w C++ standard, a także to nie jest dla mnie jasne, dokładnie, w jakim stopniu zachowanie C99 jest „importowanej”

Nie pojawia się w projekcie Spojrzałem na. Sugeruje to, że C++ nie gwarantuje jej dostępności, pozostawiając domyślną wzmiankę powyżej o "dowolne wyrażenie zmiennoprzecinkowe może być zakontraktowane", ale wiemy na M.M. komentarze i cytaty Standard i cppreference nad rzutem (double) jest wyjątkiem wymuszającym zaokrąglanie do 64 bitów.

C++ standardowy projekt wspomniano powyżej mówi <cfloat>:

Treści są takie same jak w nagłówku biblioteka standardowa C. Patrz również: ISO C 7.1.5, 5.2.4.2.2, 5.2.4.2.1.

Jeśli jeden z tych C wymaganych standardów STDC FP_CONTRACT istnieje większa szansa, że ​​był przenośny do użytku programów C++, ale nie wcześniej badanych implementacje dla wsparcia.

+0

Która linia normy odpowiada linii cppreferencji? –

+0

Zaraz poniżej cytowanego akapitu strona cppreference mówi: "Obsada i przypisanie usuwają wszelki obcy zakres i precyzję: modeluje to akcję przechowywania wartości z precyzyjnie rozszerzonego rejestru FPU w miejsce pamięci o standardowej wielkości." To leci w obliczu tego, co napisałeś. – tmyklebu

+0

'STDC FP_CONTRACT' nie wydaje się pojawiać w standardzie C++, a także nie jest dla mnie jasne, do jakiego stopnia zachowanie C99 jest" importowane " –

2

W zależności od FLT_EVAL_METHOD, xi * xd może być obliczone z większą dokładnością niż podwójne. Jeśli xi byłyby tak duże, że nie można ich dokładnie odwzorować w dwóch, to nie jestem nawet pewien, czy kompilator mógłby przekonwertować go dokładnie na długi podwójny, czy też nie - prawdopodobnie nie, ponieważ ta konwersja dzieje się przed czymkolwiek objętym FLT_EVAL_METHOD. Nie ma wymogu, aby konsekwentnie stosować większą precyzję.

Istnieją dwa miejsca, w których musi nastąpić konwersja do podwójnej: w punkcie rzutu (podwójne) iw punkcie przypisania do podwójnego. Istnieją wersje gcc, w których rzut na podwójne został "zoptymalizowany", jeśli wartość była już "oficjalnie" podwójna (jak na przykład xi * xd), nawet jeśli w rzeczywistości była to wyższa precyzja; że "optymalizacja" zawsze była błędem, ponieważ konwersja musi zostać przekształcona.

Być może natknąłeś się na ten błąd, w którym nie wykonano rzutu podwójnego (jeśli błąd wciąż tam jest), możesz mieć niespójne użycie wyższej precyzji, co jest legalne, jeśli pozwala na to FLT_EVAL_METHOD, oraz możesz nawet spotkać się z niespójnym użyciem wyższej precyzji, gdy FLT_EVAL_METHOD w ogóle na to nie zezwoliło, co znowu byłoby błędem (a nie niespójnością, ale przede wszystkim wyższą precyzją).

+0

@MM Więc jest to błąd w kompilatorze, ponieważ rzeczywiście istnieje ** [expr.static.cast] 5.2.9 \ 4 **: "Efekt takiej wyraźnej konwersji jest taki sam jak wykonanie deklaracji i inicjalizacja, a następnie użycie zmiennej tymczasowej w wyniku konwersji. " Zmienna "d" jest tymczasowa, ale jej wyrażenia są różne. –

+0

Och, jakie to interesujące (https://gc.gnu.org/bugzilla/show_bug.cgi?id=323#c92)! –

Powiązane problemy