Converting decimal to binary integers: custom methods vs. to_i [Update]
At my last entry, a question arose about what is the most efficient way to convert integers between the bases 2 and 10: either using built-in ruby methods (and even do lightweight string-operations) or calculating it manually. I had to find out ;). So I have written a little benchmark program, which does the conversion in three different ways:
- using built-in to_i-magic
- calculating it by hand
- using sprintf
It stops the time each method needs to get the fastest. The result might be surprising. [Update: improved the custom methods]
In most cases, the built-in functions are slightly faster than the custom methods. The following extract shows the output on my machine, using Ruby 1.9.1p0:
Speed test: using to_i and to_s - decimal to binary...0.15s
Speed test: using to_i and to_s - binary to decimal...0.08s
Methods ran correctly? yes
Speed test: calculating it - decimal to binary...0.31s
Speed test: calculating it - binary to decimal...0.11s
Methods ran correctly? yes
Speed test: using sprintf - decimal to binary...0.16s
Speed test: using sprintf - binary to decimal...0.10s
Methods ran correctly? yes
In Ruby 1.8.6p368, everything is a little bit slower:
Speed test: using to_i and to_s - decimal to binary...0.22s
Speed test: using to_i and to_s - binary to decimal...0.30s
Methods ran correctly? yes
Speed test: calculating it - decimal to binary...0.33s
Speed test: calculating it - binary to decimal...0.30s
Methods ran correctly? yes
Speed test: using sprintf - decimal to binary...0.26s
Speed test: using sprintf - binary to decimal...0.32s
Methods ran correctly? yes
An explanation for this is, that the built-in methods can calculate the result using native C, which often beats ruby implementations.
.to_i / .to_s is better than sprintf is better than calculating it manually.
I have also tried JRuby (1.3.1 —1.9) and the manual conversion was even worse:
Speed test: using to_i and to_s - decimal to binary...0.15s
Speed test: using to_i and to_s - binary to decimal...0.26s
Methods ran correctly? yes
Speed test: calculating it - decimal to binary...0.34s
Speed test: calculating it - binary to decimal...0.41s
Methods ran correctly? yes
Speed test: using sprintf - decimal to binary...0.13s
Speed test: using sprintf - binary to decimal...0.33s
Methods ran correctly? yes
Without the 1.9 switch, most times improve (but are still worse at calculating it in Ruby):
Speed test: using to_i and to_s - decimal to binary...0.13s
Speed test: using to_i and to_s - binary to decimal...0.30s
Methods ran correctly? yes
Speed test: calculating it - decimal to binary...0.37s
Speed test: calculating it - binary to decimal...0.35s
Methods ran correctly? yes
Speed test: using sprintf - decimal to binary...0.13s
Speed test: using sprintf - binary to decimal...0.27s
Methods ran correctly? yes
Also notable: in JRuby, the winner is – sprintf
Here is the code I used for benchmarking:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
# # # # # # # # # # Test, which way to convert the base of an integer from 2 to 10 and vice versa, # is the most efficient one class ST attr_reader :dec_to_bin, :bin_to_dec, :correct # abstract methods def desc; end def speedtest_dec_to_bin; end def speedtest_bin_to_dec; end # stop time def timer(&proc) started_at = Time.now yield if block_given? stopped_at = Time.now res = stopped_at - started_at puts '%.2fs' % res res end # do the testing def speedtest( to_test = (42**42)**42*(42**42)**42 ) @cur = to_test print 'Speed test: ' << desc << ' - decimal to binary...' @dec_to_bin = speedtest_dec_to_bin print 'Speed test: ' << desc << ' - binary to decimal...' @bin_to_dec = speedtest_bin_to_dec puts 'Methods ran correctly? ' << ((@correct = (@cur == to_test)) ? 'yes' : 'no') end end # ruby's .to_i and .to_s functions class ST::To_i < ST def desc; 'using to_i and to_s'; end def speedtest_dec_to_bin timer{ @cur = @cur.to_s(2).to_i } end def speedtest_bin_to_dec timer{ @cur = @cur.to_s.to_i(2) } end end # calculate it class ST::Calc < ST def desc; 'calculating it'; end def speedtest_dec_to_bin timer{ res = [] while @cur != 0 # res << (@cur.odd? ? '1' : '0') # @cur >>= 1 new_cur = @cur >> 1 res << ((@cur == new_cur << 1) ? '0' : '1') @cur = new_cur end @cur = res.reverse.join.to_i } end def speedtest_bin_to_dec timer{ res = 0 cur_exp = 1 cur_missing_1 = 0 # counts the 0s for shifting @cur.to_s.reverse.each_byte{ |set| if set == 49 cur_exp <<= cur_missing_1 res += cur_exp cur_missing_1 = 1 else cur_missing_1 += 1 end } @cur = res } end end # using sprintf class ST::Sprintf < ST def desc; 'using sprintf'; end def speedtest_dec_to_bin timer{ @cur = ('%b' % @cur.to_s).to_i } end def speedtest_bin_to_dec timer{ @cur = ('%d' % ('0b' + @cur.to_s)).to_i } end end # for the lazy ones ;) def ST.demo jl1 = ST::To_i.new jl1.speedtest jl2 = ST::Calc.new jl2.speedtest jl3 = ST::Sprintf.new jl3.speedtest end # let's start ST.demo
If you know a way to improve the manual calculation, please let me know ;)