Почему значение double кажется измененным после назначения?

Результат следующей программы немного странен для меня на моей машине.

#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;
}

Вывод:

$ ./test
a < b * c

Я знаю, что двойной не точный из-за точности. Но я не ожидаю, что значение изменится и даст несогласованный результат сравнения.

Если распечатать a < b * c, я ожидаю, что a < d также должен быть напечатан. Но когда я запускаю этот код на моем сервере i686 и даже на моем cygwin. Я вижу a < b * c, но не вижу a < d.

Эта проблема была подтверждена как зависящая от платформы. Это вызвано различной инструкцией и реализацией двойного назначения?

UPDATE

Сгенерированная сборка:

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

Ответ 1

Гипотеза: вы можете видеть эффекты 80-битного Intel FPU.

При определении double d = b * c величина b * c вычисляется с 80-битной точностью и округляется до 64 бит, когда она сохраняется в d. (a < d) будет сравнивать 64-разрядный a с 64-бит d.

OTOH, с выражением (a < b * c). У вас есть 80-разрядный арифметический результат b * c, который сравнивается непосредственно с a перед выходом из FPU. Таким образом, результат b*c никогда не имеет своей точности, отсеченной путем сохранения в 64-битной переменной.

Вам нужно будет посмотреть на сгенерированные инструкции, и я ожидаю, что это будет отличаться от версий компилятора и флагов оптимизатора.

Ответ 2

Я не уверен, какой тип оборудования является AS3-машиной, но, например, вы можете увидеть это поведение на машинах, где внутренний блок с плавающей запятой использует более 64-битные поплавки для хранения промежуточных результатов. Это имеет место в архитектуре x86 с блоками с плавающей запятой x87 (но не с SSE).

Проблема заключается в том, что процессор будет загружать в b и c в регистры с плавающей запятой, затем выполнять умножение и хранить временный результат в регистре. Если этот регистр больше 64 бит, результат будет отличаться от d (или a), которые были вычислены и сохранены в памяти, заставляя их быть 64-битными.

Это один из сценариев для многих, вам нужно будет посмотреть на свой код сборки, чтобы точно определить, что происходит. Вы также должны понимать, как ваше оборудование имеет дело с вычислениями с плавающей запятой.

Ответ 3

Быстрая проверка кода с помощью MinGW на моей машине Windows дает такие же результаты. Что действительно странно, так это то, что если я смену дублеры на поплавки, все будет отлично, как и должно быть (без вывода вообще). Однако, если я изменю их на длинные удвоения, появятся "a < b * c" и "a < d".

Мое предположение, возможно, потому, что предполагается, что удваивается более высокая точность, что-то странное происходит при умножении двух непосредственных значений и выполнении сравнения, а также на сохранении результата позже? Это также объясняет, почему в конечном итоге проблема возникает с длинными удвоениями, так как они потребуют еще большего объема памяти.