Python przechodzi do następnej najwyższej potęgi 10

44

Jak miałbym wykonać math.ceiltak, aby numer został przypisany do następnej najwyższej potęgi 10?

# 0.04  ->  0.1
# 0.7   ->  1
# 1.1   ->  10  
# 90    ->  100  
# ...

Moje obecne rozwiązanie to słownik, który sprawdza zakres liczby wejściowej, ale jest on zakodowany na stałe i wolałbym rozwiązanie jednoliniowe. Może brakuje mi tutaj prostej sztuczki matematycznej lub odpowiedniej funkcji numpy?

offeltoffel
źródło
3
@bold wygląda na to, że te rozwiązania działają od zera 10, będzie to wymagać czegoś np log10.
jonrsharpe
3
Słowo, którego chcesz, to „moc”. Być może masz niewłaściwe tłumaczenie dla swojego języka ojczystego.
user2357112 obsługuje Monikę
Dzięki, Monica! @bold: Znalazłem to pytanie, ale to inny problem. Jonrsharpe udzielił idealnej odpowiedzi
offeltoffel
2
Jest to również związane z rzędem wielkości . 1 to 0 rzędu, 10 to 1 rzędu, 100 to 2 rzędu itd.
wjandrea

Odpowiedzi:

60

Możesz używać math.ceilzmath.log10 tego robić:

>>> 10 ** math.ceil(math.log10(0.04))
0.1
>>> 10 ** math.ceil(math.log10(0.7))
1
>>> 10 ** math.ceil(math.log10(1.1))
10
>>> 10 ** math.ceil(math.log10(90))
100

log10(n) daje rozwiązanie x które spełnia 10 ** x == n, więc jeśli zaokrąglisz w górę x, daje wykładnik następnej najwyższej potęgi 10.

Zauważ, że dla wartości, nktóra xjest już liczbą całkowitą, „następną najwyższą potęgą 10” będzie n:

>>> 10 ** math.ceil(math.log10(0.1))
0.1
>>> 10 ** math.ceil(math.log10(1))
1
>>> 10 ** math.ceil(math.log10(10))
10
jonrsharpe
źródło
1
Praca z funkcją dziennika wydaje się być sztuczką, której nie mogłem wymyślić. Wierzę, że właśnie na to liczyłem!
Wielkie
2
Uwaga: w zależności od pożądanego zachowania nie działa to dla mocy 10, np. 10 ** math.ceil(math.log10(1)) == 1Która nie jest „następną najwyższą mocą”
Cireo,
5
Uwaga: odpowiedź zależy od arytmetyki zmiennoprzecinkowej i jako taka może się nie powieść z powodu błędów zaokrąglania. Spróbuj na przykład podać 1000000000000001.
płukanie
2
@plwwash niekoniecznie, funkcje matematyczne przyjmą również np. dziesiętny.
jonrsharpe
5
Tak, możesz przekazać inne typy, ale zostaną one przekonwertowane na liczbę zmiennoprzecinkową podwójnej precyzji i przekazane do funkcji C „log10”. Istnieje specjalny przypadek, aby zapobiec przepełnieniu dzienników o dużych liczbach, ale nic nie zapobiega błędom zaokrąglania.
płukanie
21

Twój problem jest nieokreślony, musisz cofnąć się i zadać kilka pytań.

  • Jakie typy są twoje dane wejściowe?
  • Jakiego rodzaju chcesz dla swoich wyników?
  • W przypadku wyników mniejszych niż 1, co dokładnie chcesz zaokrąglić? Czy chcesz rzeczywistych mocy 10 lub przybliżonych liczb zmiennoprzecinkowych mocy 10? Czy zdajesz sobie sprawę, że mocy ujemnych 10 nie można dokładnie wyrazić w liczbach zmiennoprzecinkowych, prawda? Załóżmy na razie, że chcesz przybliżenia zmiennoprzecinkowe potęg 10.
  • Jeśli wejście ma dokładnie moc 10 (lub najbliższe przybliżenie zmiennoprzecinkowe mocy 10), czy wynik powinien być taki sam jak na wejściu? A może powinna to być następna moc 10 w górę? „10 -> 10” czy „10 -> 100”? Załóżmy na razie to pierwsze.
  • Czy twoje wartości wejściowe mogą być dowolną możliwą wartością danych typów? czy są bardziej ograniczeni.

W innej odpowiedzi zaproponowano, aby wziąć logarytm, a następnie zaokrąglić w górę (funkcja pułapu), a następnie potęgować.

def nextpow10(n):
    return 10 ** math.ceil(math.log10(n))

Niestety cierpi to na błędy zaokrąglania. Przede wszystkim n jest konwertowane z dowolnego typu danych, który ma, na liczbę zmiennoprzecinkową o podwójnej precyzji, potencjalnie wprowadzając błędy zaokrąglania, następnie logarytm jest obliczany, potencjalnie wprowadzając więcej błędów zaokrąglania zarówno w wewnętrznych obliczeniach, jak i w wyniku.

Jako taki nie zajęło mi długo znalezienie przykładu, w którym dał niepoprawny wynik.

>>> import math
>>> from numpy import nextafter
>>> n = 1
>>> while (10 ** math.ceil(math.log10(nextafter(n,math.inf)))) > n:
...     n *= 10
... 
>>> n
10
>>> nextafter(n,math.inf)
10.000000000000002
>>> 10 ** math.ceil(math.log10(10.000000000000002))
10

Teoretycznie możliwe jest, że zawiodą w innym kierunku, choć wydaje się to znacznie trudniejsze do sprowokowania.

Dlatego dla solidnego rozwiązania dla liczb zmiennoprzecinkowych i liczb wewnętrznych musimy założyć, że wartość naszego logarytmu jest jedynie przybliżona i dlatego musimy przetestować kilka możliwości. Coś w stylu

def nextpow10(n):
    p = round(math.log10(n))
    r = 10 ** p
    if r < n:
        r = 10 ** (p+1) 
    return r;

Uważam, że ten kod powinien dawać poprawne wyniki dla wszystkich argumentów w rozsądnym zakresie wielkości rzeczywistych. Zepsuje się dla bardzo małej lub bardzo dużej liczby typów niecałkowitych i nie zmiennoprzecinkowych z powodu problemów z konwersją ich na zmiennoprzecinkowe. Python w szczególnych przypadkach argumentuje liczby całkowite dla funkcji log10, próbując zapobiec przepełnieniu, ale wciąż przy wystarczająco dużej liczbie całkowitej może być możliwe wymuszenie niepoprawnych wyników z powodu błędów zaokrąglania.

Aby przetestować dwie implementacje, użyłem następującego programu testowego.

n = -323 # 10**-324 == 0
while n < 1000:
    v = 10 ** n
    if v != nextpow10(v): print(str(v)+" bad")
    try:
        v = min(nextafter(v,math.inf),v+1)
    except:
        v += 1
    if v > nextpow10(v): print(str(v)+" bad")
    n += 1

Stwierdzono wiele błędów we wdrożeniu naiwnym, ale nie we ulepszonym.

płyn do płukania
źródło
Dziękujemy za wysiłek włożony w szczegółowe zapoznanie się tutaj. Chociaż odpowiedź jonrsharpe już rozwiązała mój problem, odpowiedź ta może być przydatna dla innych z podobnymi, ale bardziej szczegółowymi pytaniami.
offeltoffel
1
Dlaczego używasz roundzamiast math.ceil? Spowoduje to wprowadzenie wielu niepotrzebnych przypadków, w których r < njest to prawdą, dlatego musi wykonać dodatkową pracę.
a_guest
1
Ponieważ dziennik może być wyłączony w dowolnym kierunku.
płukanie
1
za pomocą „ulepszone” kod, ale z okrągłym zastąpione wynikami Math.ceil w niepowodzeń na 1e-317 na niskim końcu i 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 na wysokiej klasy.
płukanie
1
(w praktyce prawdopodobnie jest w porządku)
płukanie
3

Wygląda na to, że chcesz raczej najniższej następnej potęgi 10 ... Oto sposób korzystania z czystej matematyki i bez dziennika, ale rekurencja.

def ceiling10(x):
    if (x > 10):
        return ceiling10(x / 10) * 10
    else:
        if (x <= 1):
            return ceiling10(10 * x) / 10
        else:
            return 10
for x in [1 / 1235, 0.5, 1, 3, 10, 125, 12345]:
    print(x, ceiling10(x))
Silvain Dupertuis
źródło
Właśnie przetestowałem ten, oceniam go, ponieważ wydaje się, że działa dobrze w większości praktycznych przypadków, ale wydaje się, że cierpi na błędy zaokrąglania przy wystarczająco małych danych wejściowych. pułap 10 (1e-6) daje 1.0000000000000002e-06
płukanie
0
y = math.ceil(x)
z = y + (10 - (y % 10))

Może coś takiego? Jest tuż przy mojej głowie, ale zadziałało, gdy wypróbowałem kilka numerów w terminalu.

Lugen
źródło
0

Spójrz na to!

>>> i = 0.04123; print i, 10 ** len( str( int( i ) ) ) if int( i ) > 1  else 10 if i > 1.0 else 1 if i > 0.1 else  10 ** ( 1 - min( [ ("%.100f" % i ).replace('.','').index( k ) for k in [ str( j ) for j in xrange( 1, 10 ) if str( j ) in "%.100f" % i  ] ]  ) )               
0.04123 0.1
>>> i = 0.712; print i, 10 ** len( str( int( i ) ) ) if int( i ) > 1  else 10 if i > 1.0 else 1 if i > 0.1 else  10 ** ( 1 - min( [ ("%.100f" % i ).replace('.','').index( k ) for k in [ str( j ) for j in xrange( 1, 10 ) if str( j ) in "%.100f" % i  ] ]  ) )                 
0.712 1
>>> i = 1.1; print i, 10 ** len( str( int( i ) ) ) if int( i ) > 1  else 10 if i > 1.0 else 1 if i > 0.1 else  10 ** ( 1 - min( [ ("%.100f" % i ).replace('.','').index( k ) for k in [ str( j ) for j in xrange( 1, 10 ) if str( j ) in "%.100f" % i  ] ]  ) )                   
1.1 10
>>> i = 90; print i, 10 ** len( str( int( i ) ) ) if int( i ) > 1  else 10 if i > 1.0 else 1 if i > 0.1 else  10 ** ( 1 - min( [ ("%.100f" % i ).replace('.','').index( k ) for k in [ str( j ) for j in xrange( 1, 10 ) if str( j ) in "%.100f" % i  ] ]  ) )                    
90 100

Ten kod oparty na zasadzie mocy dziesięciu w len( str( int( float_number ) ) ).

Istnieją 4 przypadki:

    1. int( i ) > 1.

    FloatNumer - przekształca się intnastępnie będzie ciągiem str()z nią, da nam stringz lengthco jesteśmy dokładnie szukać. Tak więc pierwsza część na wejściu i > 1.0- ma dziesięć 10mocy tej długości.

    1. & 3. Małe rozgałęzienia: i > 1.0i i > 0.1<=> odpowiednio 10i 1.
    1. I ostatni przypadek, kiedy i < 0.1: tutaj dziesięć będzie w mocy ujemnej. Aby uzyskać pierwszy niezerowy element po przecinku, użyłem takiej konstrukcji ("%.100f" % i ).replace('.','').index( k ), w której k przebiega przez [1:10]interwał. Następnie weź minimum listy wyników. I pomniejsz o jeden, to pierwsze zero, które należy policzyć. Również tutaj norma Pythona index()może upaść, jeśli nie znajdzie się co najmniej jeden z niezerową elementu z [1:10]przedziału, dlatego w końcu muszę „filtr” wystawianie przez występowania: if str( j ) in "%.100f" % i. Ponadto, aby uzyskać głębszą precyzję - %.100fmogą się różnić.
Marshmello123123123
źródło
Dodaj wyjaśnienie, proszę.
Mobin Ranjbar