5

Можно ли использовать поплавки в качестве счетчиков циклов и увеличивать/уменьшать их на дробные суммы на каждой итерации, как, например, в безрисковой программе ниже? Конечно, я знаю, что использование float в качестве операндов для == оператор - это тупое дело. Но что не так с использованием поплавков в качестве операндов для других операций сравнения для «нормальных» целей? Под «нормальным» я подразумеваю, что, ну, хотя плавающие значения могут быть неточным точным численным представлением числа, но не являются ли такие изменения, как 0.000000001, неуместными и в большинстве случаев могут быть проигнорированы? (Например, в следующей программе, которая даже не очевидно)Любой риск использования переменных float как счетчиков циклов и их дробное приращение/декремент для условий без «==»?

Но что сказал, вот мой apprehension.Suppose представление не является точным и 5,0 фактически 4,999999 .so, как мы идем на уменьшающего по 0,5 на каждой итерации, последнее сравнение с может получиться ложным, и петля может выйти из-за разницы 0,000001, и последняя строка токового выхода не будет отображаться. Надеюсь, ты получаешь мой дрифт. Как я ошибаюсь?

#include<stdio.h> 

int main(void) 
{ 
float f; 

for(f=5.0;f>=0;f-=0.5) 
printf("%f\n",f); 
} 

Выход:

5.000000 
4.500000 
4.000000 
3.500000 
3.000000 
2.500000 
2.000000 
1.500000 
1.000000 
0.500000 
0.000000 

ответ

11

Нет, это не безопасно, по причинам, указанным в самом вашем вопросе. Рассмотрим это:

#include<stdio.h> 

int main(void) { 
    float f = 1.0; 

    for(;f>0;f-=0.1) 
    printf("%f\n",f); 
    return 0; 
} 

Этот пример, кажется, работает quite ok когда f инициализируется 1.0. Но изменить это до 3,0 - и вещи начинают получать путь more interesting довольно скоро:

2.600000 
2.500000 
2.400001 
... 
0.000001 

... что привело к печально известной «офф-на-один» провал.


Вы думаете, что вы могли бы быть безопасными с >= вместо >? Подумайте again:

float f = 5.0; 
for(;f>=1;f-=0.4) 
    printf("%f\n",f); 

... 
3.400000 
3.000000 
2.599999 
2.199999 
1.799999 
1.399999 

... и вне по одному мы снова (как 0.99999 меньше 1).

8

До тех пор, как начальное значение, величина декремента и результат все декременты могут быть представлены с никакой ошибки в пределах точности предоставленной типа с плавающей точкой, то она безопасна в использовании. Обратите внимание, что здесь «no error» означает абсолютную ошибку 0, очень маленькая ошибка по-прежнему считается ошибкой.

В вашем случае, начальное значение 5.0 и количество декремента 0.5 могут быть представлены без ошибок, и 4.5, 4.0, 3.5 ..., 0.0 также может быть представлен без ошибок в 23-битной точности float , Это безопасно в вашем случае.

Если скажем, начальное значение равно 4000000.0 и количество декремента 0.00390625 (2 -8), то вы в беде, потому что результат декремента не может быть представлен без ошибок в 23-битной точности float типа, хотя исходное значение и величина декремента могут быть правильно представлены.

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

+0

+1 Хороший ответ. –

+0

Должно быть ясно, что эти критерии применимы к циклам, заканчивающимся на нуле. Если у вас есть цикл, который работает от + X до -Y, например, тогда могут возникнуть ошибки, если Y больше X. –

+0

@EricPostpischil: Я думаю, что условие, что результирующее значение между ними должно быть представимым, охватывает этот случай? – nhahtdh

6

Предпочитайте целочисленные значения по плавающей запятой, когда это возможно, просто из-за проблем с представлением с плавающей запятой.

Вместо того, чтобы использовать число с плавающей запятой в качестве управления с обратной связью, переделки логики, чтобы использовать целые числа:

нужно декрементировать свой счетчик на .5? Удвойте начальное значение и декремент на 1:

float f = 5.0; 
int i = f * 2; 

for(; i >= 0; i--) 
    printf("%f\n", i/2.0); 

нужно уменьшать на .1?

float f = 5.0; 
int i = f * 10; 

for(; i >= 0; i--) 
    printf("%f\n", i/10.0); 

Это простой подход для примера в вопросе. Конечно, не единственный подход или самый правильный. Более сложный пример может потребовать переделать логику немного иначе. Независимо от ситуации.

Моя точка зрения Предполагаю, что мы должны сдерживать работу с фактическим значением с плавающей запятой до последнего возможного момента, чтобы уменьшить ввод ошибок из-за представления.

+0

+1 для примеров, показывающих лучший способ петли над поплавками. –