Python ronda a la siguiente potencia más alta de 10

44

¿Cómo me las arreglaría para math.ceilque un número se asigne a la siguiente potencia más alta de 10?

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

Mi solución actual es un diccionario que verifica el rango del número de entrada, pero está codificado y preferiría una solución de una línea. ¿Tal vez me estoy perdiendo un simple truco matemático o una función numpy correspondiente aquí?

offeltoffel
fuente
3
@bold parece que esas soluciones funcionan desde 10arriba, esto necesitará algo con, por ejemplo log10.
jonrsharpe
3
La palabra que quieres es "poder". Tal vez recibió la traducción incorrecta de la palabra de su idioma nativo.
user2357112 es compatible con Monica el
Gracias monica @ negrita: Encontré esta pregunta, pero es un problema diferente. Jonrsharpe ha proporcionado una respuesta perfecta
offeltoffel
2
Esto también está relacionado con el orden de magnitud . 1 es el 0 ° orden, 10 es el 1 ° orden, 100 es el 2 ° orden, etc.
wjandrea

Respuestas:

60

Puedes usar math.ceilconmath.log10 para hacer esto:

>>> 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) te da la solución x que satisface 10 ** x == n, por lo que si la redondeas xte da el exponente para la próxima potencia más alta de 10.

Tenga en cuenta que para un valor ndonde xya es un entero, la "próxima potencia más alta de 10" será 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
fuente
1
Trabajar con la función de registro parece ser el truco que no se me ocurrió. ¡Creo que esto es exactamente lo que esperaba! Muchas gracias
offeltoffel
2
NB: dependiendo de su comportamiento deseado, esto no funciona para potencias de 10, por ejemplo 10 ** math.ceil(math.log10(1)) == 1, que no es "la próxima potencia más alta"
Cireo
55
Nota: esta respuesta se basa en la aritmética de coma flotante y, como tal, puede fallar debido a errores de redondeo. Intente alimentar en 1000000000000001 por ejemplo.
lavar el
2
@plugwash no necesariamente, las funciones matemáticas también aceptarán, por ejemplo, decimales.
jonrsharpe
55
Sí, puede pasar otros tipos, pero se convertirán a un número de coma flotante de doble precisión y se pasarán a la función C "log10". Hay un caso especial para evitar que se desborden registros de grandes cantidades, pero nada para evitar errores de redondeo.
lavar el
21

Su problema está subespecificado, debe retroceder y hacer algunas preguntas.

  • ¿De qué tipo (s) son sus entradas?
  • ¿Qué tipo (s) desea para sus salidas?
  • Para resultados inferiores a 1, ¿a qué quiere redondear exactamente? ¿Desea potencias reales de 10 o aproximaciones de coma flotante de potencias de 10? Eres consciente de que las potencias negativas de 10 no se pueden expresar exactamente en coma flotante, ¿verdad? Supongamos por ahora que desea aproximaciones de punto flotante de potencias de 10.
  • Si la entrada es exactamente una potencia de 10 (o la aproximación de punto flotante más cercana de una potencia de 10), ¿la salida debería ser la misma que la entrada? ¿O debería ser el próximo poder de 10 arriba? "10 -> 10" o "10 -> 100"? Asumamos lo primero por ahora.
  • ¿Pueden sus valores de entrada ser cualquier valor posible de los tipos en cuestión? o están más restringidos.

En otra respuesta, se propuso tomar el logaritmo, luego redondear hacia arriba (función de techo), luego exponer.

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

Lamentablemente, esto sufre de errores de redondeo. En primer lugar, n se convierte de cualquier tipo de datos que tenga en un número de coma flotante de doble precisión, lo que potencialmente introduce errores de redondeo, luego el logaritmo se calcula y posiblemente introduce más errores de redondeo tanto en sus cálculos internos como en su resultado.

Como tal, no me llevó mucho tiempo encontrar un ejemplo en el que proporcionara un resultado incorrecto.

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

También es teóricamente posible que falle en la otra dirección, aunque esto parece ser mucho más difícil de provocar.

Entonces, para una solución robusta para flotadores e ints, debemos suponer que el valor de nuestro logaritmo es solo aproximado y, por lo tanto, debemos probar un par de posibilidades. Algo en la línea de

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

Creo que este código debería dar resultados correctos para todos los argumentos en un rango sensible de magnitudes del mundo real. Se romperá para números muy pequeños o muy grandes de tipos no enteros y de punto flotante debido a problemas al convertirlos en punto flotante. Los argumentos enteros de casos especiales de Python para la función log10 en un intento de evitar el desbordamiento, pero aún con un número entero suficientemente masivo, es posible forzar resultados incorrectos debido a errores de redondeo.

Para probar las dos implementaciones utilicé el siguiente programa de prueba.

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

Esto encuentra muchas fallas en la implementación ingenua, pero ninguna en la implementación mejorada.

lavado
fuente
Gracias por su esfuerzo de entrar en más detalles aquí. Si bien la respuesta de jonrsharpe ya ha resuelto mi problema, esta respuesta puede ser útil para otros con preguntas similares pero más particulares.
offeltoffel
1
¿Por qué usas en roundlugar de math.ceil? Esto introducirá muchos casos innecesarios donde r < nes cierto y, por lo tanto, debe realizar un trabajo adicional.
a_guest
1
Porque el registro podría estar apagado en cualquier dirección.
lavar el
1
utilizando el código de "mejorado" pero con ronda sustituye por resultados Math.ceil en fracasos para 1e-317 en el extremo inferior y 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 en el extremo alto.
lavar el
1
(en la práctica, probablemente esté bien)
plugwash
3

Parece que quieres más bien la próxima potencia más baja de 10 ... Aquí hay una manera de usar matemáticas puras y sin registro, sino recurrencia.

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
fuente
Acabo de probar este, le estoy dando un voto positivo, ya que parece funcionar bien en la mayoría de los casos prácticos, pero parece sufrir errores de redondeo con entradas suficientemente pequeñas. ceiling10 (1e-6) da 1.0000000000000002e-06
lavar el
0
y = math.ceil(x)
z = y + (10 - (y % 10))

¿Algo así quizás? Está justo en la parte superior de mi cabeza, pero funcionó cuando probé algunos números en la terminal.

Lugene
fuente
0

¡Mira esto!

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

Este código se basa en el principal del poder de diez len( str( int( float_number ) ) ).

Hay 4 casos:

    1. int( i ) > 1.

    Floatnúmero - convertido a int, a partir de entonces una cadena a str()partir de él, nos dará una stringcon la lengthque estamos buscando exactamente. Entonces, la primera parte, para la entrada i > 1.0, es diez 10en potencia de esta longitud.

    1. & 3. Pocas ramificaciones: i > 1.0y i > 0.1<=> es 10y 1respectivamente.
    1. Y el último caso, cuando i < 0.1: Aquí, diez estarán en poder negativo. Para obtener el primer elemento distinto de cero después de la coma, he utilizado dicha construcción ("%.100f" % i ).replace('.','').index( k ), donde k se ejecuta en [1:10]intervalos. A partir de entonces, tome el mínimo de la lista de resultados. Y disminuir en uno, es el primer cero, que se contará. Además, aquí pitón de norma index()puede bloquearse, si no va a encontrar por lo menos uno de no-cero elemento de [1:10]intervalo, que es lo que al final debo "filtro" lista de ocurrencia: if str( j ) in "%.100f" % i. Además, para obtener una precisión más profunda, %.100fpuede tomarse diferente.
Marshmello123123123
fuente
Agregue alguna explicación por favor.
Mobin Ranjbar