У меня возникли проблемы с пониманием конкретного двойного вычисления IEEE. Возьмем следующую программу C99, которая работает на хосте с двойным IEEE (8 байт, 11-битная смещенная экспонента, 52-битная закодированная мантисса):
#include <stdio.h>
#include <fenv.h>
// Expect IEEE double
int _a[sizeof (double) == 8 ? 1 : -1];
#define PP(X) printf (#X " = %d\n", X)
int main (void)
{
// Print rounding modes.
PP (FE_TONEAREST);
PP (FE_UPWARD);
PP (FE_DOWNWARD);
PP (FE_TOWARDZERO);
// What mode do we have?
printf ("rounding mode = %d\n", fegetround());
// Add a and b.
double a = -0x1.638e38e38e38ep5;
double b = -0x1.638e38e38e38ep6;
__asm ("" : "+r" (a));
__asm ("" : "+r" (b));
printf ("a = %a\n", a);
printf ("b = %a\n", b);
printf ("a+b = %a\n", a + b);
return 0;
}
Скомпилируйте и запустите:
$ gcc rounding.c -lm && ./a.out
FE_TONEAREST = 0
FE_UPWARD = 2048
FE_DOWNWARD = 1024
FE_TOWARDZERO = 3072
rounding mode = 0
a = -0x1.638e38e38e38ep+5
b = -0x1.638e38e38e38ep+6
a+b = -0x1.0aaaaaaaaaaaap+7
Мой вопрос: почему наименее значимая часть суммы равна a
и не округляется до b
, поскольку включено округление до ближайшего?
В двойной эмуляции IEEE с 56 битами для декодированной мантиссы вычисление выглядит следующим образом:
# Printed the double values, same like on the host:
A = -0x1.638e38e38e38ep5
B = -0x1.638e38e38e38ep6
# Internal format with 56-bit mantissa. The digit after | indicates
# three extra bits compared to IEEE.
#
A = -0x1.638e38e38e38e|0, expo = 5
B = -0x1.638e38e38e38e|0, expo = 6
A + B = -0x1.0aaaaaaaaaaaa|8, expo = 7
Поэтому, когда A + B
упаковывается как двойной, это округляется до
(double) (A + B) = -0x1.0aaaaaaaaaaabp+7
Нет?
см. также stackoverflow.com/questions/69365468/…: «... если два ближайших представимых значения одинаково близки, должно быть доставлено то, у которого наименее значащий бит равен нулю»
Я нашел это, так что, возможно, это дубликат stackoverflow.com/questions/34386360/…
Результат установки a
в положительное число double a = 0x1.638e38e38e38ep5;
и печати его с printf ("a = %a\n", a);
не показывает отрицательное число a = -0x1.638e38e38e38ep+5
. Пожалуйста, отредактируйте вопрос так, чтобы код и вывод точно совпадали.
Как показывают ваши данные расширенной точности, точный результат сложения равен 1,0AAAAAAAAAAAA816•27, где жирным шрифтом показана часть, которая будет соответствовать числу двойной точности IEEE-754 (binary64); цифра 8 выходит за рамки того, что можно представить.
Два числа, представленные в двоичном формате64, которые непосредственно окружают это число, — это 1.0AAAAAAAAAAAA16•27 и 1.0AAAAAAAAAAAB16•27. Они одинаково далеки от 1.0AAAAAAAAAAAA816•27. Для метода округления до ближайшего и привязки к четному стандарт IEEE 754 4.3.1 гласит: «… если два ближайших числа с плавающей запятой, заключающие в скобки непредставимый бесконечно точный результат, одинаково близки, то число с четной младшей цифрой должно быть доставленным…"
Наименее значащие двоичные цифры кандидатов — 0 и 1 (поскольку наименее значащие шестнадцатеричные цифры — A, 1010 и B, 1011). 0 — четное, а 1 — нечетное, поэтому выбрано 1.0AAAAAAAAAAAA16•27.
Что значит «бесконечно точный»? Этого невозможно достичь, скажем, с помощью трансцендентных функций, за исключением очень небольшого числа случаев, таких как cos(0)=1
. Хотя для умножения и сложения это всегда возможно.
@emacsdrivesmenuts: «Бесконечно точный» означает точный математический результат. Это достижимо математически. Например, π — это бесконечно точное значение отношения длины окружности к ее диаметру. Его нельзя представить конечным числом десятичных цифр, но это не имеет значения. Мы можем манипулировать им математически и реализовать алгоритмы, которые определяют, ближе ли оно к одному десятичному числу, чем к другому, и так далее, и этого достаточно для реализации арифметики с плавающей запятой.
Актуально math.stackexchange.com/questions/3448/…