Подтвердить что ты не робот

Объясните этот код в 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?

4b9b3361

Ответ 1

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

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

Ответ 2

OP:... почему автор добавил 1111e28 в переменную fltest?
A: [Изменить] Для работы кода с использованием 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 является вторым по величине числом, добавив delta, не будет суммироваться вплоть до float.infinity и пропустить FLT_MAX. Это 3x min_delta​​p >

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.

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

Если DBL_MAX было меньше 1111e297, метод также терпит неудачу. Примечание. На простых платформах в 2014 году нет ничего удивительного в том, чтобы найти double и float как один 4-байтовый IEEE binary32) В первый раз, хотя цикл, 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. Достаточно просто, чтобы свернуть свой собственный, если нужно.

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

[Изменить]
Точность a 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);

Ответ 3

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