module Radix # Collection of base encodings. module BASE B10 = ('0'..'9').to_a B12 = B10 + ['X', 'E'] B16 = B10 + ('A'..'F').to_a B36 = B10 + ('A'..'Z').to_a B60 = B36 + ('a'..'x').to_a B62 = B36 + ('a'..'z').to_a # Like BASE16 but encodes with lowercase letters. HEX = B10 + ('a'..'f').to_a end # Radix::Base provides the means of converting to and from any base. # # b10 = Radix::Base.new(10) # b10.convert_base([100, 10], 256) # #=> [2,5,6,1,0] # # And it can handle any notation upto base 62. # # b10.convert("10", 62) #=> "62" # # And the notations need not be in ASCII order --odd notations # can be used. # # b10 = Radix::Base.new(%w{Q W E R T Y U I O U}) # b10.convert("FF", 16) #=> "EYY" # # NOTE: Radix::Base is the original Radix API. But with the advent of v2.0 # and the new Integer and Float classes, it is outmoded. For now it is here # for backward compatibility. In a future version it may be deprecated, or # reworked to serve as the backbone of the other classes. # class Base attr :chars attr :base attr :values # New Radix using +chars+ representation. def initialize(chars=BASE::B62) if ::Numeric === chars chars = BASE::B62[0...chars] end @chars = chars.map{ |c| c.to_s } @base = @chars.size @values = Hash[*(0...@base).map { |i| [ @chars[i], i ] }.flatten] end # Convert an *encoded* +number+ of given +base+ to the Base's radix. def convert(number, radix_base) radix_base = Radix::Base.new(radix_base) unless Radix::Base === radix_base case number when ::String, ::Numeric digits = number.to_s.split(//) else digits = number end # decode the digits digits = digits.map{ |digit| radix_base.values[digit] } # THINK: Is best way to check for base out of bounds? raise TypeError if digits.any?{ |digit| digit.nil? } digits = Radix.convert_base(digits, radix_base.base, base) digits = digits.map{ |digit| chars[digit] } digits.join end # Convert any base to any other base, using array of +digits+. def convert_base(digits, from_base, to_base) bignum = 0 digits.each { |digit| bignum = bignum * from_base + digit } converted = [] until bignum.zero? bignum, digit = bignum.divmod(to_base) converted.push(digit) end converted << 0 if converted.empty? # THINK: correct? converted.reverse end # Encode a string in the radix. def encode(byte_string) digits = byte_string.unpack("C*") digits = Radix.convert_base(digits, 256, base) digits.map{ |d| @chars[d] }.join end # Decode a string that was previously encoded in the radix. def decode(encoded) digits = encoded.split(//).map{ |c| @values[c] } Radix.convert_base(digits, base, 256).pack("C*") end end # Convert number from it's given base to antoher base. # Do a standard conversion upto base 62. def self.convert(number, from_base, to_base) from_base = Radix::Base.new(from_base) unless Radix::Base === from_base to_base = Radix::Base.new(to_base) unless Radix::Base === to_base to_base.convert(number, from_base) end # Convert any base to any other base, using array of +digits+. def self.convert_base(digits, from_base, to_base) bignum = 0 digits.each { |digit| bignum = bignum * from_base + digit } converted = [] until bignum.zero? bignum, digit = bignum.divmod(to_base) converted.push(digit) end converted << 0 if converted.empty? # THINK: correct? converted.reverse end end