Объясните этот код в 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, и достаточно маленьким, чтобы результат был точным.