# A function for calculating magnitude order of a number

What about a function for calculating magnitude order, maybe in math
module? Something like this:

``````def magnitude_order(num):
if num == 0:
return 0

absnum = abs(num)
order = math.log10(absnum)
res = math.floor(order)

return res
``````

Feels kinda niche, and easy enough to write yourself. Do you find yourself using it often or do you consider it more of a ‘nice to have’ kind of feature?

2 Likes

Do you truly need it to be precisely the base 10 log? If not, integers have a bit_length.

2 Likes

Be wary of using logarithms for this computation, notably for large integers close to boundaries of orders of magnitude. See this, for example:

``````for exponent in range(11, 20):
print(exponent, 10 ** exponent - 1, magnitude_order(10 ** exponent - 1))
``````

Output:

``````11 99999999999 10
12 999999999999 11
13 9999999999999 12
14 99999999999999 13
15 999999999999999 15
16 9999999999999999 16
17 99999999999999999 17
18 999999999999999999 18
19 9999999999999999999 19
``````

Note the increase of `2` in the computed order of magnitude between these two lines of output:

``````14 99999999999999 13
15 999999999999999 15
``````

EDIT:

To get a closer look at that affected boundary, we can try this:

``````for num in range(999999999999996, 1000000000000002):
print(num, magnitude_order(num))
``````

Output:

``````999999999999996 14
999999999999997 14
999999999999998 15
999999999999999 15
1000000000000000 15
1000000000000001 15
``````
3 Likes

I used it and I find it very useful. It’s common in physics to calculate the magnitude order of observables.

Well, you can add a parameter for the base. I wrote 10 because it’s the most common base for calculating the order of magnitude.

I think that, if the base is two and the value is an integer you can use bit_length, yes.

Yes, mine was only an example. Probably CPython already has a better algorithm, since you can format a number in scientific notation.

I think Chris means that, for an integer `k`, the number of decimal digits is `bit_length(k) * log(2)` … rounded up … at least approximately. Close enough for physics.

If you want an answer that is exactly the decimal exponent that `repr(float(k))` would give, that’s quite delicate near boundaries as @Quercus indicates. ISTR this part of the decimal formatting is complicated (and so is the rest of it).

I think in English, and physics, this is usually called “order of magnitude”.

When doing order of magnitude calculations, I think that it is far more common to round the value rather than to floor it. 985 is closer to 1000 than 100, so it should be three orders of magnitude, not two.

But putting that issue aside:

Being delicate and complicated is an argument in favour of doing it right in the stdlib.

The naive algorithm by Marco is sometimes wrong, even for exact powers of ten:

``````def order(x):
if x == 0: return 0
return math.floor(math.log10(abs(x)))

print([i for i in range(1, 100001) if order(10**i) != i])
``````

Output is: `[512, 1024, 2048, 32768, 65536]`

For one less than an exact powers of ten it is frequently wrong. Note the change in comparison – the following shows the few times it is correct:

``````print([i for i in range(1, 100001) if order(10**i - 1) == i-1])
``````

Giving results: `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 512, 1024, 2048, 32768, 65536]`

Unfortunately Stackoverflow gives only the naive algorithm.

The orders of magnitude boundaries aren’t at 100, 1000, 10000 and so on, but rather at 10^1.5 (~31), 10^2.5 (~310) etc. Floating point error is very much not important for these conversions. This leads me to think that the OP wants a simple number-of-digits-in-base function

2 Likes

Agreed, it is more usual to round than floor.

Wait, are you suggesting that 30 is “one order of magnitude” but 32 is “two orders of magnitude”? That surely isn’t right.

Why not? There has to be a boundary somewhere. Otherwise, it’s just as weird that 99 is “one” and 101 is “two”. Orders of magnitude aren’t meant to be infinitely granular logarithms.

This does mean there’s a difference between “length of the number” and “order of magnitude”, but only by which direction you round them. We can all agree that 1056 is a much bigger number than 8, and the dispute will only happen when you ask whether or not 60 is a notably bigger number than 20.

@Marco_Sulla, perhaps we need a clarification regarding precisely what you would like this function to do. Please indicate where you would like to place boundaries between ranges of numbers that produce different `return` values. Given your specifications, we can combine our efforts to achieve the desired result, and regardless of whether or not such a function becomes part of the `math` module, as you have proposed, we will have something that is useful to you In that case, you’re comparing two numbers, whose relative difference is less than one order of magnitude, so you would say that they both have the same order of magnitude. In isolation from each other, I would be suggesting what you say.

You seem to be using a definition of “order of magnitude” which is nothing like anything I’ve seen in physics or mathematics. It is certainly nothing like the way it is taught is Australia.

The usual calculation for orders of magnitude is:

• round the number to the nearest exact power of 10;

• the order of magnitude is that power.

E.g. 49 is closer to 10 than 100, so rounds to 10; 51 is closer to 100 than 10, so rounds to 100.

(One might use truncation instead, although I’m not sure why. Orders of magnitude are normally used for rough calculations and estimates. If you estimated a job would come in at \$999 would you really truncate it as “\$100 as an order of magnitude”? I don’t think so.)

This is equivalent to the exponent part in scientific notation, and to the calculation:

• write the number as `a*10**b`, where `1 <= a < 10`

• the order of magnitude is the power `b`.

Do you have a reference for this unusual definition other than Wikipedia? Wikipedia gives three different definitions, the first agrees with you but its citation to Wolfram Mathworld does not justify the definition given.

The Talk page for the Wikipedia article contains this comment from an anonymous editor:

“This article suffers from a problem that afflicts many minor articles on Wikipedia. It is founded on a definition that exists only on Wikipedia, a definition that was crafted by a Wikipedian editor/author but is not sourced”

and then ironically that editor then goes on to seemingly invent their own unsourced definiton of order of magnitude where the boundaries are `10**(-0.5) <= a < 10**0.5`.

Here are some additional citations showing order of magnitude calculations:

Hmmm… I wonder whether somebody started with the (incorrect) implementation `round(math.log10(x))` and worked backwards to the definition, instead of starting with the definition in terms of place value notation and then coming up with an implementation?

Or possibly confused logarithmic scales with orders of magnitude?

1 Like

I have a number of digits function. If nothing else, this discussion has revealed to me that it is buggy!

I think what we need to make this work is a floor logarithm function, one which always truncates instead of being correctly rounded. So something like this might work…

``````import math

def floor_log(x, base=10):
if base == 10:
a = math.log10(x)
elif base == 2:
a = math.log2(x)
else:
a = math.log(x, base)
if pow(base, a) > x:
a -= math.ulp(a)
return a

def num_digits(num, base=10):
"""Return the number of digits of num in the given base.

The sign is ignored, and zero is treated as having a single digit.
"""
num = abs(num)
if num == 0:
return 1
a = floor_log(num, base)
return math.floor(a) + (pow(base, a) <= num)

``````

I have done very little testing on the above, so it may contain bugs.

Orders of magnitude ARE logarithmic, by definition. Why is it so illogical to your brain that the boundaries be the geometric means?

Regarding “51 is closer to 100 than 10” what is the definition of
“closer” used for this? 51 is closer to 100 than to 0, is that what
you meant? 55 is halfway between 10 and 100, so by simple distance
51 is closer to 10 than it is to 100.

Doesn’t look trustworthy. It managed to get all six derived values in its “What if we were to use grams?” table wrong, in different ways. If only it were Wikipedia so we could fix it I think you say 51 gets rounded to 100 (order 2?), but you also say it’s done as 5.1×10¹ (order 1?), and you say those two ways are equivalent. But they’re not, are they?

Btw I’ve been wanting `math.ilog` for a while, corresponding to `math.isqrt`. Mostly because I’ve seen too many people use some form of `floor(log(n, base))` and I’m tired of showing them how inaccurate it is. Also, that use of `float` not only makes it inaccurate but also limited to a rather small finite range. I think one of the recent int<->str discussions had a short and supposedly fast Python implementation (as part of a referenced “algorithm 1.26”, I think).

1 Like