Объясните этот код в K&R 2-1

Я пытаюсь определить диапазон различных типов с плавающей точкой. Когда я читаю этот код:

#include <stdio.h>

main()
{
    float fl, fltest, last;
    double dbl, dbltest, dblast;

    fl = 0.0;
    fltest = 0.0;
    while (fl == 0.0) {
        last = fltest;
        fltest = fltest + 1111e28;
        fl = (fl + fltest) - fltest;
    }
    printf("Maximum range of float variable: %e\n", last);

    dbl = 0.0;
    dbltest = 0.0;
    while (dbl == 0.0) {
        dblast = dbltest;
        dbltest = dbltest + 1111e297;
        dbl = (dbl + dbltest) - dbltest;
    }
    printf("Maximum range of double variable: %e\n", dblast);
    return 0;
}

Я не понимаю, почему автор добавил 1111e28 в fltest переменная?

3 ответа

Цикл завершается, когда fltest достигает +Infкак на тот момент fl = (fl + fltest) - fltest становится NaNчто неравно 0.0, last содержит значение, которое при добавлении в 1111e28 производит +Inf и так близко к верхней границе float,

1111e28 выбран для достижения +Inf достаточно быстро; он также должен быть достаточно большим, чтобы при добавлении к большим значениям цикл продолжал развиваться, т. е. он был бы по меньшей мере таким же, как разрыв между наибольшим и вторым по величине бесконечным float ценности.

ОП: ... почему автор добавил 1111e28 в fltest переменная?
A: [Edit] Чтобы код работал, используя float, 1111e28, или же 1.111e31 это значение дельты нуждается в тщательном отборе. Он должен быть достаточно большим, чтобы, если fltest было FLT_MAX, сумма fltest + delta переполнится и станет float.infinity, В режиме округления до ближайшего FLT_MAX*FLT_EPSILON/4, На моей машине:

min_delta           1.014120601e+31 1/2 step between 2nd largest and FLT_MAX
FLT_MAX             3.402823466e+38
        FLT_EPSILON 8.388608000e+06
FLT_MAX*FLT_EPSILON 4.056481679e+31

delta должен быть достаточно маленьким, так что если f1test 2-е по величине число, с добавлением дельты, не суммируется вплоть до float.infinity и пропустить FLT_MAX, Это 3x min_delta

max_delta           3.042361441e+31

Так 1.014120601e+31 <= 1111e28 < 3.042361441e+31,

@david.pfx Да. 1111e28 - симпатичное число, и оно в пределах досягаемости.

Примечание: осложнения возникают, когда математические и промежуточные значения, хотя переменные float может рассчитывать на более высокую точность, как double, Это разрешено в C и контролируется FLT_EVAL_METHOD или очень осторожное кодирование.


1111e28 это любопытная ценность, которая имеет смысл, если автор все готово знал общий диапазонFLT_MAX,

Ожидается, что приведенный ниже код будет повторяться много раз (24946069 на одной тестовой платформе). Надеюсь, ценность fltest в конце концов становится "бесконечным". затем f1 воля становится NaN как разница Бесконечности - Бесконечности. Цикл while заканчивается как Nan!= 0.0. @ecatmur

while (fl == 0.0) {
    last = fltest;
    fltest = fltest + 1111e28;
    fl = (fl + fltest) - fltest;
}

Цикл, если он выполняется с достаточно малыми приращениями, даст точный ответ. Предварительное знание FLT_MAX а также FLT_EPSILON необходимы, чтобы застраховать это.

Проблема в том, что С не определяет диапазон FLT_MAX а также DBL_MAX кроме того, что они должны быть по крайней мере 1E+37, Таким образом, если максимальное значение было довольно большим, значение приращения 1111e28 или 1111e297 не будет иметь никакого эффекта. Пример: dbltest = dbltest + 1111e297;, за dbltest = 1e400 конечно, не увеличит 1e400, если только dbltest сто десятичных цифр точности.

Если DBL_MAX был меньше, чем 1111e297, метод тоже не работает. Примечание: на простых платформах в 2014 году неудивительно найти double а также float быть тем же 4-байтовым двоичным IEEE32) Первый раз, хотя цикл, dbltest становится бесконечным, и цикл останавливается, сообщая "Максимальный диапазон двойной переменной: 0,000000e+00".

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

float float_max(void) {
  float nextx = 1.0 + rand()/RAND_MAX;
  float x;
  do {
    x = nextx;
    nextx *= 2;
  } while (!isinf(nextx));
  float delta = x;
  do {
    nextx = x + delta/2;
    if (!isinf(nextx)) {
      x = nextx;
    }
    delta /= 2;
  } while (delta >= 1.0);
  return x;
}

isinf() это новая функция C. Достаточно просто, чтобы свернуть свой собственный, если это необходимо.

В ре: @didierc комментарий

[Редактировать]
Точность float а также double подразумевается с помощью "epsilon": "разница между 1 и наименьшим значением больше 1, представляемая в данном типе с плавающей запятой...". Максимальные значения следуют

FLT_EPSILON 1E-5
DBL_EPSILON 1E-9

За комментарий @Pascal Cuoq. "... 1111e28 выбирается больше, чем FLT_MAX*FLT_EPSILON.", 1111e28 должно быть как минимум FLT_MAX*FLT_EPSILON чтобы повлиять на сложение цикла, но достаточно маленький, чтобы точно достичь числа до бесконечности. Опять же, предварительное знание FLT_MAX а также FLT_EPSILON необходимы, чтобы сделать это определение. Если эти значения известны заранее, то простой код мог бы быть:

printf("Maximum range of float variable: %e\n", FLT_MAX);

Наибольшее значение, представимое в float составляет 3.40282e+38. Константа 1111e28 выбрана так, что добавление этой константы к числу в диапазоне 10^38 все еще приводит к другому значению с плавающей запятой, так что значение fltest будет продолжать увеличиваться по мере выполнения функции. Оно должно быть достаточно большим, чтобы оно все равно было значительным в диапазоне 10^38, и достаточно маленьким, чтобы результат был точным.

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