lib/ruby_units/ruby-units.rb in ruby-units-1.0.2 vs lib/ruby_units/ruby-units.rb in ruby-units-1.1.0
- old
+ new
@@ -1,22 +1,10 @@
require 'mathn'
require 'rational'
require 'date'
require 'parsedate'
-
-=begin
-require 'math'
-
-require 'object_class'
-require 'array_class'
-require 'string_class'
-require 'date_class'
-require 'time_class'
-require 'numeric_class'
-=end
-
# = Ruby Units
#
# Copyright 2006 by Kevin C. Olbrich, Ph.D.
#
# See http://rubyforge.org/ruby-units/
@@ -50,18 +38,18 @@
# }
# end
# Unit.setup
class Unit < Numeric
# pre-generate hashes from unit definitions for performance.
- VERSION = '1.0.2'
+ VERSION = '1.1.0'
@@USER_DEFINITIONS = {}
@@PREFIX_VALUES = {}
@@PREFIX_MAP = {}
@@UNIT_MAP = {}
@@UNIT_VALUES = {}
@@OUTPUT_MAP = {}
- @@BASE_UNITS = ['<meter>','<kilogram>','<second>','<mole>', '<farad>', '<ampere>','<radian>','<kelvin>','<byte>','<dollar>','<candela>','<each>','<steradian>','<decibel>']
+ @@BASE_UNITS = ['<meter>','<kilogram>','<second>','<mole>', '<farad>', '<ampere>','<radian>','<kelvin>','<temp-K>','<byte>','<dollar>','<candela>','<each>','<steradian>','<decibel>']
UNITY = '<1>'
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)/
@@ -74,13 +62,13 @@
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>']
+ FAHRENHEIT = ['<fahrenheit>']
RANKINE = ['<rankine>']
- CELCIUS = ['<celcius>']
+ CELSIUS = ['<celsius>']
SIGNATURE_VECTOR = [:length, :time, :temperature, :mass, :current, :substance, :luminosity, :currency, :memory, :angle, :capacitance]
@@KINDS = {
-312058=>:resistance,
-312038=>:inductance,
@@ -222,11 +210,10 @@
initialize("#{options[0]} #{options[1]}/#{options[2]}")
end
return
end
-
case options[0]
when Hash:
@scalar = options[0][:scalar] || 1
@numerator = options[0][:numerator] || UNITY_ARRAY
@denominator = options[0][:denominator] || UNITY_ARRAY
@@ -249,11 +236,12 @@
when String: parse(options[0])
else
raise ArgumentError, "Invalid Unit Format"
end
self.update_base_scalar
- self.replace_temperature
+ raise ArgumentError, "Temperature out of range" if self.is_temperature? && self.base_scalar < 0
+
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})|i\s?(.+)?|±|\+\/-/)
@@cached_units[opt_units] = (self.scalar == 1 ? self : opt_units.unit) if opt_units && !opt_units.empty?
@@ -288,11 +276,11 @@
alias :unit :to_unit
# Returns 'true' if the Unit is represented in base units
def is_base?
return @is_base if defined? @is_base
- return @is_base=true if @signature == 400 && @numerator.size == 1 && @numerator[0] =~ /(celcius|kelvin|farenheit|rankine)/
+ return @is_base=true if @signature == 400 && self.numerator.size == 1 && self.denominator == UNITY_ARRAY && self.units =~ /(deg|temp)K/
n = @numerator + @denominator
for x in n.compact do
return @is_base=false unless x == UNITY || (@@BASE_UNITS.include?((x)))
end
return @is_base = true
@@ -300,13 +288,22 @@
# convert to base SI units
# results of the conversion are cached so subsequent calls to this will be fast
def to_base
return self if self.is_base?
- cached = @@base_unit_cache[self.units] * self.scalar rescue nil
+ if self.units =~ /\A(deg|temp)(C|F|K|C)\Z/
+ @signature = 400
+ base = case self.units
+ when /temp/ : self.to('tempK')
+ when /deg/ : self.to('degK')
+ end
+ return base
+ end
+
+ cached = ((@@base_unit_cache[self.units] * self.scalar) rescue nil)
return cached if cached
-
+
num = []
den = []
q = 1
for unit in @numerator.compact do
if @@PREFIX_VALUES[unit]
@@ -383,10 +380,20 @@
def inspect(option=nil)
return super() if option == :dump
self.to_s
end
+ def is_temperature?
+ return true if self.signature == 400 && self.units =~ /temp/
+ end
+
+ def temperature_scale
+ return nil unless self.is_temperature?
+ self.units =~ /temp(C|F|R|K)/
+ "deg#{$1}"
+ end
+
# returns true if no associated units
# false, even if the units are "unitless" like 'radians, each, etc'
def unitless?
(@numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY)
end
@@ -447,12 +454,22 @@
def +(other)
if Unit === other
case
when self.zero? : other.dup
when self =~ other :
- @q ||= @@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar
- Unit.new(:scalar=>(self.base_scalar + other.base_scalar)*@q, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
+ raise ArgumentError, "Cannot add two temperatures" if (self.is_temperature? && other.is_temperature?)
+ if [self, other].any? {|x| x.is_temperature?}
+ case self.is_temperature?
+ when true:
+ Unit.new(:scalar => (self.scalar + other.to(self.temperature_scale).scalar), :numerator => @numerator, :denominator=>@denominator, :signature => @signature)
+ else
+ Unit.new(:scalar => (other.scalar + self.to(other.temperature_scale).scalar), :numerator => other.numerator, :denominator=>other.denominator, :signature => other.signature)
+ end
+ else
+ @q ||= ((@@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar) rescue (self.units.unit.to_base.scalar))
+ Unit.new(:scalar=>(self.base_scalar + other.base_scalar)*@q, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
+ end
else
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
end
elsif Time === other
other + self
@@ -467,12 +484,21 @@
def -(other)
if Unit === other
case
when self.zero? : -other.dup
when self =~ other :
- @q ||= @@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar
- Unit.new(:scalar=>(self.base_scalar - other.base_scalar)*@q, :numerator=>@numerator, :denominator=>@denominator, :signature=>@signature)
+ case
+ when [self, other].all? {|x| x.is_temperature?} :
+ Unit.new(:scalar => (self.base_scalar - other.base_scalar), :numerator => KELVIN, :denominator => UNITY_ARRAY, :signature => @signature).to(self.temperature_scale)
+ when self.is_temperature? :
+ Unit.new(:scalar => (self.base_scalar - other.base_scalar), :numerator => ['<temp-K>'], :denominator => UNITY_ARRAY, :signature => @signature).to(self)
+ when other.is_temperature? :
+ raise ArgumentError, "Cannot subtract a temperature from a differential unit"
+ else
+ @q ||= ((@@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar) rescue (self.units.unit.scalar/self.units.unit.to_base.scalar))
+ Unit.new(:scalar=>(self.base_scalar - other.base_scalar)*@q, :numerator=>@numerator, :denominator=>@denominator, :signature=>@signature)
+ end
else
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
end
elsif Time === other
other - self
@@ -484,10 +510,11 @@
# Multiply two units.
def *(other)
case other
when Unit
+ raise ArgumentError, "Cannot multiply by temperatures" if [other,self].any? {|x| x.is_temperature?}
opts = Unit.eliminate_terms(@scalar*other.scalar, @numerator + other.numerator ,@denominator + other.denominator)
opts.merge!(:signature => @signature + other.signature)
Unit.new(opts)
when Numeric
Unit.new(:scalar=>@scalar*other, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
@@ -501,10 +528,11 @@
# Throws an exception if divisor is 0
def /(other)
case other
when Unit
raise ZeroDivisionError if other.zero?
+ raise ArgumentError, "Cannot divide with temperatures" if [other,self].any? {|x| x.is_temperature?}
opts = Unit.eliminate_terms(@scalar/other.scalar, @numerator + other.denominator ,@denominator + other.numerator)
opts.merge!(:signature=> @signature - other.signature)
Unit.new(opts)
when Numeric
raise ZeroDivisionError if other.zero?
@@ -522,10 +550,11 @@
# It should then convert the float to a rational and raise the unit by the numerator and root it by the denominator
# but, sadly, floats can't be converted to rationals.
#
# For now, if a rational is passed in, it will be used, otherwise we are stuck with integers and certain floats < 1
def **(other)
+ raise ArgumentError, "Cannot exponentiate a temperature" if self.is_temperature?
if Numeric === other
return Unit("1") if other.zero?
return self if other == 1
return self.inverse if other == -1
end
@@ -544,10 +573,11 @@
end
end
# returns the unit raised to the n-th power. Integers only
def power(n)
+ raise ArgumentError, "Cannot raise a temperature to a power" if self.is_temperature?
raise ArgumentError, "Can only use Integer exponenents" unless Integer === n
return self if n == 1
return Unit("1") if n == 0
return self.inverse if n == -1
if n > 0 then
@@ -558,10 +588,11 @@
end
# Calculates the n-th root of a unit, where n = (1..9)
# if n < 0, returns 1/unit^(1/n)
def root(n)
+ raise ArgumentError, "Cannot take the root of a temperature" if self.is_temperature?
raise ArgumentError, "Exponent must an Integer" unless Integer === n
raise ArgumentError, "0th root undefined" if n == 0
return self if n == 1
return self.root(n.abs).inverse if n < 0
@@ -600,57 +631,75 @@
# unit1 >>= unit2
# Throws an exception if the requested target units are incompatible with current Unit.
#
# Special handling for temperature conversions is supported. If the Unit object is converted
# from one temperature unit to another, the proper temperature offsets will be used.
- # Supports Kelvin, Celcius, Farenheit, and Rankine scales.
+ # Supports Kelvin, Celcius, fahrenheit, and Rankine scales.
#
# Note that if temperature is part of a compound unit, the temperature will be treated as a differential
# and the units will be scaled appropriately.
def to(other)
return self if other.nil?
return self if TrueClass === other
return self if FalseClass === other
- if (Unit === other && other.units =~ /temp(K|C|R|F)/) || (String === other && other =~ /temp(K|C|R|F)/)
- raise ArgumentError, "Receiver is not a temperature unit" unless self.signature==400
+ if (Unit === other && other.is_temperature?) || (String === other && other =~ /temp(K|C|R|F)/)
+ raise ArgumentError, "Receiver is not a temperature unit" unless self.signature == 400
start_unit = self.units
target_unit = other.units rescue other
+ unless @base_scalar
+ @base_scalar = case start_unit
+ when 'tempC' : @scalar + 273.15
+ when 'tempK' : @scalar
+ when 'tempF' : (@scalar+459.67)*(5.0/9.0)
+ when 'tempR' : @scalar*(5.0/9.0)
+ end
+ end
+ q= case target_unit
+ when 'tempC' : @base_scalar - 273.15
+ when 'tempK' : @base_scalar
+ when 'tempF' : @base_scalar * (9.0/5.0) - 459.67
+ when 'tempR' : @base_scalar * (9.0/5.0)
+ end
+
+=begin
q=case start_unit
- when 'degC':
+ when /\A(temp|deg)C\Z/:
case target_unit
when 'tempC' : @scalar
when 'tempK' : @scalar + 273.15
when 'tempF' : @scalar * (9.0/5.0) + 32.0
when 'tempR' : @scalar * (9.0/5.0) + 491.67
end
- when 'degK':
+ when /\A(temp|deg)K\Z/:
case target_unit
when 'tempC' : @scalar - 273.15
when 'tempK' : @scalar
when 'tempF' : @scalar * (9.0/5.0) - 459.67
when 'tempR' : @scalar * (9.0/5.0)
end
- when 'degF':
+ when /\A(temp|deg)F\Z/:
case target_unit
when 'tempC' : (@scalar-32)*(5.0/9.0)
when 'tempK' : (@scalar+459.67)*(5.0/9.0)
when 'tempF' : @scalar
when 'tempR' : @scalar + 459.67
end
- when 'degR':
+ when /\A(temp|deg)R\Z/:
case target_unit
when 'tempC' : @scalar*(5.0/9.0) -273.15
when 'tempK' : @scalar*(5.0/9.0)
when 'tempF' : @scalar - 459.67
when 'tempR' : @scalar
end
+
else
return self.to_base.to(other) unless self.is_base?
#raise ArgumentError, "Unknown temperature conversion requested #{self.numerator}"
end
- target_unit =~ /temp(C|K|F|R)/
- Unit.new("#{q} deg#{$1}")
+=end
+ #target_unit =~ /temp(C|K|F|R)/
+ Unit.new("#{q} #{target_unit}")
else
case other
when Unit:
return self if other.units == self.units
target = other
@@ -724,13 +773,13 @@
return out
end
# negates the scalar of the Unit
def -@
+ #raise ArgumentError, "Cannot negate an absolute temperature" if self.is_temperature? && ['degK','degR'].include?(self.temperature_scale)
return -@scalar if self.unitless?
- #Unit.new(-@scalar,@numerator,@denominator)
- -1 * self.dup
+ self.dup * -1
end
# returns abs of scalar, without the units
def abs
return @scalar.abs
@@ -765,13 +814,17 @@
end
# convert a duration to a DateTime. This will work so long as the duration is the duration from the zero date
# defined by DateTime
def to_datetime
- DateTime.new(self.to('d').scalar)
+ DateTime.new0(self.to('d').scalar)
end
+ def to_date
+ Date.new0(self.to('d').scalar)
+ end
+
def round
return @scalar.round if self.unitless?
Unit.new(@scalar.round, @numerator, @denominator)
end
@@ -889,23 +942,9 @@
n = SIGNATURE_VECTOR.index(r[2])
vector[n] = vector[n] - 1 if n
end
end
vector
- end
-
- def replace_temperature
- return self unless self.kind == :temperature && self.units =~ /temp(R|K|F|C)/
- un = $1
- @numerator = case un
- when 'R' : RANKINE
- when 'C' : CELCIUS
- when 'F' : FARENHEIT
- when 'K' : KELVIN
- end
- @unit_name = nil
- r= self.to("tempK")
- copy(r)
end
private
def initialize_copy(other)