Aby bezpośrednio odpowiedzieć na pytanie, oto moja wersja używająca nazewnictwa z funkcji R :
import math
def signif(x, digits=6):
if x == 0 or not math.isfinite(x):
return x
digits -= math.ceil(math.log10(abs(x)))
return round(x, digits)
Moim głównym powodem opublikowania tej odpowiedzi są komentarze narzekające, że „0,075” zaokrągla do 0,07 zamiast 0,08. Wynika to, jak wskazał „Nowicjusz C”, z połączenia arytmetyki zmiennoprzecinkowej mającej zarówno skończoną precyzję, jak i reprezentację o podstawie 2 . Liczba najbliższa 0,075, którą można w rzeczywistości przedstawić, jest nieco mniejsza, dlatego zaokrąglanie wygląda inaczej, niż można by się naiwnie spodziewać.
Należy również zauważyć, że dotyczy to wszelkich zastosowań arytmetyki zmiennoprzecinkowej innej niż dziesiętna, np. C i Java mają ten sam problem.
Aby pokazać bardziej szczegółowo, prosimy Pythona o sformatowanie liczby w formacie „szesnastkowym”:
0.075.hex()
co daje nam: 0x1.3333333333333p-4
. Powodem takiego postępowania jest to, że normalna reprezentacja dziesiętna często obejmuje zaokrąglanie, a zatem nie jest to sposób, w jaki komputer faktycznie „widzi” liczbę. Jeśli nie jesteś przyzwyczajony do tego formatu, kilka przydatnych odnośników są docs Python i standard C .
Aby pokazać, jak działają te liczby, możemy wrócić do punktu wyjścia, wykonując:
0x13333333333333 / 16**13 * 2**-4
który powinien zostać wydrukowany 0.075
. 16**13
Dzieje się tak, ponieważ po przecinku znajduje się 13 cyfr szesnastkowych, a 2**-4
wykładniki szesnastkowe mają podstawę 2.
Teraz mamy już pewne wyobrażenie o tym, jak reprezentowane są zmiennoprzecinkowe, możemy użyć decimal
modułu, aby zwiększyć precyzję, pokazując nam, co się dzieje:
from decimal import Decimal
Decimal(0x13333333333333) / 16**13 / 2**4
dawanie: 0.07499999999999999722444243844
i miejmy nadzieję, że wyjaśnia, dlaczego round(0.075, 2)
ocenia0.07