Number of Digits in a Number
·For reasons involving string-formatting and width adjustments in a project, I ran into the pickle of figuring out how to get the number of digits in a number.
This isn’t a serious problem.
In theory I could have just used foobar.digits.size
,
be done with it, and save the hassle of writing this post.
While premature optimization is said to be the root of all evil,
this in particular felt more like a challenge to figure out how to
get the number of digits in a number without going through a loop.
Constant time rather than linear just to get this info is a bonus.
Since this project is written in Crystal, so is the code here.
Traditional Way
Like before, foobar.digits.size
would be the easy way out.
Int#digits
just gives us an Array
of digits in
a number by looping, dividing and getting the remainder from 10
and stopping when the quotient reaches zero.
# Int#digits is part of the standard library in Crystal
# but this method illustrates the gist of this.
def digits(number : Int32)
digits = Array(Int32).new
while !number.zero?
digits.push (number % 10).to_i
number = number.tdiv 10
end
return digits
end
The amount of iterations in this kind of loop is never large.
A UInt32
’s maximum limit is ten digits and a UInt64
’s,
the largest fixed number type, twenty digits.
The rocx
-y Road
For some reason this situation brought up “powers of ten” in my head. Specifically the part where, say, 103 means there’s three zeroes in 1,000. What’s the inverse of doing that? The logarithm. That looks like a good place to start.
Math.log10(2024)
# ⇒ 3.3062105081677613 : Float64
Math.log10(30)
# ⇒ 1.4771212547196624 : Float64
# Am I on the right track? 🤔 Let's try this...
Math.log10(2024).ceil.to_i
# ⇒ 4 : Int32
Math.log10(30)
# ⇒ 2 : Int32
# 😎
Nice. But there’s one snag I encountered while testing: dealing with powers of ten (or whatever the base used is).
Math.log10(1000).ceil.to_i
# ⇒ 3 : Int32
# 🫤
Well drat. Time to pull up a playground and tinker around. There has to be a pattern somewhere…
log_999 = Math.log10 999
# ⇒ 2.9995654882259823 : Float64
log_1000 = Math.log10 1000
# ⇒ 3.0 : Float64
log_1001 = Math.log10 1001
# ⇒ 3.000434077479319 : Float64
log_999.ceil # ⇒ 3.0 : Float64
log_1000.ceil # ⇒ 3.0 : Float64
log_1001.ceil # ⇒ 4.0 : Float64
Ooooooh I see now…
Aha! A Solution!
There’s the pattern. It looks like it’s a typical off-by-one error in my logic. What needs to be done is to get the logarithm of a given number plus one, turning log10(1000) into log10(1000 + 1).
def digit_count(number : Int32) : Int32
return Math.log10(number.succ).ceil.to_i
end
digit_count 999 # ⇒ 3 : Int32
digit_count 1000 # ⇒ 4 : Int32
digit_count 1001 # ⇒ 4 : Int32
No loops. Just raw mathematics. You never think this kind of stuff comes up outside of a problem domain in computer science and yet it still comes in handy.