Jak udałoby mi się wykonać math.ceil tak, aby liczba była przypisana 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 zakodowany na stałe i wolałbym rozwiązanie jednokreskowe. Może brakuje mi tutaj prostej matematycznej sztuczki lub odpowiedniej funkcji numpy?

50
offeltoffel 3 kwiecień 2020, 12:35

6 odpowiedzi

Najlepsza odpowiedź

Możesz użyć math.ceil z math.log10, aby to zrobić:

>>> 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, otrzymasz wykładnik dla następnej największej potęgi 10.

Uwaga , że dla wartości n, gdzie x jest 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
66
jonrsharpe 3 kwiecień 2020, 18:27

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

  • Jakiego typu są twoje dane wejściowe?
  • Jaki typ (y) chcesz dla swoich wyników?
  • W przypadku wyników mniejszych niż 1, do czego dokładnie chcesz zaokrąglić? Czy chcesz rzeczywiste potęgi 10 lub zmiennoprzecinkowe przybliżenia potęg 10? Wiesz, że ujemne potęgi 10 nie mogą być wyrażone dokładnie w postaci zmiennoprzecinkowej, prawda? Załóżmy na razie, że potrzebujesz przybliżeń zmiennoprzecinkowych potęg 10.
  • Jeśli wejście ma moc równą 10 (lub najbliższe przybliżenie zmiennoprzecinkowe o mocy 10), to czy wyjście powinno być takie samo jak wejście? A może powinna to być następna potęga 10 w górę? „10 -> 10” czy „10 -> 100”? Załóżmy na razie to pierwsze.
  • Czy wartości wejściowe mogą być jakąkolwiek możliwą wartością danego typu? czy też są bardziej ograniczeni.

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

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

Niestety obarczone są błędami zaokrągleń. Przede wszystkim n jest konwertowane z dowolnego typu danych, jakie ma, na liczbę zmiennoprzecinkową o podwójnej precyzji, potencjalnie wprowadzając błędy zaokrągleń, a następnie obliczany jest logarytm, potencjalnie wprowadzający więcej błędów zaokrągleń zarówno w wewnętrznych obliczeniach, jak i w wyniku.

W związku z tym nie zajęło mi dużo czasu znalezienie przykładu, w którym dał on 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 również, że zawiedzie w innym kierunku, choć wydaje się, że jest to znacznie trudniejsze do sprowokowania.

Tak więc, aby uzyskać solidne rozwiązanie dla liczb zmiennoprzecinkowych i liczb całkowitych, musimy założyć, że wartość naszego logarytmu jest tylko 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 w świecie rzeczywistym. Zepsuje się dla bardzo małych lub bardzo dużych liczb typów niecałkowitych i nie zmiennoprzecinkowych z powodu problemów z konwersją ich na zmiennoprzecinkowe. Argumenty całkowite w przypadkach specjalnych w Pythonie do funkcji log10 w celu uniknięcia przepełnienia, ale nadal przy wystarczająco dużej liczbie całkowitej może być możliwe wymuszenie niepoprawnych wyników z powodu błędów zaokrągleń.

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

Wykrywa to wiele błędów w naiwnej implementacji, ale żadnej w ulepszonej implementacji.

23
plugwash 8 kwiecień 2020, 12:02

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 potęgi dziesiątki w len(str(int(float_number))).

Istnieją 4 przypadki:

    1. int(i) > 1.

      Float liczba - przekonwertowana na int, a następnie napis str() z niej, da nam string z length, którego dokładnie szukamy. Tak więc, pierwsza część, dla danych wejściowych i > 1.0 - jest to dziesięć 10 potęgi tej długości.

    1. & amp; 3. Małe rozgałęzienia: i > 1.0 i i > 0.1 & lt; = & gt; to jest odpowiednio 10 i 1.
    1. I ostatni przypadek, kiedy i < 0.1: Tutaj dziesięć będzie miało moc ujemną. Aby otrzymać pierwszy niezerowy element po przecinku, użyłem takiej konstrukcji ("%.100f" % i).replace('.', '').index(k), gdzie k biegnie po interwale [1:10]. Następnie weź minimum listy wyników. I zmniejszając o jeden, otrzymujemy pierwsze zero, które należy policzyć. Również tutaj standardowe index() Pythona może się zawiesić, jeśli nie znajdzie co najmniej jednego niezerowego elementu z interwału [1:10], dlatego na koniec muszę "filtrować" listę według wystąpienia: { {X5}}. Dodatkowo, aby uzyskać większą dokładność - %.100f może być traktowane inaczej.
0
jonrsharpe 1 czerwiec 2020, 14:31
y = math.ceil(x)
z = y + (10 - (y % 10))

Może coś takiego? To tylko z głowy, ale zadziałało, gdy wypróbowałem kilka liczb w terminalu.

-1
Lugene 13 kwiecień 2020, 15:37

Myślę, że najprostszy sposób to:

import math


number = int(input('Enter a number: '))
next_pow_ten = round(10 ** math.ceil(math.log10(number)))
print(str(10) + ' power ' + str(round(math.log10(number))) + ' = '\
      + str(next_pow_ten))

Mam nadzieję, że to ci pomoże.

0
Vacendak 1 czerwiec 2020, 17:44

Wygląda na to, że wolisz raczej najniższą następną potęgę 10 ... Oto sposób na użycie czystej matematyki i bez dziennika, ale rekurencję.

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))
4
plugwash 8 kwiecień 2020, 00:18