Python volta para a próxima potência mais alta de 10

44

Como eu conseguiria executar math.ceiltal número que é atribuído à próxima potência mais alta de 10?

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

Minha solução atual é um dicionário que verifica o intervalo do número de entrada, mas é codificado permanentemente e eu prefiro uma solução de uma linha. Talvez esteja faltando um truque matemático simples ou uma função numpy correspondente aqui?

offeltoffel
fonte
3
O @ bold parece que essas soluções funcionam de 10cima para baixo, isso precisará de algo com, por exemplo log10.
jonrsharpe 03/04
3
A palavra que você quer é "poder". Talvez você tenha traduzido incorretamente a palavra da sua língua nativa.
user2357112 suporta Monica
Obrigado Monica! @ bold: Encontrei esta pergunta, mas é um problema diferente. Jonrsharpe forneceu uma resposta perfeita
offeltoffel 03/04
2
Isso também está relacionado à ordem de magnitude . 1 é 0ª ordem, 10 é 1ª ordem, 100 é 2ª ordem, etc.
wjandrea 03/04

Respostas:

60

Você pode usar math.ceilcom math.log10para fazer isso:

>>> 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)fornece a solução xque satisfaz 10 ** x == n; portanto, se você arredondar, xele fornece o expoente para a próxima potência mais alta de 10.

Observe que, para um valor em nque xjá seja um número inteiro, a "próxima potência mais 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
fonte
11
Trabalhar com a função log parece ser apenas o truque que não consegui inventar. Eu acredito que isso é exatamente o que eu estava esperando! Muito obrigado
offeltoffel 03/04
2
Nota: dependendo do comportamento desejado, isso não funciona para potências de 10, por exemplo 10 ** math.ceil(math.log10(1)) == 1, que não é "a próxima potência mais alta"
Cireo 03/04
5
Nota: esta resposta depende da aritmética do ponto flutuante e, como tal, pode falhar devido a erros de arredondamento. Tente alimentar 1000000000000001, por exemplo.
plugwash 03/04
2
@plugwash não necessariamente, as funções matemáticas também aceitam, por exemplo, decimal.Decimals.
jonrsharpe 3/04
5
Sim, você pode passar outros tipos, mas eles serão convertidos em um número de ponto flutuante de precisão dupla e passados ​​para a função C "log10". Há um caso especial para impedir que os logs de grandes números sejam excedidos, mas nada para evitar erros de arredondamento.
plugwash
21

Seu problema está subespecificado, você precisa voltar atrás e fazer algumas perguntas.

  • Que tipo (s) são suas entradas?
  • Que tipo (s) você deseja para suas saídas?
  • Para resultados inferiores a 1, para o que exatamente você deseja arredondar? Deseja potências reais de 10 ou aproximações de potências de ponto flutuante de 10? Você sabe que potências negativas de 10 não podem ser expressas exatamente em ponto flutuante, certo? Vamos supor, por enquanto, que você deseja aproximações de vírgulas flutuantes de potências de 10.
  • Se a entrada for exatamente uma potência de 10 (ou a aproximação de ponto flutuante mais próxima de uma potência de 10), a saída deve ser a mesma que a entrada? Ou deveria ser a próxima potência de 10 acima? "10 -> 10" ou "10 -> 100"? Vamos assumir o primeiro por enquanto.
  • Seus valores de entrada podem ter algum valor possível dos tipos em questão? ou eles são mais restritos.

Em outra resposta, foi proposto pegar o logaritmo, arredondar para cima (função de teto) e depois exponenciar.

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

Infelizmente, isso sofre erros de arredondamento. Antes de tudo, n é convertido de qualquer tipo de dado que possua em um número de ponto flutuante de precisão dupla, potencialmente introduzindo erros de arredondamento, e o logaritmo é calculado, potencialmente introduzindo mais erros de arredondamento, tanto em seus cálculos internos quanto em seu resultado.

Como tal, não demorei muito para encontrar um exemplo em que desse um resultado incorreto.

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

Também é teoricamente possível que fracasse na outra direção, embora isso pareça ser muito mais difícil de provocar.

Portanto, para uma solução robusta para flutuadores e entradas, precisamos assumir que o valor do nosso logaritmo é apenas aproximado e, portanto, devemos testar algumas possibilidades. Algo ao longo das linhas de

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

Acredito que este código deve fornecer resultados corretos para todos os argumentos em uma gama sensata de magnitudes no mundo real. Ele será interrompido por números muito pequenos ou muito grandes de tipos de pontos não inteiros e não flutuantes devido a problemas ao convertê-los em ponto flutuante. Casos especiais do Python argumentam argumentos inteiros para a função log10 na tentativa de impedir o estouro, mas ainda com um número inteiro suficientemente grande, pode ser possível forçar resultados incorretos devido a erros de arredondamento.

Para testar as duas implementações, usei o seguinte programa de teste.

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

Isso encontra muitas falhas na implementação ingênua, mas nenhuma na implementação aprimorada.

plugwash
fonte
Obrigado pelo seu esforço de detalhar mais aqui. Embora a resposta de jonrsharpe já tenha resolvido meu problema, essa resposta pode ser útil para outras pessoas com perguntas semelhantes, mas mais particulares.
offeltoffel 5/04
11
Por que você usa em roundvez de math.ceil? Isso apresentará muitos casos desnecessários onde r < né verdade e, portanto, ele precisa executar um trabalho adicional.
a_guest 8/04
11
Porque o log pode estar desativado em qualquer direção.
plugwash
11
usando o código "aprimorado", mas com round substituído por math.ceil, resulta em falhas para 1e-317 na extremidade baixa e 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
plugwash
11
(na prática provavelmente é bom)
plugwash 8/04
3

Parece que você quer a menor potência seguinte de 10 ... Aqui está uma maneira de usar matemática pura e sem log, mas com recursão.

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
fonte
Apenas testei este, estou dando um voto positivo, pois parece funcionar bem na maioria dos casos práticos, mas parece sofrer de erros de arredondamento com entradas suficientemente pequenas. teto10 (1e-6) fornece 1.0000000000000000002e-06
plugwash
0
y = math.ceil(x)
z = y + (10 - (y % 10))

Algo assim talvez? Está no topo da minha cabeça, mas funcionou quando tentei alguns números no terminal.

Lugene
fonte
0

Veja isso!

>>> 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 é baseado no princípio do poder de dez len( str( int( float_number ) ) ).

Existem 4 casos:

    1. int( i ) > 1.

    Floatnumber - convertido para int, a partir daí, a string str(), nos dará um stringcom o lengthqual estamos olhando exatamente. Então, primeira parte, para entrada i > 1.0- são dez 10neste poder.

    1. & 3. Pouca ramificação: i > 1.0e i > 0.1<=> é 10e 1respectivamente.
    1. E último caso, quando i < 0.1: Aqui, dez estarão em poder negativo. Para obter o primeiro elemento diferente de zero após a vírgula, usei essa construção("%.100f" % i ).replace('.','').index( k ) , em que k é executado no [1:10]intervalo. Depois disso, faça o mínimo da lista de resultados. E diminuir por um, é o primeiro zero, que será contado. Além disso, aqui padrão python do index()pode falhar, se não vai encontrar pelo menos um de diferente de zero elemento de [1:10]intervalo, é por isso que, no final, eu devo "filtro" listando por ocorrência: if str( j ) in "%.100f" % i. Além disso, para ficar mais preciso - %.100fpode ser diferente.
Marshmello123123123
fonte
Adicione alguma explicação, por favor.
Mobin Ranjbar