Dlaczego wiele (starych) programów używa floor (0.5 + input) zamiast round (input)?

80

Różnice tkwią w zwracanej wartości, podając dane wejściowe dotyczące rozstrzygania remisów, jak sądzę, na przykład ten kod :

int main()
{
    std::cout.precision(100);

    double input = std::nextafter(0.05, 0.0) / 0.1;
    double x1 = floor(0.5 + input);
    double x2 = round(input);

    std::cout << x1 << std::endl;
    std::cout << x2 << std::endl;
}

które wyjścia:

1
0

Ale w końcu to tylko różne wyniki, wybiera się ten preferowany. Widzę wiele "starych" programów C / C ++ używających floor(0.5 + input)zamiast round(input).

Czy jest jakiś historyczny powód? Najtańszy na CPU?

markzzz
źródło
20
std :: round przesuwa obserwacje w połowie drogi od zera. To nie jest matematycznie jednolite, jak podłoga (f + .5), gdzie skrzynki w połowie długości zawsze idą w górę. Powodem stosowania metody podłogowej jest to, że jest ona wymagana do prawidłowego zaokrąglania w świecie inżynierii.
Michaël Roy
7
Jak zauważono w round () dla float w C ++ przed C ++ 11, nie mieliśmy round. Jak zauważyłem w mojej odpowiedzi, prawidłowe napisanie własnej rundy jest trudnym problemem.
Shafik Yaghmour
16
@Arne Używając std :: round () odległość między zaokrąglonymi wartościami -0,5 i +0,5 wynosi 2. używając floor, wynosi 1. Dzieje się tak tylko wtedy, gdy dwie wartości mają przeciwne znaki. Bardzo irytujące, gdy próbujesz rysować proste linie lub gdy wybierasz niewłaściwy piksel tekstury.
Michaël Roy
20
Niektóre języki programowania i środowiska (w tym .NET) używają zwodniczej rzeczy zwanej zaokrąglaniem bankiera, w której x.5 zaokrągla do najbliższej liczby PARZYSTEJ. Czyli 0,5 zaokrągla do 0, a 1,5 zaokrągla do 2. Możesz sobie wyobrazić zamieszanie, jakie może to spowodować podczas debugowania. Myślę, że rozwiązaniem tej złej `` funkcji '' jest w ogóle nie mieć funkcji .Round (), a zamiast tego mieć .RoundBankers (), .RoundHalfUp (), .RoundHalfDown (), etc (lub .BankersRound () itd. ale inteligencja działałaby lepiej z .RoundBankers ()). Przynajmniej w ten sposób byłbyś zmuszony wiedzieć, czego się spodziewać.
user3685427
8
@ user3685427: Zaokrąglanie bankierów jest konieczne w zastosowaniach finansowych i statystycznych, które wymagają wyeliminowania subtelnego i systemowego odchylenia w górę wprowadzonego przez odchodzenie od zaokrąglania zera . Jest prawie niemożliwe do zaimplementowania bez faktycznej znajomości sprzętowej implementacji zmiennoprzecinkowej, stąd jest to wybór domyślny w C #.
Pieter Geerkens

Odpowiedzi:

115

std::roundzostała wprowadzona w C ++ 11. Wcześniej std::floorbył dostępny tylko, więc używali go programiści.

haccks
źródło
Nic. Ale jest starszy niż C ++ 11.
Wydaje
12
@markzzz - Biblioteka standardowa C ++ nie dziedziczy automatycznie biblioteki standardowej C. Trwa ostrożne zbieranie i wybieranie. Synchronizacja z C99 zajęła im 12 lat.
StoryTeller - Unslander Monica
2
@haccks: Rzeczywiście. IMHO C wyprzedziło C ++ pod względem funkcji matematycznych aż do C ++ 11.
Batszeba
3
Należy zauważyć, że używana metoda nie działa w przypadku niektórych danych wejściowych, a także, że C ++ 11 opiera się na C99, podczas gdy C ++ 03 opiera się na C90, co w pewnym sensie przechodzi do punktu @markzzz.
Shafik Yaghmour
1
@markzzz: Twoja uwaga jest trafna (pomimo krytyków). Rzecz w tym, że bez standardów C ++ była długa przerwa, pierwszym standardem C ++ był C ++ 98, a pierwszą dużą poprawką był C ++ 11. Pojawiła się niewielka aktualizacja, C ++ 03, ale jak zauważa strona Wikipedii , była to głównie wersja „poprawiająca błędy”. Dlatego C ++ 11 był pierwszą okazją do dogonienia standardowej biblioteki C po 13 latach iatus.
Matthieu M.
21

Nie ma żadnego historycznego powodu. Ten rodzaj dewiacji istnieje od roku Dot. Ludzie robią to, gdy czują się bardzo, bardzo niegrzeczni. Jest to nadużycie arytmetyki zmiennoprzecinkowej i wielu doświadczonych zawodowych programistów się na to daje. Nawet elementy Java działały do ​​wersji 1.7. Śmieszni goście.

Moje przypuszczenie jest takie, że przyzwoita, gotowa do użycia, niemiecka funkcja zaokrąglania nie była formalnie dostępna aż do C ++ 11 (pomimo, że C dostał swoje w C99), ale to naprawdę nie jest wymówką dla przyjęcia tak zwanej alternatywy.

Oto rzecz: floor(0.5 + input) nie zawsze odzyskuje ten sam wynik, co odpowiednie std::roundpołączenie!

Powód jest dość subtelny: punkt odcięcia dla zaokrąglenia niemieckiego a.5dla liczby całkowitej ajest, przez przypadkową właściwość Wszechświata, diadą racjonalną . Ponieważ można to dokładnie przedstawić w zmiennoprzecinkowym IEEE754 do 52 potęgi 2, a następnie zaokrąglanie jest i tak nie std::rounddziała , zawsze działa poprawnie. W przypadku innych schematów zmiennoprzecinkowych zapoznaj się z dokumentacją.

Ale dodanie 0.5do a doublemoże wprowadzić niedokładność, powodując niewielkie niedokonanie lub przeregulowanie dla niektórych wartości. Jeśli się nad tym zastanowić, dodanie do siebie dwóch doublewartości - które są początkiem nieświadomych konwersji denarów - i zastosowanie funkcji, która jest bardzo silną funkcją danych wejściowych (na przykład funkcja zaokrąglania), z pewnością zakończy się łzami.

Nie rób tego .

Odniesienie: Dlaczego Math.round (0.49999999999999994) zwraca 1?

Batszeba
źródło
2
Komentarze nie służą do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Andy
2
nearbyint()jest zwykle lepszym wyborem niż round(), ponieważ nearbyintużywa obecnego trybu zaokrąglania zamiast funky tiebreak od zera round()(który x86 nie ma nawet wsparcia sprzętowego, chociaż ARM ma). Oba zostały dodane do C ++ w C ++ 11.
Peter Cordes
9

Myślę, że tutaj się mylisz:

Ale w końcu to tylko różne wyniki, wybiera się ten preferowany. Widzę wiele "starych" programów C / C ++ używających floor (0.5 + input) zamiast round (input).

Tak nie jest. Musisz wybrać odpowiedni schemat zaokrąglania domeny . W aplikacji finansowej będziesz obchodzić się zgodnie z regułami bankiera (przy okazji nie używając float). Jednak podczas próbkowania zaokrąglanie w górę przy użyciu static_cast<int>(floor(f + .5))daje mniejszy szum próbkowania, co zwiększa zakres dynamiczny. Podczas wyrównywania pikseli, tj. Konwertowania pozycji na współrzędne ekranu, użycie dowolnej innej metody zaokrąglania spowoduje powstanie dziur, luk i innych artefaktów.

Michaël Roy
źródło
„to zwiększa zakres dynamiczny” - wygląda jak bezsensowny dodatkowy tekst; skopiowane gdzieś przez pomyłkę? Może chcesz go usunąć.
anatolyg
Nie. Zmniejszenie szumu próbkowania zmniejsza poziom szumów i rzeczywiście zwiększa zakres dynamiczny.
Michaël Roy
Czy mógłbyś podać prosty przykład lub odniesienie, aby zilustrować zwiększenie zakresu dynamiki / zmniejszenie szumów?
Ruslan
1
Przy standardowym zaokrąglaniu liczb całkowitych (braku), wszystkie wartości od zera do minus jeden „znikają”, a raczej zmieniają znak (ta sama wartość i dla wejść od 0 do + 1), wszystkie wartości ujemne są przesunięte o co najmniej jeden bit. Tutaj jest trochę szumu z dodanymi zniekształceniami.
Michaël Roy
4

Prosty powód może być taki, że istnieją różne metody zaokrąglania liczb, więc jeśli nie znasz stosowanej metody, możesz uzyskać inne wyniki.

Dzięki floor () możesz być spójny z wynikami. Jeśli liczba zmiennoprzecinkowa wynosi .5 lub więcej, dodanie jej spowoduje skok do następnego int. Ale 0,49999 po prostu pominie przecinek.

MivaScott
źródło
+1 Ta odpowiedź wynika z samych komentarzy do pytania, w którym nie ma zgody co do tego, co round()powoduje.
Loduwijk
@Aaron Jednak odpowiedź na temat tego, co floor(x + 0.5)robi, jest błędna .
EOF
Och, hah! Dobry chwyt. To ironiczne. „Użyj lepiej znanego X, ponieważ nie jesteśmy zgodni lub nie posiadamy pełnej wiedzy na temat Y.” Więc co robisz, gdy to samo dotyczy X?
Loduwijk
1
@Aaron Easy. Robisz jedyną rozsądną rzecz i używasz nearbyint(x), która używa rozsądnego (do najbliższego parzystego) zaokrąglenia, pod warunkiem, że nie zepsułeś środowiska zmiennoprzecinkowego.
EOF
@EOF: Twój wybór zaokrąglania nie jest rozsądny. Funkcja zaokrąglania, która nie ma okresu 1 w składniku nieliniowym, jest szalona.
Eric Towers
2

Wielu programistów dostosowuje idiomy, których nauczyli się podczas programowania w innych językach. Nie wszystkie języki mają round()funkcję iw tych językach normalne jest użycie jej floor(x + 0.5)jako substytutu. Kiedy ci programiści zaczynają używać C ++, nie zawsze zdają sobie sprawę, że jest coś wbudowanego round(), nadal używają stylu, do którego są przyzwyczajeni.

Innymi słowy, tylko dlatego, że widzisz dużo kodu, który coś robi, nie oznacza to, że istnieje dobry powód, aby to zrobić. Przykłady tego można znaleźć w każdym języku programowania. Pamiętaj o prawie Jesiotra :

dziewięćdziesiąt procent wszystkiego to bzdury

Barmar
źródło