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