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 =~ /(\+\/-|±)/
+ 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