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?
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
Your problem is under-specified, you need to step back and ask some questions.
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.
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:
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.
i > 1.0 and i > 0.1 <=> it is 10 and 1 respectively.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.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))
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.
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.