5

Czy możemy bezpiecznie używać pływaków jako liczników pętli i zwiększać/zmniejszać je o ułamkowe wartości w każdej iteracji, tak jak w poniższym programie bez ryzyka? Oczywiście wiem, że używanie zmiennoprzecinkowych jako argumentów dla == operator jest głupim posunięciem. Ale co jest złego w używaniu zmiennoprzecinkowych jako operandów dla innych operacji porównania dla "normalnych" celów? Przez "normalny" mam na myśli to, że nawet jeśli liczba znaków nie może być liczbową reprezentacją liczby, ale nie jest odmianą taką jak ta, która jest nieistotna i może być zignorowana w większości przypadków? (Na przykład w poniższym programie, który nie jest jeszcze widoczny)Czy istnieje ryzyko wykorzystania zmiennych zmiennoprzecinkowych jako liczników pętli i ich ułamkowego przyrostu/dekrecji dla warunków innych niż "= "?

Ale powiedział, że tu jest mój apprehension.Suppose reprezentacja nie jest dokładna i 5,0 jest rzeczywiście 4,999999 .Tak jak idziemy na zmniejszanie przez 0.5 w każdej iteracji, ostatnie porównanie zmoże okazać się nieprawdą, a pętla może zostać zakończona z powodu różnicy w stosunku do 0.000001, a ostatnia linia bieżącego wyjścia nie będzie wyświetlana. Mam nadzieję, że dostaję mój dryf. Jak się mylę?

#include<stdio.h> 

int main(void) 
{ 
float f; 

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

wyjściowa:

5.000000 
4.500000 
4.000000 
3.500000 
3.000000 
2.500000 
2.000000 
1.500000 
1.000000 
0.500000 
0.000000 

Odpowiedz

11

Nie, to nie jest bezpieczne, z powodów podanych w samym pytaniu. Rozważ to:

#include<stdio.h> 

int main(void) { 
    float f = 1.0; 

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

Przykład ten wydaje się działać quite ok gdy f jest inicjowany przez 1.0. Ale to zmienić na 3,0 - i wszystko zaczyna się droga more interesting wkrótce:

2.600000 
2.500000 
2.400001 
... 
0.000001 

... prowadząc do niesławnej awarii 'off-by-one'.


Myślisz, że może być bezpiecznie z >= zamiast >? Pomyśl 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 

... i off-by-one znowu idziemy (jak 0.99999 jest mniejsza niż 1).

8

Dopóki wartość początkową, kwotę obniżanie i wyniku wszystkie te ubytki mogą być reprezentowane z bez błędów w precyzji dostarczonych przez pływającego typu punkt, to jest bezpieczny w użyciu. Zauważ, że "brak błędu" oznacza tutaj 0 absolutny błąd, bardzo mały błąd jest nadal uważany za błąd.

W twoim przypadku, wartość początkowa 5.0 a kwota ubytek 0.5 może być reprezentowana bez błędu i 4.5, 4.0, 3.5, ..., 0.0 może również być reprezentowana bez błędu w 23-bitowej precyzji float . Jest bezpieczny w twoim przypadku.

Jeśli powiedzmy wartość początkowa wynosi 4000000.0 a kwota ubytek jest 0.00390625 (2 -8), to jesteś w tarapatach, ponieważ wynikiem ubytku nie może być reprezentowana bez błędu w 23-bitowej precyzji float typ, chociaż wartość początkowa i wielkość dekrementacji mogą być poprawnie reprezentowane.

Jednak nie widzę sensu w używaniu zmiennoprzecinkowej, gdy integralny typ jest bardziej wiarygodny w takim przypadku. Nie musisz marnować komórki mózgowej na sprawdzanie, czy powyższy warunek ma zastosowanie, czy nie.

+0

+1 Dobra odpowiedź. –

+0

Powinno być jasne, że te kryteria mają zastosowanie do pętli kończących się na zero. Jeśli masz pętlę, która działa na przykład od + X do -Y, wtedy mogą wystąpić błędy, jeśli Y jest większe niż X. –

+0

@EricPostpischil: Myślę, że warunek, że wynikowa wartość pomiędzy musi być reprezentowalna pokrywa ten przypadek? – nhahtdh

6

Preferuj wartości całkowite nad zmiennoprzecinkowym, gdy tylko jest to możliwe, ze względu na problemy z reprezentacją zmiennoprzecinkową.

Zamiast liczbę zmiennoprzecinkową jako kontroli pętli, przerobienie logiki do korzystania liczby całkowite:

Need aby zmniejszyć swój licznik przez .5? Podwoi swoją wartość początkową i ubytek w stosunku 1:

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

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

Trzeba zmniejszyć przez .1?

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

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

Jest to proste podejście do przykładu w pytaniu. Z pewnością nie jest to jedyne podejście lub najbardziej poprawne. Bardziej złożony przykład może wymagać nieco innej przeróbki logiki. Cokolwiek pasuje do sytuacji.

Moim celem jest wstrzymanie pracy z rzeczywistą wartością zmiennoprzecinkową do ostatniego możliwego momentu, aby ograniczyć wprowadzanie błędów z powodu reprezentacji.

+0

+1 dla przykładów pokazujących lepszy sposób na zapętlenie przeplotów. –