require 'radix/numeric' module Radix ## # Advanced float class for Radix conversions and mathematical operations # with other bases. # # @todo Make fully immutable. After that we can catch @digits and # the library should be a good bit faster. # # @!attribute [r] value # @return [Float] Float's decimal value. # @!attribute [r] base # @return [Fixnum] The base level of Float instance. # @!attribute [r] code # @return [Array<String>, nil] Substitution chars or nil if default. class Float < Numeric ## # Internal floating point value. # # @return [Float] Float's decimal value. attr :value ## # Base of the number. # # @return [Fixnum] The base level of Float instance. attr :base ## # Base encoding table. # # @return [Array<String>, nil] Substitution chars or nil if default. attr :code private ## # Starts a new instance of the Radix::Float class. # # @param [Radix::Numeric, Numeric, Array, String] value # The value of the new integer in context of base. # # @param [Fixnum, Array<String>] base # The base context in which value is determined. Can be an array # of characters to use in place of default. # # @return [void] def initialize(value, base=10) @value = parse_value(value, base) @base, @code = parse_base(base) end ## # Takes a Radix::Numeric, String or array and returns the decimal float # value for storage in @value. # # @param [Radix::Numeric, Numeric, String, Array<Numeric, String>] value # The value of the integer in base context. # # @param [Fixnum, Array<String>] base # The context base of value. # # @return [Float] Float value of Integer. def parse_value(value, base) case value when Float, Integer # Radix parse_numeric(value.to_f, base) when ::Array parse_array(value, base) when ::String parse_string(value, base) when ::Numeric parse_numeric(value.to_f, base) end end public ## # Makes this Radix::Float a ruby Integer. # # @return [Integer] Base(10) value as Integer. def to_i to_f.to_i end alias_method :to_int, :to_i ## # Makes this Radix::Float a ruby float. # # @return [Float] Base(10) value as Float. def to_f value.to_f end ## # Makes this Radix::Float an array using code if defined. Returns an # array using default chars otherwise. # # @param [Fixnum] base # Desired base. # # @return [Array<Fixnum, String>] Current base encoded array. def to_a(base=nil) if base convert(base).digits_encoded else digits_encoded end end ## # Creates an encoded string in passed base, with passed digit divider. # # @note For base 10 or less does not use a divider unless specified. # # @param [Fixnum, Array<String>] base # Desired base. # # @param [String] divider # Desired divider character(s). # # @return [String] Encoded string with specified divider. def to_s(base=nil, divider=nil) divider = divider.to_s if divider if base convert(base).to_s(nil, divider) else if code digits_encoded.join(divider) else if @base > 10 digits.join(divider || DIVIDER) else digits.join(divider) end end end end ## # Creates a string representation of self. # # @return [String] String rep of self.digits and @base. def inspect "#{digits.join(' ')} (#{base})" end ## # Returns an array representation of each column's value in decimal chars. # # @return [Array<String, Fixnum>] # Values per column of @base as array. Prepended with "-" if negative. def digits i, f = base_conversion(value, base) if negative? ['-'] + i + [DOT] + f else i + [DOT] + f end end ## # Returns digits, or coded version of digits if @code. # # @return [Array<String, Fixnum>] # Values per column of @base as array. Prepended with "-" if negative. # Or encoded version if @code is defined. def digits_encoded base_encode(digits) end ## # Returns true if the number is negative? # # @return [Boolean] True if float value is < 0. def negative? value < 0 end ## # Creates a new Radix::Float of same value in different base. # # @return [Radix::Float] New float of same value in different base. def convert(new_base) self.class.new(value, new_base) end ## # Power exponentional operation. # # @param [#to_f] other # The exponent by which to raise Float. # # @return [Radix::Float] Result of exponential operation. def **(other) operation(:**, other) end ## # Modulo binary operation. # # @param [#to_f] other # # @return [Radix::Float] Modulo result of division operation. def %(other) operation(:%, other) end alias_method :modulo, :% ## # Returns the absolute value of self in @base. # # @return [Radix::Float] Absolute of @value. def abs self.class.new(value.abs, base) end ## # Returns the largest integer greater than or equal to self as a # Radix::Float. # # @return [Radix::Float] def ceil self.class.new(value.ceil, base) end ## # Returns the smallest integer less than or equal to self as a # Radix::Float. # # @return [Radix::Float] def floor self.class.new(value.floor, base) end ## # Returns a new Radix::Float instance of same base, rounded to the nearest # whole integer. # # @example Rounding Radix Float # > round_test = Radix::Float.new(123.03, 16) # 7 11 . 0 7 10 14 1 4 7 10 14 1 (16) # > round_test.value # 123.03 # > round_test.round # 7 11 . 0 (16) # > round_test.round.value # 123.0 # > round_test += 0.5 # 7 11 . 8 7 10 14 1 4 7 10 14 1 (16) # > round_test.value # 123.53 # > round_test.round # 7 12 . 0 (16) # > round_test.round.value # 124.0 # # @return [Radix::Float] New Instance def round return self.class.new((value + 0.5).floor, base) if self > 0.0 return self.class.new((value - 0.5).ceil, base) if self < 0.0 return self.class.new(0, base) end ## # Strict equality requires same class as well as value. # # @param [Object] num # Object to compare. # # @return [Boolean] True if class and value are equal. def eql?(num) self.class.equal?(num.class) && self == num end ## # Simple equality requires equal values only. # # @param [Numeric] other # Any Numeric instance. # # @return [Boolean] True if values are equal. def ==(other) case other when Float, Integer # Radix value == other.value else value == other end end ## # Comparitive binary operation. Very useful for sorting methods. # # @param [#to_f] other # The object to compare value against. # # @example Comparison testing # > lower = Radix::Float.new(123.00,10) # 1 2 3 . 0 (10) # > higher = Radix::Float.new(456.00,16) # 1 12 8 . 0 (16) # > lower <=> higher # -1 # > lower <=> 123 # 0 # > lower <=> "123" # 0 # > higher <=> lower # 1 # # @return [Fixnum] Returns -1 for less than, 0 for equal or 1 for more than. def <=>(other) to_f <=> other.to_f end # #def infinite? # digits[0,2] == [0, DOT] #end # #def finite? # !infinite #end # #def nan? # digits[-2,2] == [DOT, 0] #end ## # Create a new Radix::Float from value in Base-10. # # @param [Numeric, Array, String] other # The value of the new integer in base-10. # # @return [Array<Radix::Float>] An array of the new Float object and self. def coerce(other) [Radix::Float.new(other), self] end private ## # Perform passed arithmetic operation. # # @param [#to_f] other # # @return [Radix::Float] Result of binary operation in @base. def operation(op, other) a = self.to_f b = other.to_f x = a.__send__(op, b) Radix::Float.new(x, base) end ## # Returns two arrays. The integer part and the fractional part of the Float # value in param base. # # @param [Float] value Float's decimal value. # @param [Fixnum] base The base level of Float instance. # @param [Fixnum] prec The # of places to extend F-part. # # @return [Array<(Array[Fixnum], Array[Fixnum])>] def base_conversion(value, base, prec=10) #if value < 0 # @negative, value = true, value.abs #end value = value.to_f.abs i, f = split_float(value) a = [] while i > 0 i, r = i.divmod(base) a << r end #c = [] # f-cache p = prec b = [] while !f.zero? k = (f * base) r, f = split_float(k) #c.include?(f) ? break : c << f break if p == 0; p -= 1 b << r end a << 0 if a.empty? b << 0 if b.empty? [a.reverse, b] end ## # Convert array of values of a different base to decimal as called by # parse_array. # # @param [Array<Numeric, String>] digits Representation of Base values. # @param [Fixnum, Array<String>] base The base to convert from. # # @return [Float] The digits of base converted to decimal. def decimal(digits, base) i, f = split_digits(digits) e = i.size - 1 v = 0 (i + f).each do |n| v += n * base**e e -= 1 end v end ## # Returns the I-Part and F-Part of the passed value as arrays of fixnums. # # @param [Array<Numeric, String>] value # The array of decimal values per column of @base. # # @return [Array<(Array<Fixnum>, Array<Fixnum>)>] def split_digits(value) if d = value.index(DOT) || value.index('.') i, f = value[0...d], value[d+1..-1] else i, f = value, [0] end i.map!{ |x| x.to_i } f.map!{ |x| x.to_i } return i, f end ## # Returns an array of Integer and Float portions of the Radix::Float # # @param [Radix::Float] float Float value to split # # @return [Array<(Integer, Float)>] def split_float(float) i, f = float.to_s.split('.') return i.to_i, ('0.'+f).to_f end end end