Когда происходит переполнение?

Я попадаю в ситуацию, когда вычисление 1.77e-308/10 вызывает исключение переполнения, но вычисление 1.777e-308/10 - нет. Это странно, потому что:

Недополнение происходит, когда истинный результат с плавающей запятой операция меньше по величине (т.е. ближе к нулю), чем наименьшее значение, представляемое как нормальное число с плавающей запятой в целевой тип данных (из Arithmetic Underflow, Wikipedia)

Другими словами, если мы вычисляем x/y, где оба x и y равны double, тогда нижнее течение должно происходить, если 0 < |x/y| < 2.2251e-308 (наименьшая положительная нормализованная double равна 2.2251e-308). Поэтому в теории как 1.77e-308/10, так и 1.777e-308/10 должно возникать исключение underflow. Теория противоречит тому, что я тестировал с помощью программы C. ниже.

#include <stdio.h>
#include <fenv.h>
#include <math.h>


int main(){
  double x,y;

  // x = 1.77e-308 => underflow
  // x = 1.777e-308 gives  ==> no underflow
  x=1.77e-308;

  feclearexcept(FE_ALL_EXCEPT);
  y=x/10.0;
  if (fetestexcept(FE_UNDERFLOW)) {
    puts("Underflow\n");
  }
  else puts("No underflow\n");
}

Чтобы скомпилировать программу, я использовал gcc program.c -lm; Я также попробовал Клэнг, который дал мне тот же результат. Любое объяснение?

[Edits] Я разделил приведенный выше код через эту онлайн-среду IDE.

Ответы

Ответ 1

Underflow - это не только вопрос диапазона, но и точность/округление.

7.12.1 Обработка условий ошибок
Результат заканчивается, если величина математического результата настолько мала, что математический результат не может быть представлен без особой ошибки округления в объекте указанного типа. C11 §7.12.1 6

1.777e-308, преобразованный в ближайший binary64 0x1.98e566222bcfcp-1023, имеет значение (0x198E566222BCFC, 7193376082541820), которое кратна 10. Таким образом, деление на 10 точнее. Ошибка округления.

Мне кажется, что это проще для демонстрации с шестнадцатеричной нотацией. Обратите внимание, что деление на 2 всегда точное, за исключением наименьшего значения.

#include <float.h>
#include <stdio.h>
#include <fenv.h>
#include <math.h>

int uf_test(double x, double denominator){
  printf("%.17e %24a ", x, x);
  feclearexcept(FE_ALL_EXCEPT);
  double y=x/denominator;
  int uf = !!fetestexcept(FE_UNDERFLOW);
  printf("%-24a %s\n", y, uf ? "Underflow" : "");
  return uf;
}

int main(void) {
  uf_test(DBL_MIN, 2.0);
  uf_test(1.777e-308, 2.0);
  uf_test(1.77e-308, 2.0);
  uf_test(DBL_TRUE_MIN, 2.0);

  uf_test(pow(2.0, -1000), 10.0);
  uf_test(DBL_MIN, 10.0);
  uf_test(1.777e-308, 10.0);
  uf_test(1.77e-308, 10.0);
  uf_test(DBL_TRUE_MIN, 10.0);
  return 0;
}

Выход

2.22507385850720138e-308                0x1p-1022 0x1p-1023                
1.77700000000000015e-308  0x1.98e566222bcfcp-1023 0x1.98e566222bcfcp-1024  
1.77000000000000003e-308  0x1.97490d21e478cp-1023 0x1.97490d21e478cp-1024  
4.94065645841246544e-324                0x1p-1074 0x0p+0                   Underflow

// No underflow as inexact result is not too small
9.33263618503218879e-302                0x1p-1000 0x1.999999999999ap-1004  
// Underflow as result is too small and inexact
2.22507385850720138e-308                0x1p-1022 0x1.99999999999ap-1026   Underflow
// No underflow as result is exact
1.77700000000000015e-308  0x1.98e566222bcfcp-1023 0x1.471deb4e8973p-1026   
1.77000000000000003e-308  0x1.97490d21e478cp-1023 0x1.45d40a818394p-1026   Underflow
4.94065645841246544e-324                0x1p-1074 0x0p+0                   Underflow

Ответ 2

Проверка документации для вызываемой функции приводит к определению:

FE_UNDERFLOW результат более ранней операции с плавающей запятой был субнормальным с потерей точности

http://en.cppreference.com/w/c/numeric/fenv/FE_exceptions

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