lib/ruby-units.rb in ruby-units-0.3.9 vs lib/ruby-units.rb in ruby-units-1.0.0

- old
+ new

@@ -1,10 +1,10 @@ require 'mathn' require 'rational' require 'date' require 'parsedate' -# = Ruby Units 0.3.9 +# = Ruby Units 1.0.0 # # Copyright 2006 by Kevin C. Olbrich, Ph.D. # # See http://rubyforge.org/ruby-units/ # @@ -38,11 +38,11 @@ # end # Unit.setup class Unit < Numeric require 'units' # pre-generate hashes from unit definitions for performance. - VERSION = '0.3.9' + VERSION = '1.0.0' @@USER_DEFINITIONS = {} @@PREFIX_VALUES = {} @@PREFIX_MAP = {} @@UNIT_MAP = {} @@UNIT_VALUES = {} @@ -52,15 +52,19 @@ UNITY_ARRAY= [UNITY] FEET_INCH_REGEX = /(\d+)\s*(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inches)/ TIME_REGEX = /(\d+)*:(\d+)*:*(\d+)*[:,]*(\d+)*/ LBS_OZ_REGEX = /(\d+)\s*(?:#|lbs|pounds)+[\s,]*(\d+)\s*(?:oz|ounces)/ SCI_NUMBER = %r{([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)} + RATIONAL_NUMBER = /(\d+)\/(\d+)/ + COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/ NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/ UNIT_STRING_REGEX = /#{SCI_NUMBER}*\s*([^\/]*)\/*(.+)*/ TOP_REGEX = /([^ \*]+)(?:\^|\*\*)([\d-]+)/ BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/ - + UNCERTAIN_REGEX = /#{SCI_NUMBER}\s*\+\/-\s*#{SCI_NUMBER}\s(.+)/ + COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/ + RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(.+)?/ KELVIN = ['<kelvin>'] FARENHEIT = ['<farenheit>'] RANKINE = ['<rankine>'] CELCIUS = ['<celcius>'] @@ -186,11 +190,11 @@ if options.size == 2 begin cached = @@cached_units[options[1]] * options[0] copy(cached) rescue - initialize("#{options[0]} #{options[1]}") + initialize("#{options[0]} #{(options[1].units rescue options[1])}") end return end if options.size == 3 begin @@ -231,11 +235,11 @@ self.update_base_scalar self.replace_temperature unary_unit = self.units || "" opt_units = options[0].scan(NUMBER_REGEX)[0][1] if String === options[0] - unless @@cached_units.keys.include?(opt_units) || (opt_units =~ /(temp|deg(C|K|R|F))|(pounds|lbs[ ,]\d+ ounces|oz)|('\d+")|(ft|feet[ ,]\d+ in|inch|inches)|%|(#{TIME_REGEX})/) + unless @@cached_units.keys.include?(opt_units) || (opt_units =~ /(temp|deg(C|K|R|F))|(pounds|lbs[ ,]\d+ ounces|oz)|('\d+")|(ft|feet[ ,]\d+ in|inch|inches)|%|(#{TIME_REGEX})|i\s?(.+)?/) @@cached_units[opt_units] = (self.scalar == 1 ? self : opt_units.unit) if opt_units && !opt_units.empty? end unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /(temp|deg)(C|K|R|F)/) then @@cached_units[unary_unit] = (self.scalar == 1 ? self : unary_unit.unit) end @@ -343,11 +347,16 @@ out = $2 ? self.to($2).to_s($1) : "#{($1 || '%g') % @scalar || 0} #{self.units}".strip rescue #if that is malformed, try a time string out = (Time.gm(0) + self).strftime(target_units) end else - out = "#{'%g' % @scalar} #{self.units}".strip + out = case @scalar + when Rational : + "#{@scalar} #{self.units}" + else + "#{'%g' % @scalar} #{self.units}" + end.strip end @output = {target_units => out} return out end end @@ -509,10 +518,17 @@ else raise ArgumentError, "Invalid Exponent" end end + def real + return Unit.new(self.scalar.real,self.units) + end + + def imag + return Unit.new(self.scalar.imag, self.units) + end # returns the unit raised to the n-th power. Integers only def power(n) raise ArgumentError, "Can only use Integer exponenents" unless Integer === n return self if n == 1 @@ -548,11 +564,11 @@ for item in @denominator.uniq do x = den.find_all {|i| i==item}.size r = ((x/n)*(n-1)).to_int r.times {|x| den.delete_at(den.index(item))} end - q = @scalar**(1/n) + q = @scalar < 0 ? (-1)**Rational(1,n) * (@scalar.abs)**Rational(1,n) : @scalar**Rational(1,n) Unit.new(:scalar=>q,:numerator=>num,:denominator=>den) end # returns inverse of Unit (1/unit) def inverse @@ -641,13 +657,20 @@ alias :convert_to :to # converts the unit back to a float if it is unitless. Otherwise raises an exception def to_f return @scalar.to_f if self.unitless? - raise RuntimeError, "Can't convert to float unless unitless. Use Unit#scalar" + raise RuntimeError, "Can't convert to Float unless unitless. Use Unit#scalar" end + # converts the unit back to a complex if it is unitless. Otherwise raises an exception + + def to_c + return Complex(@scalar) if self.unitless? + raise RuntimeError, "Can't convert to Complex unless unitless. Use Unit#scalar" + end + # returns the 'unit' part of the Unit object without the scalar def units return "" if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY return @unit_name unless @unit_name.nil? output_n = [] @@ -686,11 +709,12 @@ end # negates the scalar of the Unit def -@ return -@scalar if self.unitless? - Unit.new(-@scalar,@numerator,@denominator) + #Unit.new(-@scalar,@numerator,@denominator) + -1 * self.dup end # returns abs of scalar, without the units def abs return @scalar.abs @@ -705,21 +729,21 @@ return @scalar.floor if self.unitless? Unit.new(@scalar.floor, @numerator, @denominator) end # if unitless, returns an int, otherwise raises an error - def to_int + def to_i return @scalar.to_int if self.unitless? - raise RuntimeError, 'Cannot convert to Integer, use Unit#scalar' + raise RuntimeError, 'Cannot convert to Integer unless unitless' end + alias :to_int :to_i # Tries to make a Time object from current unit. Assumes the current unit hold the duration in seconds from the epoch. def to_time Time.at(self) end alias :time :to_time - alias :to_i :to_int def truncate return @scalar.truncate if self.unitless? Unit.new(@scalar.truncate, @numerator, @denominator) end @@ -953,15 +977,36 @@ unit_string = passed_unit_string.dup if unit_string =~ /\$\s*(#{NUMBER_REGEX})/ unit_string = "#{$1} USD" end + unit_string.gsub!(/%/,'percent') unit_string.gsub!(/'/,'feet') unit_string.gsub!(/"/,'inch') unit_string.gsub!(/#/,'pound') - + if defined?(Uncertain) && unit_string =~ /(\+\/-|&plusmn;)/ + value, uncertainty, unit_s = unit_string.scan(UNCERTAIN_REGEX)[0] + result = unit_s.unit * Uncertain(value.to_f,uncertainty.to_f) + copy(result) + return + end + + if defined?(Complex) && unit_string =~ COMPLEX_NUMBER + real, imaginary, unit_s = unit_string.scan(COMPLEX_REGEX)[0] + result = Unit(unit_s || '1') * Complex(real.to_f,imaginary.to_f) + copy(result) + return + end + + if defined?(Rational) && unit_string =~ RATIONAL_NUMBER + numerator, denominator, unit_s = unit_string.scan(RATIONAL_REGEX)[0] + result = Unit(unit_s || '1') * Rational(numerator.to_i,denominator.to_i) + copy(result) + return + end + unit_string =~ NUMBER_REGEX unit = @@cached_units[$2] mult = ($1.empty? ? 1.0 : $1.to_f) rescue 1.0 if unit copy(unit) @@ -1210,11 +1255,15 @@ self.to_datetime(options) rescue self.to_time(options) end end -# Allow time objects to use +# +# Time math is handled slightly differently. The difference is considered to be an exact duration if +# the subtracted value is in hours, minutes, or seconds. It is rounded to the nearest day if the offset +# is in years, decades, or centuries. This leads to less precise values, but ones that match the +# calendar better. class Time class << self alias unit_time_at at end @@ -1261,10 +1310,11 @@ def self.in(duration) Time.now + duration.to_unit end alias :unit_sub :- + def -(other) case other when Unit: other = other.to('d').round.to('s') if ['y', 'decade', 'century'].include? other.units begin @@ -1278,65 +1328,74 @@ unit_sub(other) end end end +# Math will convert unit objects to radians and then attempt to use the value for +# trigonometric functions. module Math + alias unit_sqrt sqrt + def sqrt(n) + Unit === n ? n**(1/2) : unit_sqrt(n) + end + alias unit_sin sin - def sin(n) - if Unit === n - unit_sin(n.to('radian').scalar) - else - unit_sin(n) - end - end + def sin(n) + Unit === n ? unit_sin(n.to('radian').scalar) : unit_sin(n) + end - alias unit_cos cos - def cos(n) - if Unit === n - unit_cos(n.to('radian').scalar) - else - unit_cos(n) - end - end + alias unit_cos cos + def cos(n) + Unit === n ? unit_cos(n.to('radian').scalar) : unit_cos(n) + end - alias unit_sinh sinh - def sinh(n) - if Unit === n - unit_sinh(n.to('radian').scalar) - else - unit_sinh(n) - end - end + alias unit_sinh sinh + def sinh(n) + Unit === n ? unit_sinh(n.to('radian').scalar) : unit_sinh(n) + end - alias unit_cosh cosh - def cosh(n) - if Unit === n - unit_cosh(n.to('radian').scalar) - else - unit_cosh(n) - end - end + alias unit_cosh cosh + def cosh(n) + Unit === n ? unit_cosh(n.to('radian').scalar) : unit_cosh(n) + end - alias unit_tan tan - def tan(n) - if Unit === n - unit_tan(n.to('radian').scalar) - else - unit_tan(n) - end - end + alias unit_tan tan + def tan(n) + Unit === n ? unit_tan(n.to('radian').scalar) : unit_tan(n) + end - alias unit_tanh tanh - def tanh(n) - if Unit === n - unit_tanh(n.to('radian').scalar) - else - unit_tanh(n) - end - end + alias unit_tanh tanh + def tanh(n) + Unit === n ? unit_tanh(n.to('radian').scalar) : unit_tanh(n) + end + alias unit_hypot hypot + # Convert parameters to consistent units and perform the function + def hypot(x,y) + if Unit === x && Unit === y + (x**2 + y**2)**(1/2) + else + unit_hypot(x,y) + end + end + + alias unit_atan2 atan2 + def atan2(x,y) + case + when (Unit === x && Unit === y) && (x !~ y) + raise ArgumentError, "Incompatible Units" + when (Unit === x && Unit === y) && (x =~ y) + unit_atan2(x.base_scalar, y.base_scalar) + else + unit_atan2(x,y) + end + end + + module_function :unit_hypot + module_function :hypot + module_function :unit_sqrt + module_function :sqrt module_function :unit_sin module_function :sin module_function :unit_cos module_function :cos module_function :unit_sinh @@ -1345,8 +1404,11 @@ module_function :cosh module_function :unit_tan module_function :tan module_function :unit_tanh module_function :tanh + module_function :unit_atan2 + module_function :atan2 + end Unit.setup