Wyjaśniłem moim uczniom, że testowanie równe nie jest wiarygodne dla zmiennych zmiennoprzecinkowych, ale jest dobre dla liczb całkowitych. W podręczniku, którego używam, jest napisane, że łatwiej jest czytać> oraz <niż> = i <=. Zgadzam się do pewnego stopnia, ale w pętli For? Czy nie jest bardziej jasne, aby pętla określała wartości początkowe i końcowe?
Czy brakuje mi czegoś, co autor podręcznika ma rację?
Innym przykładem są testy zasięgu, takie jak:
jeśli wynik> 89 ocena = „A”
, jeśli wynik> 79 ocena = „B” ...
Dlaczego po prostu nie powiesz: jeśli wynik> = 90?
Odpowiedzi:
W kręconych językach programowania z tablicami zerowymi zwykle zapisuje się
for
takie pętle:Przechodzi przez wszystkie elementy w tablicy i jest zdecydowanie najczęstszym przypadkiem. Unika używania
<=
lub>=
.Jedyny moment, który musiałby to zmienić, to konieczność pominięcia pierwszego lub ostatniego elementu, przejścia w przeciwnym kierunku lub przejścia z innego punktu początkowego lub innego punktu końcowego.
W przypadku kolekcji w językach, które obsługują iteratory, częściej można to zobaczyć:
Co całkowicie eliminuje porównania.
Jeśli szukasz twardej i szybkiej reguły, kiedy użyć
<=
vs<
, nie ma takiej; użyj tego, co najlepiej wyraża Twoją intencję. Jeśli twój kod musi wyrażać pojęcie „mniej niż lub równe 55 mil na godzinę”, to znaczy<=
, że nie<
.>= 90
Bardziej sensowne jest udzielenie odpowiedzi na pytanie dotyczące zakresów ocen , ponieważ 90 to rzeczywista wartość brzegowa, a nie 89.źródło
for
takich pętli. Podana tutaj formafor
pętli będzie natychmiast rozpoznawalna dla każdego programisty z odrobiną doświadczenia. Jeśli chcesz uzyskać bardziej szczegółową odpowiedź w oparciu o bardziej szczegółowy scenariusz, musisz uwzględnić ją w swoim pytaniu.To nie ma znaczenia
Ale ze względu na argument, przeanalizujmy dwie opcje:
a > b
vsa >= b
.Wytrzymać! To nie jest równoważne!
OK, a następnie
a >= b -1
vsa > b
luba > b
vsa >= b +1
.Hm,
a >b
ia >= b
oba wyglądają lepiej niża >= b - 1
ia >= b +1
. Co to w ogóle1
jest? Argumentowałbym więc, że wszelkie korzyści z posiadania>
zamiast>=
lub odwrotnie są eliminowane przez dodawanie lub odejmowanie losowych liczb1
s.Ale co jeśli to liczba? Czy lepiej powiedzieć
a > 7
czya >= 6
? Poczekaj sekundę. Czy poważnie spieramy się, czy lepiej używać>
vs>=
i ignorować zmienne zakodowane na stałe? Tak naprawdę staje się pytanie, czya > DAYS_OF_WEEK
jest lepsze niża >= DAYS_OF_WEEK_MINUS_ONE
... czy to jesta > NUMBER_OF_LEGS_IN_INSECT_PLUS_ONE
vsa >= NUMBER_OF_LEGS_IN_INSECT
? Wróciliśmy do dodawania / odejmowania1
s, tylko tym razem w nazwach zmiennych. A może debatowanie, czy najlepiej zastosować próg, limit, maksimum.I wygląda na to, że nie ma ogólnej zasady: zależy od tego, co jest porównywane
Ale tak naprawdę są o wiele ważniejsze rzeczy do poprawienia w kodzie i znacznie bardziej obiektywne i rozsądne wytyczne (np. Limit znaków X na linię), które wciąż mają wyjątki.
źródło
>
vs>=
czy dyskusji na temat tego, czy dyskusja na temat>
vs>=
jest znacząca? chociaż prawdopodobnie najlepiej unikać omawiania tego: pObliczeniowo nie ma różnicy w kosztach przy użyciu
<
lub w>
porównaniu do<=
lub>=
. Jest obliczany równie szybko.Jednak większość pętli będzie liczyła od 0 (ponieważ wiele języków używa 0 indeksowania dla swoich tablic). Tak więc kanoniczna pętla for w tych językach jest
zrobienie tego za pomocą
<=
wymagałoby dodania gdzieś -1, aby uniknąć błędu off-by onelub
Oczywiście, jeśli język używa indeksowania 1, wówczas użyłbyś <= jako warunku ograniczającego.
Kluczem jest to, że wartości wyrażone w warunku są tymi z opisu problemu. Czytanie jest łatwiejsze
na półotwartą przerwę niż
i muszę wykonać matematykę, aby wiedzieć, że nie ma żadnej możliwej wartości między 19 a 20
źródło
for(markup = 5; markup <= MAX_MARKUP; ++markup)
. Wszystko inne by to skomplikowało.Powiedziałbym, że nie chodzi o to, czy powinieneś użyć> czy> =. Chodzi o to, aby użyć czegokolwiek, co pozwala pisać ekspresyjny kod.
Jeśli uznasz, że musisz dodać / odjąć jeden, rozważ użycie drugiego operatora. Uważam, że dobre rzeczy dzieją się, gdy zaczynasz od dobrego modelu swojej domeny. Wtedy logika się pisze.
To jest o wiele bardziej wyraziste niż
W innych przypadkach preferowany jest inny sposób:
O wiele lepszy niż
Proszę wybaczyć „prymitywną obsesję”. Oczywiście chciałbyś tutaj użyć odpowiednio typu Velocity i Money, ale pominąłem je dla zwięzłości. Chodzi o to: użyj wersji, która jest bardziej zwięzła i pozwala skupić się na problemie biznesowym, który chcesz rozwiązać.
źródło
Jak wskazałeś w swoim pytaniu, testowanie równości zmiennych zmiennoprzecinkowych nie jest wiarygodne.
To samo dotyczy
<=
i>=
.Jednak nie ma takiego problemu z niezawodnością dla typów całkowitych. Moim zdaniem autorka wyraziła swoją opinię na temat tego, co jest bardziej czytelne.
To, czy się z nim zgadzasz, jest oczywiście Twoją opinią.
źródło
<
lub<=
bazować na tym, co najbardziej naturalne dla konkretnego problemu, który rozwiązuję. Jak zauważyli inni, w pętli FOR<
ma większy sens w językach typu C. Istnieją inne przypadki użycia, które faworyzują<=
. Skorzystaj ze wszystkich dostępnych narzędzi, kiedy i gdzie to stosowne.for (unsigned int i = n; i >= 0; i--)
czyfor (unsigned int i = x; i <= y; i++)
jeśliy
dzieje sięUINT_MAX
. Ups, te pętle na zawsze.Każdy z relacji
<
,<=
,>=
,>
a także==
i!=
mają swoje przypadków użycia do porównywania dwóch wartości zmiennoprzecinkowych. Każdy ma określone znaczenie i należy wybrać odpowiedni.Podam przykłady przypadków, w których chcesz dokładnie tego operatora dla każdego z nich. (Pamiętaj jednak o NaN).
f
która przyjmuje zmiennoprzecinkową wartość jako dane wejściowe. Aby przyspieszyć obliczenia, zdecydujesz się dodać cache ostatnio obliczonych wartości, czyli odwzorowanie tabeli odnośnikówx
dof(x)
. Naprawdę będziesz chciał użyć==
do porównania argumentów.x
? Prawdopodobnie chcesz użyćx != 0.0
.x
mieści się w przedziale jednostkowym?(x >= 0.0) && (x < 1.0)
jest prawidłowy warunek.d
macierzy i chcesz powiedzieć, czy jest ona pozytywnie określona? Nie ma powodu, aby używać niczego innegod > 0.0
.alpha <= 1.0
.Matematyka zmiennoprzecinkowa (ogólnie) nie jest dokładna. Ale to nie znaczy, że powinieneś się go bać, traktować go jak magię, a na pewno nie zawsze traktować dwie zmiennoprzecinkowe wielkości, jeśli są w środku
1.0E-10
. Takie postępowanie naprawdę złamie Twoją matematykę i spowoduje, że wydarzy się wiele dziwnych rzeczy.x != 0.0
iy
jest skończoną wartością zmiennoprzecinkową,y / x
nie musi być skończony. Warto jednak wiedzieć, czyy / x
nie jest ona skończona z powodu przepełnienia, czy dlatego, że operacja nie była matematycznie dobrze zdefiniowana na początek.x
musi znajdować się w przedziale jednostkowym [0, 1), byłbym naprawdę zdenerwowany, gdyby wywołał błąd potwierdzenia podczas wywołania za pomocąx == 0.0
lubx == 1.0 - 1.0E-14
.1.0E-30
, nic nie zyskuje. Wszystko, co zrobiłeś, to zwiększyło prawdopodobieństwo udzielenia złej odpowiedzi.alpha
może być obciążony błędami zaokrąglania i dlategoalpha <= 1.0
może być prawdziwy, nawet jeśli prawdziwa wartość matematyczna wyrażeniaalpha
została obliczona, może być naprawdę większa niż 1. Ale w tym momencie nie możesz nic na to poradzić.Jak zawsze w inżynierii oprogramowania, obsługuj błędy na odpowiednim poziomie i usuwaj je tylko raz. Jeśli dodasz błędy zaokrąglania w kolejności
1.0E-10
(wydaje się, że jest to magiczna wartość, której większość ludzi używa, nie wiem dlaczego) za każdym razem, gdy porównujesz liczby zmiennoprzecinkowe, wkrótce wystąpią błędy w kolejności1.0E+10
…źródło
Rodzaj warunkowy stosowany w pętli może ograniczać rodzaje optymalizacji, które kompilator może wykonać, na lepsze lub na gorsze. Na przykład biorąc pod uwagę:
kompilator mógłby założyć, że powyższy warunek powinien spowodować zakończenie pętli po pętli n -tego przejścia, chyba że n może 65535, a pętla może wyjść w inny sposób niż przez przekroczenie n. Jeśli te warunki mają zastosowanie, kompilator musi wygenerować kod, który spowodowałby uruchomienie pętli, dopóki coś innego niż powyższy warunek nie spowoduje jej wyjścia.
Jeśli zamiast tego pętla została zapisana jako:
wtedy kompilator mógłby bezpiecznie założyć, że pętla nigdy nie będzie musiała wykonywać więcej niż n razy, a zatem może być w stanie wygenerować bardziej wydajny kod.
Pamiętaj, że każde przepełnienie podpisanymi typami może mieć paskudne konsekwencje. Dany:
Kompilator może przepisać to jako:
Taka pętla zachowywałaby się identycznie jak oryginał, gdyby w obliczeniach nie wystąpiło przepełnienie, ale mogłaby działać wiecznie, nawet na platformach sprzętowych, gdzie przepełnienie liczb całkowitych normalnie miałoby spójną semantykę zawijania.
źródło