Информация об изменениях

Сообщение Re: Точность double от 25.05.2021 20:05

Изменено 25.05.2021 20:06 watchmaker

Re: Точность double
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Можно ли без округления вычислить c, чтобы гарантировать (b+c) == a?

Разница двух double чисел не всегда представима в double без потери точности.
То есть для double ответ — нельзя.

Если необходимо считать точно подобные суммы, то тут поможет только длинная арифметика (с хранением всех значащих бит). Но понятно, что не всегда подходит и всё это довольно далеко от стандартного типа double.


А так, операции в IEEE754 c floating-point не ассоциативные. То есть в них принципиально не выполняется равенство (a + b) + c == a + (b + c).
Нужно с этим уметь жить и писать алгоритмы соответствующим образом.

Например, этот эффект можно учитывать и минимизировать как в алгоритмы суммирования Кэхэна. Заметь, что там тоже повторяется похожий паттерн из действий c = a — b; t = b + c; почти как у тебя в примере.

Либо этот эффект можно даже эксплуатировать во благо, например, для быстрого округления чисел без преобразования типов:
double round_pos(double x) {
    const double magic = 0x1p+52;
    return x + magic - magic;
}

Демо: https://godbolt.org/z/5eGjTTjWY



Ну и ещё можно упомянуть, что компилятору обычно можно сказать, что он должен считать, что floating-point ассоциативный (например, через ключ -ffast-math в gcc/clang). Тогда он будет генерировать машинный код исходя из предположений, что (a + b) — b) можно заменить сначала на (a + (b — b), и потом на просто (a) (да, случай с b==NaN тоже считается несуществующим). Но не советую идти этой дорогой: тут наоборот сломаются хорошие алгоритмы, которые написаны в рассчёте на гарантии и поведение ieee754. То есть они будут выдавать иногда чушь, но зато работать будет быстро
Re: Точность double
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Можно ли без округления вычислить c, чтобы гарантировать (b+c) == a?

Разница двух double чисел не всегда представима в double без потери точности.
То есть для double ответ — нельзя.

Если необходимо считать точно подобные суммы, то тут поможет только длинная арифметика (с хранением всех значащих бит). Но понятно, что не всегда подходит и всё это довольно далеко от стандартного типа double.


А так, операции в IEEE754 c floating-point не ассоциативные. То есть в них принципиально не выполняется равенство (a + b) + c == a + (b + c).
Нужно с этим уметь жить и писать алгоритмы соответствующим образом.

Например, этот эффект можно учитывать и минимизировать как в алгоритме суммирования Кэхэна. Заметь, что там тоже повторяется похожий паттерн из действий c = a — b; t = b + c; почти как у тебя в примере.

Либо этот эффект можно даже эксплуатировать во благо, например, для быстрого округления чисел без преобразования типов:
double round_pos(double x) {
    const double magic = 0x1p+52;
    return x + magic - magic;
}

Демо: https://godbolt.org/z/4M5h66Yo3



Ну и ещё можно упомянуть, что компилятору обычно можно сказать, что он должен считать, что floating-point ассоциативный (например, через ключ -ffast-math в gcc/clang). Тогда он будет генерировать машинный код исходя из предположений, что (a + b) — b) можно заменить сначала на (a + (b — b), и потом на просто (a) (да, случай с b==NaN тоже считается несуществующим). Но не советую идти этой дорогой: тут наоборот сломаются хорошие алгоритмы, которые написаны в рассчёте на гарантии и поведение ieee754. То есть они будут выдавать иногда чушь, но зато работать будет быстро