lib/ruby-units.rb in ruby-units-0.2.1 vs lib/ruby-units.rb in ruby-units-0.2.2
- old
+ new
@@ -1,10 +1,10 @@
require 'mathn'
require 'rational'
require 'date'
require 'parsedate'
-# = Ruby Units 0.2.1
+# = Ruby Units 0.2.2
#
# Copyright 2006 by Kevin C. Olbrich, Ph.D.
#
# See http://rubyforge.org/ruby-units/
#
@@ -71,11 +71,10 @@
self.setup
include Comparable
attr_accessor :scalar, :numerator, :denominator, :signature, :base_scalar
-
def to_yaml_properties
%w{@scalar @numerator @denominator @signature @base_scalar}
end
# basically a copy of the basic to_yaml. Needed because otherwise it ends up coercing the object to a string
@@ -120,18 +119,19 @@
@denominator = ['<1>']
else
raise ArgumentError, "Invalid Unit Format"
end
self.update_base_scalar
+ self.replace_temperature
self.freeze
end
- def initialize_copy(other)
- @numerator = other.numerator.clone
- @denominator = other.denominator.clone
+ def to_unit
+ self
end
-
+ alias :unit :to_unit
+
# Returns 'true' if the Unit is represented in base units
def is_base?
return true if @signature == 400 && @numerator.size == 1 && @numerator[0] =~ /(celcius|kelvin|farenheit|rankine)/
n = @numerator + @denominator
n.compact.each do |x|
@@ -141,10 +141,11 @@
end
#convert to base SI units
def to_base
return self if self.is_base?
+# return self.to('degK') if self.units =~ /temp(C|K|F|R)/
num = []
den = []
q = @scalar
@numerator.compact.each do |unit|
if @@PREFIX_VALUES[Regexp.escape(unit)]
@@ -260,12 +261,13 @@
# Add two units together. Result is same units as receiver and scalar and base_scalar are updated appropriately
# throws an exception if the units are not compatible.
def +(other)
if Unit === other
if self =~ other then
- q = @scalar + other.to(self).scalar
- Unit.new(:scalar=>q, :numerator=>@numerator, :denominator=>@denominator)
+ a = self.to_base
+ b = other.to_base
+ Unit.new(:scalar=>(a.scalar + b.scalar), :numerator=>a.numerator, :denominator=>b.denominator).to(self)
else
raise ArgumentError, "Incompatible Units"
end
elsif Time === other
other + self
@@ -278,17 +280,18 @@
# Subtract two units. Result is same units as receiver and scalar and base_scalar are updated appropriately
# throws an exception if the units are not compatible.
def -(other)
if Unit === other
if self =~ other then
- q = @scalar - other.to(self).scalar
- Unit.new(:scalar=>q, :numerator=>@numerator, :denominator=>@denominator)
+ a = self.to_base
+ b = other.to_base
+ Unit.new(:scalar=>(a.scalar - b.scalar), :numerator=>a.numerator, :denominator=>b.denominator).to(self)
else
raise ArgumentError, "Incompatible Units"
end
elsif Time === other
- other + self
+ other - self
else
x,y = coerce(other)
x - y
end
end
@@ -409,46 +412,48 @@
# 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 String === other && other =~ /temp(K|C|R|F)/
+ 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
return self.to_base.to(other) unless self.is_base?
- #return self.to_base.to("tempF") if @numerator.size > 1 || @denominator != ['<1>']
- q=case
- when @numerator.include?('<celcius>'):
- case other
+ start_unit = self.units
+ target_unit = other.units rescue other
+ q=case start_unit
+ when 'degC':
+ 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 @numerator.include?( '<kelvin>'):
- case other
+ when 'degK':
+ 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 @numerator.include?( '<farenheit>'):
- case other
+ when 'degF':
+ 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 @numerator.include?( '<rankine>'):
- case other
+ when 'degR':
+ 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
raise ArgumentError, "Unknown temperature conversion requested #{self.numerator}"
end
+ target_unit =~ /temp(C|K|F|R)/
Unit.new("#{q} deg#{$1}")
else
case other
when Unit:
return self if other.units == self.units
@@ -466,88 +471,15 @@
y = one.inject(1) {|product,n| product*n} / two.inject(1) {|product,n| product*n}
q = @scalar * v/y
Unit.new(:scalar=>q, :numerator=>target.numerator, :denominator=>target.denominator)
end
end
-
- alias :>> :to
- # calculates the unit signature vector used by unit_signature
- def unit_signature_vector
- return self.to_base.unit_signature_vector unless self.is_base?
- result = self
- y = [:length, :time, :temperature, :mass, :current, :substance, :luminosity, :currency, :memory, :angle]
- vector = Array.new(y.size,0)
- y.each_with_index do |units,index|
- vector[index] = result.numerator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
- vector[index] -= result.denominator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
- end
- vector
- end
-
- # calculates the unit signature id for use in comparing compatible units and simplification
- # the signature is based on a simple classification of units and is based on the following publication
- #
- # Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering,
- # 21(8), Aug 1995, pp.651-661
- # doi://10.1109/32.403789
- # http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr.
- #
- def unit_signature
- vector = unit_signature_vector
- vector.each_with_index {|item,index| vector[index] = item * 20**index}
- @signature=vector.inject(0) {|sum,n| sum+n}
- end
-
+ alias :>> :to
+ alias :convert_to :to
+
# Eliminates terms in the passed numerator and denominator. Expands out prefixes and applies them to the
# scalar. Returns a hash that can be used to initialize a new Unit object.
- def self.eliminate_terms(q, n, d)
- num = n.clone
- den = d.clone
-
- num.delete_if {|v| v == '<1>'}
- den.delete_if {|v| v == '<1>'}
- combined = Hash.new(0)
-
- i = 0
- loop do
- break if i > num.size
- if @@PREFIX_VALUES.has_key? num[i]
- k = [num[i],num[i+1]]
- i += 2
- else
- k = num[i]
- i += 1
- end
- combined[k] += 1 unless k.nil? || k == '<1>'
- end
-
- j = 0
- loop do
- break if j > den.size
- if @@PREFIX_VALUES.has_key? den[j]
- k = [den[j],den[j+1]]
- j += 2
- else
- k = den[j]
- j += 1
- end
- combined[k] -= 1 unless k.nil? || k == '<1>'
- end
-
- num = []
- den = []
- combined.each do |key,value|
- case
- when value > 0 : value.times {num << key}
- when value < 0 : value.abs.times {den << key}
- end
- end
- num = ["<1>"] if num.empty?
- den = ["<1>"] if den.empty?
- {:scalar=>q, :numerator=>num.flatten.compact, :denominator=>den.flatten.compact}
- end
-
# converts the unit back to a float if it is unitless
def to_f
return @scalar.to_f if self.unitless?
raise RuntimeError, "Can't convert to float unless unitless. Use Unit#scalar"
end
@@ -642,10 +574,11 @@
Time.local(*ParseDate.parsedate(time_point))-self
else
time_point - self
end
end
+ alias :before_now :before
# 'min'.since(time)
def since(time_point = ::Time.now)
case time_point
when Time: (Time.now - time_point).unit('s').to(self)
@@ -665,32 +598,32 @@
else
raise ArgumentError, "Must specify a Time" unless time_point
end
end
- # '5 min'.from_now
- def from_now
- self.from
- end
-
# '5 min'.from(time)
def from(time_point = ::Time.now)
raise ArgumentError, "Must specify a Time" unless time_point
if String === time_point
Time.local(*ParseDate.parsedate(time_point))+self
else
time_point + self
end
end
alias :after :from
+ alias :from_now :from
def succ
raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i
q = @scalar.to_i.succ
Unit.new([q, @numerator, @denominator])
end
+ # Protected and Private Functions that should only be called from this class
+ protected
+
+
def update_base_scalar
if self.is_base?
@base_scalar = @scalar
@signature = unit_signature
else
@@ -698,20 +631,120 @@
@base_scalar = base.scalar
@signature = base.signature
end
end
+
def coerce(other)
case other
when Unit : [other, self]
else
[Unit.new(other), self]
end
end
+
+ # calculates the unit signature vector used by unit_signature
+ def unit_signature_vector
+ return self.to_base.unit_signature_vector unless self.is_base?
+ result = self
+ y = [:length, :time, :temperature, :mass, :current, :substance, :luminosity, :currency, :memory, :angle]
+ vector = Array.new(y.size,0)
+ y.each_with_index do |units,index|
+ vector[index] = result.numerator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
+ vector[index] -= result.denominator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
+ end
+ vector
+ end
+
+ def replace_temperature
+ return self unless self.signature == 400 && self.units =~ /temp(R|K|F|C)/
+ un = $1
+ puts "self:#{self} #{un}"
+ target = self.units
+ @numerator = case un
+ when 'R' : ['<rankine>']
+ when 'C' : ['<celcius>']
+ when 'F' : ['<farenheit>']
+ when 'K' : ['<kelvin>']
+ end
+ r= self.to("tempK")
+ @numerator = r.numerator
+ @denominator = r.denominator
+ @scalar = r.scalar
+ end
+
+
private
+ def initialize_copy(other)
+ @numerator = other.numerator.clone
+ @denominator = other.denominator.clone
+ end
+
+ # calculates the unit signature id for use in comparing compatible units and simplification
+ # the signature is based on a simple classification of units and is based on the following publication
+ #
+ # Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering,
+ # 21(8), Aug 1995, pp.651-661
+ # doi://10.1109/32.403789
+ # http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr.
+ #
+ def unit_signature
+ vector = unit_signature_vector
+ vector.each_with_index {|item,index| vector[index] = item * 20**index}
+ @signature=vector.inject(0) {|sum,n| sum+n}
+ end
+
+ def self.eliminate_terms(q, n, d)
+ num = n.clone
+ den = d.clone
+
+ num.delete_if {|v| v == '<1>'}
+ den.delete_if {|v| v == '<1>'}
+ combined = Hash.new(0)
+
+ i = 0
+ loop do
+ break if i > num.size
+ if @@PREFIX_VALUES.has_key? num[i]
+ k = [num[i],num[i+1]]
+ i += 2
+ else
+ k = num[i]
+ i += 1
+ end
+ combined[k] += 1 unless k.nil? || k == '<1>'
+ end
+
+ j = 0
+ loop do
+ break if j > den.size
+ if @@PREFIX_VALUES.has_key? den[j]
+ k = [den[j],den[j+1]]
+ j += 2
+ else
+ k = den[j]
+ j += 1
+ end
+ combined[k] -= 1 unless k.nil? || k == '<1>'
+ end
+
+ num = []
+ den = []
+ combined.each do |key,value|
+ case
+ when value > 0 : value.times {num << key}
+ when value < 0 : value.abs.times {den << key}
+ end
+ end
+ num = ["<1>"] if num.empty?
+ den = ["<1>"] if den.empty?
+ {:scalar=>q, :numerator=>num.flatten.compact, :denominator=>den.flatten.compact}
+ end
+
+
# parse a string into a unit object.
# Typical formats like :
# "5.6 kg*m/s^2"
# "5.6 kg*m*s^-2"
# "5.6 kilogram*meter*second^-2"
@@ -922,9 +955,13 @@
if Unit === other
unit_add(other.to('s').scalar)
else
unit_add(other)
end
+ end
+
+ def self.in(duration)
+ Time.now + duration.to_unit
end
alias :unit_sub :-
def -(other)
if Unit === other
\ No newline at end of file