Python arrondi à la prochaine puissance la plus élevée de 10

44

Comment pourrais-je réussir à faire en math.ceilsorte qu'un nombre soit attribué à la prochaine puissance la plus élevée de 10?

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

Ma solution actuelle est un dictionnaire qui vérifie la plage du numéro d'entrée, mais il est codé en dur et je préférerais une solution à une ligne. Peut-être qu'il me manque une astuce mathématique simple ou une fonction numpy correspondante ici?

offeltoffel
la source
3
@bold semble que ces solutions fonctionnent à partir du 10haut, cela aura besoin de quelque chose avec par exemple log10.
jonrsharpe
3
Le mot que vous voulez est "pouvoir". Peut-être que vous avez la mauvaise traduction pour le mot de votre langue maternelle.
user2357112 prend en charge Monica
Merci, Monica! @bold: J'ai trouvé cette question, mais c'est un problème différent. Jonrsharpe a fourni une réponse parfaite
offeltoffel
2
Ceci est également lié à l' ordre de grandeur . 1 est 0e ordre, 10 est 1er ordre, 100 est 2e ordre, etc.
wjandrea

Réponses:

60

Vous pouvez utiliser math.ceilavec math.log10pour cela:

>>> 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)vous donne la solution xqui vous satisfait 10 ** x == n, donc si vous arrondissez xcela vous donne l'exposant pour la prochaine puissance la plus élevée de 10.

Notez que pour une valeur nxest déjà un entier, la "prochaine puissance la plus élevée de 10" sera 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
la source
1
Travailler avec la fonction journal semble être juste l'astuce que je n'ai pas pu trouver. Je crois que c'est exactement ce que j'espérais! Merci beaucoup
offeltoffel
2
NB: en fonction de votre comportement souhaité, cela ne fonctionne pas pour des puissances de 10, par exemple 10 ** math.ceil(math.log10(1)) == 1, qui n'est pas "la prochaine puissance la plus élevée"
Cireo
5
Remarque: cette réponse repose sur l'arithmétique à virgule flottante et en tant que telle, elle peut échouer en raison d'erreurs d'arrondi. Essayez par exemple de nourrir 1000000000000001.
plugwash
2
@plugwash pas nécessairement, les fonctions mathématiques accepteront également par exemple decimal.Decimals.
jonrsharpe
5
Oui, vous pouvez passer d'autres types, mais ils seront convertis en un nombre à virgule flottante double précision et transmis à la fonction C "log10". Il existe un cas particulier pour éviter les débordements de journaux de grands nombres, mais rien pour éviter les erreurs d'arrondi.
plugwash
21

Votre problème est sous-spécifié, vous devez prendre du recul et poser quelques questions.

  • Quels types sont vos entrées?
  • Quel (s) type (s) souhaitez-vous pour vos sorties?
  • Pour des résultats inférieurs à 1, à quoi voulez-vous exactement arrondir? Voulez-vous des puissances réelles de 10 ou des approximations en virgule flottante de puissances de 10? Vous savez que des puissances négatives de 10 ne peuvent pas être exprimées exactement en virgule flottante, n'est-ce pas? Supposons pour l'instant que vous vouliez des approximations en virgule flottante de puissances de 10.
  • Si l'entrée est exactement une puissance de 10 (ou l'approximation en virgule flottante la plus proche d'une puissance de 10), la sortie doit-elle être la même que l'entrée? Ou devrait-il être la prochaine puissance de 10? "10 -> 10" ou "10 -> 100"? Supposons le premier pour l'instant.
  • Vos valeurs d'entrée peuvent-elles être une valeur possible des types en question? ou sont-ils plus contraints.

Dans une autre réponse, il a été proposé de prendre le logarithme, puis d'arrondir (fonction plafond), puis d'exposer.

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

Malheureusement, cela souffre d'erreurs d'arrondi. Tout d'abord, n est converti à partir de n'importe quel type de données qu'il se trouve avoir en un nombre à virgule flottante double précision, introduisant potentiellement des erreurs d'arrondi, puis le logarithme est calculé, introduisant potentiellement plus d'erreurs d'arrondi à la fois dans ses calculs internes et dans son résultat.

En tant que tel, il ne m'a pas fallu longtemps pour trouver un exemple où il a donné un résultat incorrect.

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

Il est également théoriquement possible qu'il échoue dans l'autre sens, bien que cela semble beaucoup plus difficile à provoquer.

Donc, pour une solution robuste pour les flottants et les entiers, nous devons supposer que la valeur de notre logarithme n'est qu'approximative, et nous devons donc tester quelques possibilités. Quelque chose dans le sens de

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

Je crois que ce code devrait donner des résultats corrects pour tous les arguments dans une gamme sensible de magnitudes réelles. Il se cassera pour de très petits ou très grands nombres de types non entiers et non flottants en raison de problèmes de conversion en virgule flottante. Cas spéciaux Python arguments entiers à la fonction log10 dans le but d'empêcher le débordement, mais toujours avec un entier suffisamment massif, il peut être possible de forcer des résultats incorrects en raison d'erreurs d'arrondi.

Pour tester les deux implémentations, j'ai utilisé le programme de test suivant.

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

Cela trouve beaucoup d'échecs dans l'implémentation naïve, mais aucun dans l'implémentation améliorée.

plugwash
la source
Merci de vos efforts pour entrer plus en détail ici. Bien que la réponse de jonrsharpe ait déjà résolu mon problème, cette réponse peut être utile pour d'autres ayant des questions similaires mais plus particulières.
offeltoffel
1
Pourquoi utilisez-vous roundau lieu de math.ceil? Cela introduira de nombreux cas inutiles lorsque cela r < nest vrai et qu'il doit donc effectuer un travail supplémentaire.
a_guest
1
Parce que le journal pourrait être éteint dans les deux sens.
plugwash
1
en utilisant le code "amélioré" mais avec round remplacé par math.ceil entraîne des échecs pour 1e-317 sur le bas de gamme et 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
plugwash
1
(en pratique, c'est probablement bien)
plugwash
3

Il semble que vous souhaitiez plutôt la prochaine puissance la plus basse de 10 ... Voici un moyen d'utiliser des mathématiques pures et pas de journal, mais de récursivité.

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
la source
Je viens de tester celui-ci, je lui donne une note positive car il semble bien fonctionner dans la plupart des cas pratiques, mais il semble souffrir d'erreurs d'arrondi avec des entrées suffisamment petites. plafond10 (1e-6) donne 1.0000000000000002e-06
plugwash
0
y = math.ceil(x)
z = y + (10 - (y % 10))

Quelque chose comme ça peut-être? C'est juste sur le dessus de ma tête, mais cela a fonctionné lorsque j'ai essayé quelques chiffres dans le terminal.

Lugene
la source
0

Regarde ça!

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

Ce code basé sur le principe du pouvoir de dix en len( str( int( float_number ) ) ).

Il y a 4 cas:

    1. int( i ) > 1.

    Floatnombre - converti en int, puis chaîne à str()partir de celui-ci, nous donnera un stringavec lengthlequel nous cherchons exactement. Donc, première partie, pour l'entrée i > 1.0- c'est dix 10en puissance de cette longueur.

    1. & 3. Peu de branchement: i > 1.0et i > 0.1<=> c'est 10et 1respectivement.
    1. Et dernier cas, quand i < 0.1: Ici, dix doivent avoir une puissance négative. Pour obtenir le premier élément non nul après une virgule, j'ai utilisé une telle construction ("%.100f" % i ).replace('.','').index( k ), où k est exécuté sur un [1:10]intervalle. Ensuite, prenez le minimum de liste de résultats. Et diminuer d'une unité, c'est d'abord zéro, qui doit être compté. De plus, est ici standard de Python index()peut se bloquer, si elle ne trouvera pas au moins l' un des élément non nul d' [1:10]intervalle, qui est la raison pour laquelle à la fin je dois « filtrer » la liste par occurrence: if str( j ) in "%.100f" % i. En outre, pour obtenir une précision plus profonde - %.100fpeuvent être prises différemment.
Marshmello123123123
la source
Ajoutez quelques explications s'il vous plaît.
Mobin Ranjbar