Python round to next highest power of 10

Question:

How would I manage to perform math.ceil such that a number is assigned to the next highest power of 10?

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

My current solution is a dictionary that checks the range of the input number, but it’s hardcoded and I would prefer a one-liner solution. Maybe I am missing a simple mathematical trick or a corresponding numpy function here?

Asked By: offeltoffel

||

Answers:

You can use math.ceil with math.log10 to do this:

>>> 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) gives you the solution x that satisfies 10 ** x == n, so if you round up x it gives you the exponent for the next highest power of 10.

Note that for a value n where x is already an integer, the “next highest power of 10” will be n:

>>> 10 ** math.ceil(math.log10(0.1))
0.1
>>> 10 ** math.ceil(math.log10(1))
1
>>> 10 ** math.ceil(math.log10(10))
10
Answered By: jonrsharpe

Your problem is under-specified, you need to step back and ask some questions.

  • What type(s) are your inputs?
  • What type(s) do you want for your outputs?
  • For results less than 1, what exactly do you want to round to? Do you want actual powers of 10 or floating point approximations of powers of 10? You are aware that negative powers of 10 can’t be expressed exactly in floating point right? Let’s assume for now that you want floating point approximations of powers of 10.
  • If the input is exactly a power of 10 (or the closest floating point approximation of a power of 10), should the output be the same as the input? Or should it be the next power of 10 up? “10 -> 10” or “10 -> 100”? Let’s assume the former for now.
  • Can your input values be any possible value of the types in question? or are they more constrained.

In another answer it was proposed to take the logarithm, then round up (ceiling function), then exponentiate.

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

Unfortunately this suffers from rounding errors. First of all n is converted from whatever data type it happens to have into a double precision floating point number, potentially introducing rounding errors, then the logarithm is calculated potentially introducing more rounding errors both in its internal calculations and in its result.

As such it did not take me long to find an example where it gave an incorrect result.

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

It is also theoretically possible for it to fail in the other direction, though this seems to be much harder to provoke.

So for a robust solution for floats and ints we need to assume that the value of our logarithm is only approximate, and we must therefore test a couple of possibilities. Something along the lines of

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

I believe this code should give correct results for all arguments in a sensible real-world range of magnitudes. It will break for very small or very large numbers of non integer and non-floating point types because of issues converting them to floating point. Python special cases integer arguments to the log10 function in an attempt to prevent overflow, but still with a sufficiently massive integer it may be possible to force incorrect results due to rounding errors.

To test the two implementations I used the following test program.

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

This finds lots of failures in the naive implementation, but none in the improved implementation.

Answered By: plugwash

Check this out!

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

This code based on principle of ten’s power in len(str(int(float_number))).

There are 4 cases:

    1. int(i) > 1.

      Float number – converted to int, thereafter string str() from it, will give us a string with length which is we are looking exactly. So, first part, for input i > 1.0 – it is ten 10 in power of this length.

    1. & 3. Little branching: i > 1.0 and i > 0.1 <=> it is 10 and 1 respectively.
    1. And last case, when i < 0.1: Here, ten shall be in negative power. To get first non zero element after comma, I’ve used such construction ("%.100f" % i).replace('.', '').index(k), where k run over [1:10] interval. Thereafter, take minimum of result list. And decrease by one, it is first zero, which shall be counted. Also, here standard python’s index() may crash, if it will not find at least one of non-zero element from [1:10] interval, that is why in the end I must “filter” listing by occurrence: if str(j) in "%.100f" % i.
      Additionally, to get deeper precise – %.100f may be taken differ.
Answered By: Marshmello123123123

It seems you want rather the lowest next power of 10…
Here is a way using pure maths and no log, but recursion.

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))
Answered By: Silvain Dupertuis
y = math.ceil(x)
z = y + (10 - (y % 10))

Something like this maybe? It’s just off the top of my head but it worked when I tried a few numbers in terminal.

Answered By: Lugene

I think the simplest way is:

import math


number = int(input('Enter a number: '))
next_pow_ten = round(10 ** math.ceil(math.log10(number)))
print(str(10) + ' power ' + str(round(math.log10(number))) + ' = '
      + str(next_pow_ten))

I hope this help you.

Answered By: Vacendak
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.