Python round alla successiva massima potenza di 10


44

Come potrei riuscire ad eseguire in modo math.ceiltale che un numero sia assegnato alla successiva potenza massima di 10?

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

La mia attuale soluzione è un dizionario che controlla l'intervallo del numero di input, ma è hardcoded e preferirei una soluzione a riga singola. Forse mi sto perdendo un semplice trucco matematico o una corrispondente funzione numpy qui?


3
@bold sembra che quelle soluzioni funzionino dall'alto 10, questo avrà bisogno di qualcosa con es log10.
jonrsharpe,

3
La parola che vuoi è "potere". Forse hai trovato la traduzione sbagliata per la parola della tua lingua madre.
user2357112 supporta Monica il

Grazie Monica! @ Grassetto: ho trovato questa domanda, ma è un problema diverso. Jonrsharpe ha fornito una risposta perfetta
offeltoffel il

2
Questo è anche correlato all'ordine di grandezza . 1 è 0 ° ordine, 10 è 1 ° ordine, 100 è 2 ° ordine, ecc.
wjandrea,

Risposte:


60

Puoi usare math.ceilcon math.log10per fare questo:

>>> 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)ti dà la soluzione xche soddisfa 10 ** x == n, quindi se arrotondi xti dà l'esponente per la prossima potenza più alta di 10.

Si noti che per un valore in ncui xè già un numero intero, la "potenza massima successiva di 10" sarà 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
Lavorare con la funzione log sembra essere solo il trucco che non sono riuscito a trovare. Credo che sia esattamente quello che speravo! Grazie mille
offeltoffel il

2
NB: a seconda del comportamento desiderato, questo non funziona con potenze di 10, ad esempio 10 ** math.ceil(math.log10(1)) == 1, che non è "la prossima potenza più alta"
Cireo

5
Nota: questa risposta si basa sull'aritmetica in virgola mobile e come tale può fallire a causa di errori di arrotondamento. Prova ad alimentare in 1000000000000001 per esempio.
lavaggio

2
@plugwash non necessariamente, le funzioni matematiche accettano anche, ad esempio, i decimali.
jonrsharpe,

5
Sì, puoi passare altri tipi, ma verranno convertiti in un numero in virgola mobile a doppia precisione e passati alla funzione C "log10". Esiste un caso speciale per impedire lo straripamento di registri di grandi numeri, ma nulla per impedire errori di arrotondamento.
lavaggio

21

Il tuo problema è sotto-specificato, devi fare un passo indietro e porre alcune domande.

  • Che tipo (i) sono i tuoi input?
  • Che tipo / i vuoi per le tue uscite?
  • Per risultati inferiori a 1, cosa vuoi esattamente arrotondare? Vuoi poteri effettivi di 10 o approssimazioni in virgola mobile di poteri di 10? Sei consapevole che i poteri negativi di 10 non possono essere espressi esattamente in virgola mobile, giusto? Supponiamo per ora che tu voglia approssimazioni in virgola mobile di potenze di 10.
  • Se l'ingresso è esattamente una potenza di 10 (o l'approssimazione in virgola mobile più vicina di una potenza di 10), l'uscita dovrebbe essere uguale all'ingresso? O dovrebbe essere il prossimo potere di 10 in su? "10 -> 10" o "10 -> 100"? Supponiamo che il primo per ora.
  • I tuoi valori di input possono essere qualsiasi valore possibile dei tipi in questione? o sono più vincolati.

In un'altra risposta è stato proposto di prendere il logaritmo, quindi arrotondare per eccesso (funzione soffitto), quindi esponenziare.

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

Purtroppo questo soffre di errori di arrotondamento. Prima di tutto n viene convertito da qualunque tipo di dati capiti in un numero in virgola mobile a doppia precisione, potenzialmente introducendo errori di arrotondamento, quindi il logaritmo viene calcolato potenzialmente introducendo più errori di arrotondamento sia nei suoi calcoli interni che nei suoi risultati.

In quanto tale, non mi ci è voluto molto per trovare un esempio in cui ha dato un risultato errato.

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

È anche teoricamente possibile che fallisca nella direzione opposta, anche se questo sembra essere molto più difficile da provocare.

Quindi per una soluzione solida per float e ints dobbiamo supporre che il valore del nostro logaritmo sia solo approssimativo, e quindi dobbiamo testare un paio di possibilità. Qualcosa del genere

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

Credo che questo codice dovrebbe dare risultati corretti per tutti gli argomenti in una gamma ragionevole di dimensioni del mondo reale. Si romperà per un numero molto piccolo o molto grande di tipi non interi e non in virgola mobile a causa di problemi di conversione in virgola mobile. Argomenti interi di Python per la funzione log10 nel tentativo di impedire l'overflow, ma con un numero intero sufficientemente massiccio potrebbe essere possibile forzare risultati errati a causa di errori di arrotondamento.

Per testare le due implementazioni ho usato il seguente programma di test.

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

Questo trova molti errori nell'implementazione ingenua, ma nessuno nell'implementazione migliorata.


Grazie per il tuo sforzo di approfondire qui. Mentre la risposta di jonrsharpe ha già risolto il mio problema, questa risposta può essere utile per gli altri con domande simili ma più particolari.
offeltoffel

1
Perché usi roundinvece di math.ceil? Ciò introdurrà molti casi non necessari in cui r < nè vero e quindi deve svolgere un lavoro aggiuntivo.
a_guest

1
Perché il registro potrebbe essere disattivato in entrambe le direzioni.
plugwash

1
utilizzando il codice "migliorata" ma con tutto sostituito da risultati Math.ceil in guasti per 1e-317 sulla parte bassa e 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 per la fascia alta.
plugwash

1
(in pratica probabilmente va bene però)
plugwash

3

Sembra che tu voglia piuttosto il prossimo potere più basso di 10 ... Ecco un modo che usa matematica pura e nessun registro, ma ricorsione.

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

Appena testato questo, lo sto dando un voto in quanto sembra funzionare bene nella maggior parte dei casi pratici, ma sembra soffrire di errori di arrotondamento con input sufficientemente piccoli. ceiling10 (1e-6) restituisce 1.0000000000000002e-06
lavaggio:

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

Qualcosa del genere forse? È appena al di sopra della mia testa ma ha funzionato quando ho provato alcuni numeri nel terminale.


0

Controllalo!

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

Questo codice si basa sul principio della potenza di dieci in len( str( int( float_number ) ) ).

Ci sono 4 casi:

    1. int( i ) > 1.

    Floatnumero - convertito in int, in seguito stringa str()da esso, ci darà un stringcon lengthcui stiamo guardando esattamente. Quindi, prima parte, per input i > 1.0- è dieci 10in potenza di questa lunghezza.

    1. & 3. Piccola ramificazione: i > 1.0e i > 0.1<=> è 10e 1rispettivamente.
    1. E l'ultimo caso, quando i < 0.1: qui, dieci saranno in potere negativo. Per ottenere il primo elemento diverso da zero dopo la virgola, ho usato tale costruzione ("%.100f" % i ).replace('.','').index( k ), in cui k supera l' [1:10]intervallo. Successivamente, prendere almeno l'elenco dei risultati. E diminuisce di uno, è il primo zero, che deve essere conteggiato. Inoltre, qui standard di pitone di index()potrebbe bloccarsi, se non troverà almeno uno di non-zero elemento da [1:10]intervallo, è per questo che alla fine devo "filtro" messa in vendita dal verificarsi: if str( j ) in "%.100f" % i. Inoltre, per essere più precisi, %.100fpossono essere considerati diversi.

Aggiungi qualche spiegazione per favore.
Mobin Ranjbar,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.