module Radix ## # Namespace for common bases defined as reusable constants. # module BASE # Array of chars 0 - 9 B10 = ('0'..'9').to_a # Array of chars 0 - 9 + X + E B12 = B10 + ['X', 'E'] # Array of chars 0 - 9 + A - F B16 = B10 + ('A'..'F').to_a # Array of chars 0 - 9 + A - Z B36 = B10 + ('A'..'Z').to_a # Array of chars 0 - 9 + A - Z + a - x B60 = B36 + ('a'..'x').to_a # Array of chars 0 - 9 + A - Z + a - z B62 = B36 + ('a'..'z').to_a # Array of chars 0 - 9 + a - f HEX = B10 + ('a'..'f').to_a end ## # Radix::Base is a functional model of a base system that can be used for # number conversions. # # Radix::Base is the original Radix API. But with the advent of v2.0 # and the new Integer and Float classes, it is essentially deprecated. # # @example Convert any base # b10 = Radix::Base.new(10) # b10.convert_base([100, 10], 256) # #=> [2,5,6,1,0] # # @example Convert to string notation upto base 62 # b10.convert("10", 62) #=> "62" # # @example Odd notations # b10 = Radix::Base.new(%w{Q W E R T Y U I O U}) # b10.convert("FF", 16) #=> "EYY" # # @!attribute [r] chars # @return [Array] The ASCII character set in use. # @!attribute [r] base # @return [Fixnum] The base level in use. # @!attribute [r] values # @example Testing base hash default values. # > test = Radix::Base.new(36) # > test.values["F"] # 15 # > test.values["5"] # 5 # > test.values["Y"] # 34 # > test.values["YY"] # nil # Fails because "YY" is not a key in the +values+ hash. # @return [Hash{String=>Fixnum}] # A hash of characters and their respective value. class Base ## # The characters for this base level. # # @return [Array] The ASCII character set in use. attr :chars ## # The base of this instance. # # @return [Fixnum] The base level in use. attr :base ## # Hash of characters and values. # # @return [Hash{String=>Fixnum}] # A hash of characters and their respective value. attr :values ## # New Radix using +chars+ representation. # # @param [Array, Numeric] chars # Array of string representation of number values of the base # or a Numeric of the Base level. # # @return [void] 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 a value of given radix_base to that of the base instance. # # @param [String, Numeric, Array] number # The value in "radix_base" context. # # @param [Radix::Base, Numeric] radix_base # Numeric for the radix or instance of Radix::Base. # # @example Convert Testing (Binary, Decimal, Hex) # > b = Radix::Base.new(2) # > d = Radix::Base.new(10) # > h = Radix::Base.new(16) # > d.convert("A", h) # "10" # > h.convert("A", d) # TypeError # > h.convert(10, d) # "A" # > h.convert(10, 10) # "A" # > b.convert(10, d) # "1010" # > b.convert(10, h) # "10000" # # @return [String] representation of "number" in self.base level. 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 Fixnum's. Indexes of # the array correspond to values for each column of the number in from_base # # @param [Array] digits # Array of values for each digit of source base. # # @param [Fixnum] from_base # Source Base # # @param [Fixnum] to_base # Destination Base # # @return [String] The value of digits in from_base converted as to_base. 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. # # @param [String] byte_string # String value in this base. # # @return [String] Encoded string from this Base. 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. # # @param [String] encoded # Encoded string from this Base. # # @return [String] Decoded string of value from this base. def decode(encoded) digits = encoded.split(//).map{ |c| @values[c] } Radix.convert_base(digits, base, 256).pack("C*") end end ## # Convert a number of from_base as to_base. # # @param [String, Numeric, Array] number # The value in context of "radix_base". # # @param [Fixnum, Radix::Base] from_base # Source Base # # @param [Fixnum, Radix::Base] to_base # Destination Base # # @return [String] # The value of `digits` in `from_base` converted into `to_base`. 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 Fixnum's. Indexes of # the array correspond to values for each column of the number in from_base # # @param [Array] digits # Array of values for each digit of source base. # # @param [Fixnum] from_base # Source Base # # @param [Fixnum] to_base # Destination Base # # @return [String] The value of digits in from_base converted as to_base. 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