require 'quantity/version'
require 'quantity/dimension'
require 'quantity/dimension/base'
require 'quantity/unit'
require 'quantity/systems/si'
require 'quantity/systems/us'
class Quantity
include Comparable
autoload :Unit, 'quantity/unit'
attr_reader :value
attr_reader :unit
attr_reader :reference_value
def initialize(value, unit = nil )
case value
when Hash
@unit = Unit.for(value[:unit])
@reference_value = value[:reference_value] || (value[:value] * @unit.value)
@value = @unit.value_for(@reference_value)
when Numeric
@unit = Unit.for(unit)
if @unit.nil?
@unit = Unit.from_string_form(unit)
end
@value = value
@reference_value = value * @unit.value
end
end
def to_s
@unit.s_for(value)
end
def measures
@unit.dimension
end
def units
@unit.name
end
def abs
if @reference_value < 0
-self
else
self
end
end
def coerce(other)
if other.class == @value.class
[Quantity.new(other, @unit),self]
elsif defined?(Rational) && (@value.is_a?(Fixnum)) && (other.is_a?(Fixnum))
[Quantity.new(Rational(other), @unit), self]
elsif defined?(Rational) && (other.is_a?(Rational))
[Quantity.new(other, @unit), self]
else
[Quantity.new(other.to_f, @unit),Quantity.new(@value.to_f, @unit)]
end
end
def +(other)
if (other.is_a?(Numeric))
Quantity.new(@value + other, @unit)
elsif(other.is_a?(Quantity) && @unit.dimension == other.unit.dimension)
Quantity.new({:unit => @unit,:reference_value => @reference_value + other.reference_value})
else
raise ArgumentError,"Cannot add #{self} to #{other}"
end
end
def -(other)
if (other.is_a?(Numeric))
Quantity.new(@value - other, @unit)
elsif(other.is_a?(Quantity) && @unit.dimension == other.unit.dimension)
Quantity.new({:unit => @unit,:reference_value => @reference_value - other.reference_value})
else
raise ArgumentError, "Cannot subtract #{other} from #{self}"
end
end
def <=>(other)
if (other.is_a?(Numeric))
@value <=> other
elsif(other.is_a?(Quantity) && measures == other.measures)
@reference_value <=> other.reference_value
else
nil
end
end
def eql?(other)
other.is_a?(Quantity) && other.units == units && self == other
end
def *(other)
if (other.is_a?(Numeric))
Quantity.new(@value * other, @unit)
elsif(other.is_a?(Quantity))
Quantity.new({:unit => other.unit * @unit, :reference_value => @reference_value * other.reference_value})
else
raise ArgumentError, "Cannot multiply #{other} with #{self}"
end
end
def /(other)
if (other.is_a?(Numeric))
Quantity.new(@value / other, @unit)
elsif(other.is_a?(Quantity))
ref = nil
if defined?(Rational) && (@value.is_a?(Fixnum)) && (other.is_a?(Fixnum))
ref = Rational(@reference_value,other.reference_value)
elsif defined?(Rational) && (@value.is_a?(Rational)) && (other.is_a?(Rational))
ref = @reference_value / other.reference_value
else
ref = @reference_value / other.reference_value.to_f
end
Quantity.new({:unit => @unit / other.unit, :reference_value => ref})
else
raise ArgumentError, "Cannot multiply #{other} with #{self}"
end
end
def **(power)
unless power.is_a?(Fixnum) && power > 0
raise ArgumentError, "Quantities can only be raised to fixed powers (given #{power})"
end
if power == 1
self
else
self * self**(power - 1)
end
end
def squared
Quantity.new(@value, @unit * @unit)
end
def cubed
Quantity.new(@value, @unit * @unit * @unit)
end
def %(other)
if (other.is_a?(Numeric))
Quantity.new(@value % other, @unit)
elsif(other.is_a?(Quantity) && self.measures == other.measures)
Quantity.new({:unit => @unit, :reference_value => @reference_value % other.reference_value})
else
raise ArgumentError, "Cannot modulo #{other} with #{self}"
end
end
alias_method :modulo, :%
def -@
Quantity.new({:unit => @unit, :reference_value => @reference_value * -1})
end
def +@
self
end
def to_i
@value.to_i
end
def to_f
@value.to_f
end
def round
Quantity.new(@value.round, @unit)
end
def truncate
Quantity.new(@value.truncate, @unit)
end
def floor
Quantity.new(@value.floor, @unit)
end
def ceil
Quantity.new(@value.ceil, @unit)
end
def divmod(other)
if (other.is_a?(Numeric))
(q, r) = @value.divmod(other)
[Quantity.new(q,@unit),Quantity.new(r,@unit)]
elsif (other.is_a?(Quantity) && measures == other.measures)
(q, r) = @value.divmod(other.value)
[Quantity.new(q,@unit),Quantity.new(r,@unit)]
else
raise ArgumentError, "Cannot divmod #{other} with #{self}"
end
end
def zero?
@value.zero?
end
def convert(to)
Quantity.new({:unit => @unit.convert(to), :reference_value => @reference_value})
end
def inspect
to_s
end
def method_missing(method, *args, &block)
if method.to_s =~ /(to_|in_)(.*)/
if (Unit.is_unit?($2.to_sym))
convert($2.to_sym)
else
raise ArgumentError, "Unknown target unit type: #{$2}"
end
else
raise NoMethodError, "Undefined method `#{method}` for #{self}:#{self.class}"
end
end
def respond_to?(method)
if method.to_s =~ /(to_|in_)(.*)/
if (Unit.is_unit?($2.to_sym))
return true
end
end
super
end
end
class Numeric
alias_method :quantity_method_missing, :method_missing
def method_missing(method, *args, &block)
if Quantity::Unit.is_unit?(method)
Quantity.new(self,Quantity::Unit.for(method))
else
quantity_method_missing(method,*args, &block)
end
end
end