On 8/31/06, Phrogz <gavin / refinery.com> wrote:
> I'm writing code that needs to store an integer as a sequence of bytes.
> Array#pack works for 1-, 2-, and 4-byte integers, but I want to be able
> to support Bignums as well.
>
> I have working code below. I'm happy with it. However, I thought I'd
> pose the question to the list: how much faster can you make the below
> code run?
>
> # The natural log of 2 (for base-2 logarithms)
> Math::LOG2 = Math.log( 2 )

No need for these, I'm on a campaign to let integers be integers.

>
> class Integer
>   # Converts the integer into a string, where the value of each
> character
>   # is a byte in the bit representation of the integer.
>   # Low-order bytes appear first in the string.
>   #
>   # If the number of characters is not specified, the fewest number of
>   # characters that can represent the integer is used.
>   #
>   #
>   def to_bytestring( num_chars=nil )

replace these four lines:
>     unless num_chars
>       bits_needed = Math.log( self ) / Math::LOG2

By the way, this won't work for negative integers.  Dealing with
negative numbers provides some interesting questions which I'll ignore
for now.

>       num_chars = ( bits_needed / 8.0 ).ceil
>     end

with

        raise RangeError.new("Can't convert negative integer to bytestring")
        num_chars ||= self.size

>     if pack_code = { 1=>'C', 2=>'S', 4=>'L' }[ num_chars ]
>       [ self ].pack( pack_code )
>     else
I might try getting rid of the multiplication
>       (0...(num_chars)).map{ |i|
>         (( self >> i*8 ) & 0xFF ).chr
>       }.join
>     end

With something like:

        result = []
        i = self
        until i == 0
              result << (i & 0xFF).chr
               i >>=  8
       end

So following those ideas:
rick@frodo:/public/rubyscripts$ cat tobytes.rb
class Integer
        def to_bytestring(num_chars=nil)
                raise RangeError.new("Can't convert negative number to
bytestring") if self < 0
                num_chars ||= self.size
                mask = 0xFF << (8 * num_chars - 1)
                while (self & mask) == 0
                        mask >>= 8
                        num_chars -= 1
                end

                result = []
                bits_left = self
                until bits_left == 0
                        result << (bits_left & 0xFF).chr
                        bits_left >>= 8
                end
                result.join
        end

        # Creates an integer from a byte string.
        # See Integer#to_bytestring for more information.
        def self.from_bytestring( str )
                val = 0
                index = 0
                str.each_byte{ |b|
                        val += b << 8 * index
                        index += 1
                }
                val
        end

end

[
        0x48,
        0x6948,
        0x646c726f57206f6c6c6548,
        0x217962755220666f207463657073612074656577732061207369206d756e676942
].each{ |i| puts i, i.to_bytestring, '' }


rick@frodo:/public/rubyscripts$ ruby tobytes.rb
72
H

26952
Hi

121404708493354166158910792
Hello World

3876042760240808297111079005855559646422331404814505579794973210389374306838850
Bignum is a sweet aspect of Ruby!



-- 
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/