Without Converting to a String, How Many Digits Does a Fixnum Have?
Solution 1
puts Math.log10(1234).to_i + 1 # => 4
You could add it to Fixnum like this:
class Fixnum
def num_digits
Math.log10(self).to_i + 1
end
end
puts 1234.num_digits # => 4
Solution 2
Ruby 2.4 has an Integer#digits method, which return an Array containing the digits.
num = 123456
num.digits
# => [6, 5, 4, 3, 2, 1]
num.digits.count
# => 6
EDIT:
To handle negative numbers (thanks @MatzFan), use the absolute value. Integer#abs
-123456.abs.digits
# => [6, 5, 4, 3, 2, 1]
Solution 3
Sidenote for Ruby 2.4+
I ran some benchmarks on the different solutions, and Math.log10(x).to_i + 1
is actually a lot faster than x.to_s.length
. The comment from @Wayne Conrad is out of date. The new solution with digits.count
is trailing far behind, especially with larger numbers:
with_10_digits = 2_040_240_420
print Benchmark.measure { 1_000_000.times { Math.log10(with_10_digits).to_i + 1 } }
# => 0.100000 0.000000 0.100000 ( 0.109846)
print Benchmark.measure { 1_000_000.times { with_10_digits.to_s.length } }
# => 0.360000 0.000000 0.360000 ( 0.362604)
print Benchmark.measure { 1_000_000.times { with_10_digits.digits.count } }
# => 0.690000 0.020000 0.710000 ( 0.717554)
with_42_digits = 750_325_442_042_020_572_057_420_745_037_450_237_570_322
print Benchmark.measure { 1_000_000.times { Math.log10(with_42_digits).to_i + 1 } }
# => 0.140000 0.000000 0.140000 ( 0.142757)
print Benchmark.measure { 1_000_000.times { with_42_digits.to_s.length } }
# => 1.180000 0.000000 1.180000 ( 1.186603)
print Benchmark.measure { 1_000_000.times { with_42_digits.digits.count } }
# => 8.480000 0.040000 8.520000 ( 8.577174)
Solution 4
Although the top-voted loop is nice, it isn't very Ruby and will be slow for large numbers, the .to_s is a built-in function and therefore will be much faster. ALMOST universally built-in functions will be far faster than constructed loops or iterators.
Solution 5
Another way:
def ndigits(n)
n=n.abs
(1..1.0/0).each { |i| return i if (n /= 10).zero? }
end
ndigits(1234) # => 4
ndigits(0) # => 1
ndigits(-123) # => 3
Orcris
Updated on December 18, 2021Comments
-
Orcris over 2 years
I want find the
length
of aFixnum
,num
, without converting it into aString
.In other words, how many digits are in
num
without calling the.to_s()
method:num.to_s.length
-
Jörg W Mittag over 11 yearsWhat is the "length of a
Fixnum
"? In what representation? -
Orcris over 11 years@JörgWMittag: How many digits are in it.
-
-
Wayne Conrad over 11 yearsEvery time I profile something, I get a surprise. In MRI 1.9.3,
n.to_s.length
is faster for any integer represented by a Fixnum. A lot faster: On my box,n.to_s.length
takes somewhere between a third and half the time of the logarithm method, depending upon the length of the number. If the number has to be represented a Bignum, then the logarithm method starts winning. Both methods are very fast, though, at around .6 milliseconds (for the logarithm method), and between 0.2 and 0.3 milliseconds (for the string method). -
Alex D over 11 years@WayneConrad: Sounds like
Math.log10
must have a rather inefficient implementation. I just tried a simple method which walks over a table of all the powers of 10 which fit in 32/64 bits, and does a>=
comparison for each one -- it was a touch faster thanMath.log10
, but still slower thanto_s
. It could be made faster by "unrolling" a binary search of the same table, just like unrolling a loop (then the table wouldn't be needed any more -- the same numbers would be hard-coded into a series of conditionals). -
steenslag over 11 years@sawa: yes, I noticed. Calling
.abs
resulted in a warning (when run withruby -w
) which I did not understood nor cared for. I figured an error was still better then a wrong result from theto_s.size
idea. -
Laurent about 8 yearswhat about the negative integers ?
-
YoTengoUnLCD almost 8 yearsWarning: This breaks with 0 as well.
-
Alfonso Vergara over 7 yearsThe method size works well with fixnum objects. 10.size, fixnum.size.
-
MatzFan about 7 years..if it is a positive Integer, otherwise
Math::DomainError
. So much more Ruby though -
davegson over 6 years@WayneConrad your comment is outdated, look at my answer below
-
davegson almost 6 yearscheck my benchmarks why you should not use
.digits
for large scale use cases -
Nnnes over 2 yearsAnother data point: Using Ruby 3.0, for a 208987640-digit number
x
,Math.log10(x).to_i + 1
runs in 0.000013s on my machine;x.to_s.length
takes 45.9s (and I'm not willing to tryx.digits.count
). -
Chris over 2 yearsThe question is explicit about looking for a solution that doesn't involve converting to a string.