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.
10
cima para baixo, isso precisará de algo com, por exemplolog10
.