IEEE округление с плавающей запятой в C

У меня возникли проблемы с пониманием конкретного двойного вычисления 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

Нет?

Актуально math.stackexchange.com/questions/3448/…

Simon Goater 10.06.2024 17:59

см. также stackoverflow.com/questions/69365468/…: «... если два ближайших представимых значения одинаково близки, должно быть доставлено то, у которого наименее значащий бит равен нулю»

jhole 10.06.2024 18:12

Я нашел это, так что, возможно, это дубликат stackoverflow.com/questions/34386360/…

emacs drives me nuts 10.06.2024 19:03

Результат установки a в положительное число double a = 0x1.638e38e38e38ep5; и печати его с printf ("a = %a\n", a); не показывает отрицательное число a = -0x1.638e38e38e38ep+5. Пожалуйста, отредактируйте вопрос так, чтобы код и вывод точно совпадали.

Eric Postpischil 10.06.2024 19:24
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
102
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как показывают ваши данные расширенной точности, точный результат сложения равен 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. Хотя для умножения и сложения это всегда возможно.

emacs drives me nuts 10.06.2024 20:08

@emacsdrivesmenuts: «Бесконечно точный» означает точный математический результат. Это достижимо математически. Например, π — это бесконечно точное значение отношения длины окружности к ее диаметру. Его нельзя представить конечным числом десятичных цифр, но это не имеет значения. Мы можем манипулировать им математически и реализовать алгоритмы, которые определяют, ближе ли оно к одному десятичному числу, чем к другому, и так далее, и этого достаточно для реализации арифметики с плавающей запятой.

Eric Postpischil 10.06.2024 20:42

Другие вопросы по теме