Without Converting to a String, How Many Digits Does a Fixnum Have?

24,768

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
Share:
24,768
Orcris
Author by

Orcris

Updated on December 18, 2021

Comments

  • Orcris
    Orcris over 2 years

    I want find the length of a Fixnum, num, without converting it into a String.

    In other words, how many digits are in num without calling the .to_s() method:

    num.to_s.length
    
    • Jörg W Mittag
      Jörg W Mittag over 11 years
      What is the "length of a Fixnum"? In what representation?
    • Orcris
      Orcris over 11 years
      @JörgWMittag: How many digits are in it.
  • Wayne Conrad
    Wayne Conrad over 11 years
    Every 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
    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 than Math.log10, but still slower than to_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
    steenslag over 11 years
    @sawa: yes, I noticed. Calling .abs resulted in a warning (when run with ruby -w) which I did not understood nor cared for. I figured an error was still better then a wrong result from the to_s.size idea.
  • Laurent
    Laurent about 8 years
    what about the negative integers ?
  • YoTengoUnLCD
    YoTengoUnLCD almost 8 years
    Warning: This breaks with 0 as well.
  • Alfonso Vergara
    Alfonso Vergara over 7 years
    The method size works well with fixnum objects. 10.size, fixnum.size.
  • MatzFan
    MatzFan about 7 years
    ..if it is a positive Integer, otherwise Math::DomainError. So much more Ruby though
  • davegson
    davegson over 6 years
    @WayneConrad your comment is outdated, look at my answer below
  • davegson
    davegson almost 6 years
    check my benchmarks why you should not use .digits for large scale use cases
  • Nnnes
    Nnnes over 2 years
    Another 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 try x.digits.count).
  • Chris
    Chris over 2 years
    The question is explicit about looking for a solution that doesn't involve converting to a string.