Pytanie Dlaczego wartość podwójna wydaje się zmieniać po przypisaniu?


Wynik poniższego programu jest dla mnie trochę dziwny.

#include <iostream>

using namespace std;

int main(){
    double a = 20;
    double b = 0.020;
    double c = 1000.0;

    double d = b * c;

    if(a < b * c)
        cout << "a < b * c" << endl;

    if(a < d)
        cout << "a < d" << endl;

    return 0;
}

Wydajność:

$ ./test
a < b * c

Wiem, że podwójne nie jest dokładne z powodu precyzji. Ale nie oczekuję, że ta wartość zmieni się i da niespójny wynik porównania.

Jeśli a < b * c wydrukować, oczekuję tego a < d powinien również zostać wydrukowany. Ale kiedy uruchamiam ten kod na moim serwerze i686, a nawet na moim cygwin. widze a < b * c ale nie widzę a < d.

Ten problem został potwierdzony jako zależny od platformy. Czy jest to spowodowane różnymi instrukcjami i realizacją podwójnego przypisania?

AKTUALIZACJA

Wygenerowany zespół:

main:
.LFB1482:
    pushl   %ebp
.LCFI0:
    movl    %esp, %ebp
.LCFI1:
    subl    $56, %esp
.LCFI2:
    andl    $-16, %esp
    movl    $0, %eax
    subl    %eax, %esp
    movl    $0, -8(%ebp)
    movl    $1077149696, -4(%ebp)
    movl    $1202590843, -16(%ebp)
    movl    $1066695393, -12(%ebp)
    movl    $0, -24(%ebp)
    movl    $1083129856, -20(%ebp)
    fldl    -16(%ebp)
    fmull   -24(%ebp)
    fstpl   -32(%ebp)
    fldl    -16(%ebp)
    fmull   -24(%ebp)
    fldl    -8(%ebp)
    fxch    %st(1)
    fucompp
    fnstsw  %ax
    sahf
    ja  .L3
    jmp .L2

    //.L3 will call stdout

11
2018-02-23 03:24


pochodzenie


Być może ma to coś wspólnego z ciągłą optymalizacją. Czy możesz pokazać zespół lub odtworzyć go, jeśli złapiesz a, b, i c od std::cin? - Nate Kohl
Brak błędów / ostrzeżeń z kompilatora. - StarPinkER
Mogę ją odtworzyć Jeśli otrzymam ją ze std :: cin, opublikuję później zespół. @NateKohl - StarPinkER
Wartość ta jest potwierdzana po wydrukowaniu. @vdbuilder - StarPinkER
Nie otrzymuję danych wyjściowych dla kodu x87 i SSE. Możesz zobaczyć wyniki i edytować kod samodzielnie coliru.stacked-crooked.com/a/3022f77c07303e32  Z jakiego kompilatora korzystasz? Czym dokładnie jest twoja maszyna AS3? - Z boson


Odpowiedzi:


Hipoteza: możesz zobaczyć efekty 80-bitowej intelowskiej jednostki FPU.

Z definicją double d = b * c, ilość b * c jest obliczana z 80-bitową precyzją i zaokrąglana do 64 bitów, gdy jest zapisana d. (a < d) porównywałby 64-bit a do 64-bitów d.

OTOH, z wyrażeniem (a < b * c), Masz 80-bitowy wynik arytmetyczny b * c porównywane bezpośrednio z a przed opuszczeniem FPU. Więc b*c wynik nigdy nie jest precyzyjnie obcięty poprzez zapisanie w 64-bitowej zmiennej.

Będziesz musiał spojrzeć na wygenerowane instrukcje, aby się upewnić, i spodziewam się, że będzie się to różnić w zależności od wersji kompilatora i flag optymalizatora.


5
2018-02-23 03:57



Widzieć stackoverflow.com/q/20869904/420683 - dyp
Próbowałem ustawić x87 tutaj coliru.stacked-crooked.com/a/3022f77c07303e32 ale to nie miało znaczenia. Nie otrzymuję wyjścia z x87 lub SSE. To nie neguje niczego, co powiedziałeś, ale jestem po prostu ciekawy, jakie flagi kompilatora muszę ustawić, aby zobaczyć efekt. - Z boson


Nie jestem pewien, jaki rodzaj sprzętu jest maszyną AS3, ale na przykład można zobaczyć to zachowanie na komputerach, gdzie wewnętrzna jednostka zmiennoprzecinkowa używa większych niż 64-bitowe pływaki do przechowywania wyników pośrednich. Dzieje się tak w łuku x86 z jednostkami zmiennoprzecinkowymi x87 (ale nie z SSE).

Problem polega na tym, że procesor się załaduje b i c do rejestrów zmiennoprzecinkowych, następnie wykonaj mnożenie i zapisz wynik tymczasowy w rejestrze. Jeśli ten rejestr jest większy niż 64-bitowy, wynik będzie inny niż d (lub a), które zostały obliczone i zapisane z powrotem do pamięci, zmuszając je do 64-bitów.

Jest to jeden z wielu scenariuszy, więc musisz przyjrzeć się kodowi montażowemu, aby dokładnie określić, co się dzieje. Musisz także zrozumieć, w jaki sposób Twój sprzęt radzi sobie z obliczeniami zmiennoprzecinkowymi wewnętrznie.


3
2018-02-23 04:00



Myślę że masz rację. Ale czy oznacza to, że porównując wynik a, wynik pośredni, wartość pośrednia nie zostanie wprowadzona do pamięci, ale ładowanie a do fpu i dokonywanie tam porównania? - StarPinkER


Szybki test kodu z MinGW na moim komputerze z systemem Windows daje te same wyniki. Jednak dziwne jest to, że jeśli zmienię double do floats, wszystko przebiega idealnie dokładnie tak, jak powinno (w ogóle nie ma wyjścia). Jeśli jednak zmienię je na długie podwójne, pojawią się "a <b * c" i "a <d".

Domyślam się, że skoro debaty mają pozwolić na większą precyzję, coś dziwnego dzieje się przy pomnożeniu dwóch bezpośrednich wartości i porównaniu, w przeciwieństwie do przechowywania wyników na później? To by wyjaśniało również, dlaczego ostatecznie problem pojawia się również z długimi debelami, ponieważ wymagałoby to jeszcze więcej miejsca w pamięci.


1
2018-02-23 06:37