Python-Runde auf die nächsthöhere Potenz von 10


44

Wie würde ich es schaffen, math.ceilso zu arbeiten, dass eine Zahl der nächsthöheren Potenz von 10 zugewiesen wird?

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

Meine aktuelle Lösung ist ein Wörterbuch, das den Bereich der Eingabenummer überprüft, aber fest codiert ist, und ich würde eine einzeilige Lösung bevorzugen. Vielleicht fehlt mir hier ein einfacher mathematischer Trick oder eine entsprechende Numpy-Funktion?


3
@bold sieht so aus, als ob diese Lösungen von 10oben funktionieren . Dies erfordert etwas mit z log10.
Jonrsharpe

3
Das Wort, das Sie wollen, ist "Macht". Vielleicht haben Sie die falsche Übersetzung für das Wort Ihrer Muttersprache dafür.
user2357112 unterstützt Monica

Danke, Monica! @bold: Ich habe diese Frage gefunden, aber es ist ein anderes Problem. Jonrsharpe hat eine perfekte Antwort
geliefert

2
Dies hängt auch mit der Größenordnung zusammen . 1 ist 0. Ordnung, 10 ist 1. Ordnung, 100 ist 2. Ordnung usw.
wjandrea

Antworten:


60

Sie können math.ceilmit verwenden math.log10, um dies zu tun:

>>> 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)gibt Ihnen die Lösung x, die zufriedenstellend 10 ** x == nist. Wenn Sie also aufrunden , erhalten xSie den Exponenten für die nächsthöhere Potenz von 10.

Beachten Sie, dass für einen Wert , nwo xbereits eine ganze Zahl, die „nächsthöhere Leistung von 10“ wird n:

>>> 10 ** math.ceil(math.log10(0.1))
0.1
>>> 10 ** math.ceil(math.log10(1))
1
>>> 10 ** math.ceil(math.log10(10))
10

1
Die Arbeit mit der Protokollfunktion scheint nur der Trick zu sein, den ich mir nicht ausdenken konnte. Ich glaube, genau das habe ich mir erhofft! Vielen Dank
offeltoffel

2
NB: Abhängig von Ihrem gewünschten Verhalten funktioniert dies nicht für Potenzen von 10, z. B. 10 ** math.ceil(math.log10(1)) == 1was nicht "die nächsthöhere Potenz" ist
Cireo

5
Hinweis: Diese Antwort basiert auf Gleitkomma-Arithmetik und kann daher aufgrund von Rundungsfehlern fehlschlagen. Versuchen Sie beispielsweise, 1000000000000001 einzugeben.
Plugwash

2
@plugwash nicht unbedingt, die mathematischen Funktionen akzeptieren auch zB decimal.Decimals.
Jonrsharpe

5
Ja, Sie können andere Typen übergeben, diese werden jedoch in eine Gleitkommazahl mit doppelter Genauigkeit konvertiert und an die Funktion C "log10" übergeben. Es gibt einen Sonderfall, um das Überlaufen von Protokollen mit großen Zahlen zu verhindern, aber nichts, um Rundungsfehler zu verhindern.
Plugwash

21

Ihr Problem ist unterbestimmt. Sie müssen einen Schritt zurücktreten und einige Fragen stellen.

  • Welche Art (en) sind Ihre Eingaben?
  • Welche Art (en) möchten Sie für Ihre Ausgaben?
  • Auf was genau möchten Sie bei Ergebnissen unter 1 runden? Möchten Sie tatsächliche Potenzen von 10 oder Gleitkomma-Annäherungen von Potenzen von 10? Sie wissen, dass negative Potenzen von 10 nicht genau in Gleitkomma ausgedrückt werden können, oder? Nehmen wir zunächst an, dass Sie Gleitkomma-Approximationen von Potenzen von 10 wünschen.
  • Wenn der Eingang genau eine Potenz von 10 ist (oder die nächste Gleitkomma-Annäherung an eine Potenz von 10), sollte der Ausgang mit dem Eingang identisch sein? Oder sollte es die nächste Potenz von 10 sein? "10 -> 10" oder "10 -> 100"? Nehmen wir vorerst das erstere an.
  • Können Ihre Eingabewerte ein möglicher Wert der betreffenden Typen sein? oder sind sie eingeschränkter.

In einer anderen Antwort wurde vorgeschlagen, den Logarithmus zu nehmen, dann aufzurunden (Deckenfunktion) und dann zu potenzieren.

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

Leider weist dies Rundungsfehler auf. Zunächst wird n von einem beliebigen Datentyp in eine Gleitkommazahl mit doppelter Genauigkeit konvertiert, wodurch möglicherweise Rundungsfehler auftreten. Anschließend wird der Logarithmus berechnet, wodurch möglicherweise mehr Rundungsfehler sowohl in den internen Berechnungen als auch im Ergebnis auftreten.

Daher habe ich nicht lange gebraucht, um ein Beispiel zu finden, bei dem ein falsches Ergebnis erzielt wurde.

>>> 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

Es ist auch theoretisch möglich, dass es in die andere Richtung versagt, obwohl dies viel schwieriger zu provozieren scheint.

Für eine robuste Lösung für Floats und Ints müssen wir also davon ausgehen, dass der Wert unseres Logarithmus nur ungefähr ist, und wir müssen daher einige Möglichkeiten testen. Etwas in der Art von

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

Ich glaube, dieser Code sollte korrekte Ergebnisse für alle Argumente in einem vernünftigen realen Größenbereich liefern. Es wird für sehr kleine oder sehr große Anzahlen von nicht ganzzahligen und nicht Gleitkommatypen unterbrochen, da Probleme beim Konvertieren in Gleitkommatypen auftreten. Python-Sonderfälle Ganzzahlargumente für die Funktion log10, um einen Überlauf zu verhindern. Bei einer ausreichend massiven Ganzzahl können jedoch möglicherweise falsche Ergebnisse aufgrund von Rundungsfehlern erzwungen werden.

Um die beiden Implementierungen zu testen, habe ich das folgende Testprogramm verwendet.

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

Dies findet viele Fehler in der naiven Implementierung, aber keine in der verbesserten Implementierung.


Vielen Dank für Ihre Bemühungen, hier näher darauf einzugehen. Während Jonrsharpes Antwort mein Problem bereits gelöst hat, kann diese Antwort für andere mit ähnlichen, aber spezifischeren Fragen nützlich sein.
offeltoffel

1
Warum benutzt du roundstatt math.ceil? Dies führt zu vielen unnötigen Fällen, in denen dies r < nzutrifft, und erfordert daher zusätzliche Arbeiten.
a_guest

1
Weil das Protokoll in beide Richtungen ausgeschaltet sein könnte.
Plugwash

1
unter Verwendung des „verbesserten“ Code aber mit runder durch Math.ceil resultiert in Fehlern für 1e-317 am unteren Ende und 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 am oberen Ende ersetzt.
Plugwash

1
(In der Praxis ist es wahrscheinlich in Ordnung)
Plugwash

3

Es scheint, dass Sie eher die niedrigste nächste Potenz von 10 wollen ... Hier ist eine Möglichkeit, reine Mathematik und kein Protokoll, sondern Rekursion zu verwenden.

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))

Ich habe gerade dieses getestet und gebe ihm eine positive Bewertung, da es in den meisten praktischen Fällen gut zu funktionieren scheint, aber es scheint unter Rundungsfehlern mit ausreichend kleinen Eingaben zu leiden. Decke 10 (1e-6) gibt 1.0000000000000002e-06
Plugwash

0
y = math.ceil(x)
z = y + (10 - (y % 10))

So etwas vielleicht? Es ist nur aus meinem Kopf, aber es hat funktioniert, als ich ein paar Nummern im Terminal ausprobiert habe.


0

Schau dir das an!

>>> 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

Dieser Code basiert auf dem Prinzip der Zehnermacht in len( str( int( float_number ) ) ).

Es gibt 4 Fälle:

    1. int( i ) > 1.

    FloatZahl - konvertiert in int, danach Zeichenfolge str()daraus, gibt uns eine, stringmit lengthder wir genau suchen. Also, erster Teil, für die Eingabe i > 1.0- es ist zehn 10Potenzen dieser Länge.

    1. & 3. Kleine Verzweigung: i > 1.0und i > 0.1<=> ist 10und 1jeweils.
    1. Und letzter Fall, wenn i < 0.1: Hier sollen zehn in negativer Macht sein. Um das erste Nicht-Null-Element nach dem Komma zu erhalten, habe ich eine solche Konstruktion verwendet ("%.100f" % i ).replace('.','').index( k ), bei der k über das [1:10]Intervall läuft . Nehmen Sie danach das Minimum der Ergebnisliste. Und um eins verringern, ist es die erste Null, die gezählt werden soll. Auch hier können Standard-Pythons index()abstürzen, wenn sie nicht mindestens ein Nicht-Null-Element aus dem [1:10]Intervall finden. Deshalb muss ich am Ende die Auflistung nach Vorkommen "filtern" : if str( j ) in "%.100f" % i. Zusätzlich, um genauer zu werden - %.100fkann anders genommen werden.

Fügen Sie bitte eine Erklärung hinzu.
Mobin Ranjbar
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.