Skip to content

Commit

Permalink
Some work towards division, this is a checkpoint before
Browse files Browse the repository at this point in the history
a major branch
  • Loading branch information
bhuga committed Jan 2, 2010
1 parent f1e437a commit 4f1b181
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 20 deletions.
16 changes: 16 additions & 0 deletions lib/quantity.rb
Expand Up @@ -207,6 +207,22 @@ def **(power)
end
end

# Square the units of this quantity
# @example
# 4.meters.squared == Quantity.new(4.'m^2')
# @return [Quantity]
def squared
Quantity.new(@value, @unit * @unit)
end

# Cube the units of this quantity
# @example
# 4.meters.cubed == Quantity.new(4.'m^3')
# @return [Quantity]
def cubed
Quantity.new(@value, @unit * @unit * @unit)
end

# Mod
# @return [Quantity]
def %(other)
Expand Down
11 changes: 9 additions & 2 deletions lib/quantity/systems/si.rb
Expand Up @@ -54,7 +54,7 @@ module SI
units['candela'] = Quantity::Unit::Luminosity
units['ampere'] = Quantity::Unit::Current
units['mole'] = Quantity::Unit::Substance
units['liter'] = Quantity::Unit::Volume
# liter is a special cased, handled separately below

aliases['ampere'] = ['amp', 'amps', 'A']
aliases['liter'] = ['litre', 'litres']
Expand All @@ -77,6 +77,7 @@ module SI

class Quantity::Unit::Length
add_alias :kilometer, :km
add_alias :centimeter, :cm
add_alias :meter, :m
add_alias :nanometer, :nm
add_unit :angstrom, 10 ** -7, :angstroms
Expand All @@ -89,7 +90,13 @@ class Quantity::Unit::Mass
add_alias :megagram, :tonne, :tonnes
end

class Quantity::Unit::Volume
Quantity::Unit::Volume.class_eval do
prefixes.each do | prefix, value |
add_unit "#{prefix}liter".to_sym, value * 1000, "#{prefix}liters".to_sym
(aliases['liter']).each do | unit_alias |
add_alias "#{prefix}liter".to_sym, "#{prefix + unit_alias}".to_sym
end
end
add_alias :liter, :l
end

Expand Down
26 changes: 25 additions & 1 deletion lib/quantity/unit.rb
Expand Up @@ -66,7 +66,18 @@ class Unit
def self.is_unit?(symbol)
@@units_hash.has_key?(symbol)
end


# A list of all known units and their aliases
# Units are returned as 2-tuples, [name unit]
# @return [[[Symbol String, Unit]]]
def self.all_units
ret = []
@@units_hash.each do | key, value |
ret << [key, value]
end
ret.sort { | a, b | a.to_s <=> b.to_s }
end

# Unit for a given symbol
# @param [Unit Symbol String] Unit, name or alias of unit, or description of derived unit
# @return [Unit]
Expand Down Expand Up @@ -210,6 +221,19 @@ def *(other)
end
end

# Unit division
# @param [Unit] other
# @return [Unit]
def /(other)
if other.is_a?(Derived)
raise NotImplementedError
elsif other.is_a?(Unit)
Unit.for("#{@name}/#{other.name}")
else
raise ArgumentError, "Cannot divide #{self.name} by #{other.name}"
end
end

# Can this unit create a new unit by multiplying with the given one?
# @param [Any] other
# @return [Boolean]
Expand Down
50 changes: 40 additions & 10 deletions lib/quantity/unit/derived.rb
Expand Up @@ -8,15 +8,17 @@ class Derived < Unit
# @return [[Unit, Numeric]]
# @private
def parse_unit(text)
(num_unit,junk,power) = text.split(/( |\^)/)
unit_power = case power
(unit_name,junk,power) = text.split(/( |\^)/)
unit_power = case power
when "squared"
2
when "cubed"
3
else power.to_i
end
unit = Unit.for(num_unit.to_sym)
unit_power = 1 unless unit_power > 0
puts "parsing #{text} while creating a derived class got p #{unit_power}"
unit = Unit.for(unit_name.to_sym)
[unit, unit_power]
end

Expand All @@ -38,20 +40,35 @@ def self.inherited(child)
# @return [Unit]
def initialize(new_name, value = nil)
if value.nil?
(numerator,denominator) = new_name.split(" per ")
(numerator,denominator) = new_name.split("/")
puts "got n #{numerator} d #{denominator}"
(@num_unit,@num_power) = parse_unit(numerator)
(@den_unit,@den_power) = parse_unit(denominator) if denominator
reference_unit_name = "#{@num_unit.reference_unit.name}^#{@num_power}"
@reference_unit = reference_unit_name == new_name ? self : Unit.for(reference_unit_name)
@value = (@num_unit.value)**@num_power
@value /= (@den_unit.value)**@den_power if @den_unit
puts "parsed out a new, one-argument der unit, #{new_name}"
else
@name = new_name
(numerator,denominator) = self.class.reference_unit.name.split("/")
(@num_unit,@num_power) = parse_unit(numerator)
(@den_unit,@den_power) = parse_unit(denominator) if denominator
@value = value
(@num_unit,@num_power) = parse_unit(self.class.reference_unit.name)
puts "parsed out a new, two-argument der unit, #{new_name}"
end
# TODO: check for a hard class that matches this signature
if (@num_power != 1)
reference_unit_name = "#{@num_unit.reference_unit.name}^#{@num_power}"
@num_reference_unit = reference_unit_name == new_name ? self : Unit.for(reference_unit_name)
else
@num_reference_unit = @num_unit.reference_unit
end
if (@den_unit)
if (@den_power != 1)
reference_unit_name = "#{@den_unit.reference_unit.name}^#{@den_power}"
@den_reference_unit = reference_unit_name == new_name ? self : Unit.for(reference_unit_name)
else
@den_reference_unit = @den_unit.reference_unit
end
end
puts "made a new derived class #{measures}, val #{@value} numu #{@num_unit.name} p #{@num_power}"
end

Expand Down Expand Up @@ -161,10 +178,23 @@ def self.derived_from(*source)
# that is a class-level instance variable. so an extending *Class*'s ref unit is this.
@reference_unit = unit
@degree = unit.num_power
add_alias(unit, source)
add_alias(unit, *source)
end


# @param [Symbol] name
# @param [Numeric] value
# @param [Array] *aliases
def self.add_unit(name, value, *aliases)
puts "adding new derived unit, #{name}"
unit = self.new(name, value)
add_alias(unit, name, *aliases)
end

# Sugar for self.class.reference_unit
# @return [Unit]
#def reference_unit
# self.class.reference_unit
#end
end #class_eval
end

Expand Down
2 changes: 0 additions & 2 deletions lib/quantity/unit/volume.rb
Expand Up @@ -4,9 +4,7 @@ class Unit
##
# @see http://en.wikipedia.org/wiki/Volume
class Volume < Derived
#TODO: make this work
derived_from "millimeter^3", :milliliter, :ml, :milliliters, :millilitre, :millilitres, :cc, :ccs
#reference :milliliter, :ml, :milliliters, :millilitre, :millilitres
end
end
end
11 changes: 7 additions & 4 deletions spec/quantity.spec
Expand Up @@ -122,10 +122,13 @@ describe Quantity do
end

it "should convert derived classes to hard classes" do
(1.centimeter * 1.centimeter * 1.centimeter).should == 1.cc
(1.centimeter * 1.centimeter * 1.centimer).measures.should == :volume
(1.centimeter * 1.centimeter).measures.should == :area
(30.meters / 1.second).measures.should == :speed
(1.centimeter * 1.centimeter * 1.centimeter).should == 0.1.centiliter
(1.centimeter * 1.centimeter * 1.centimeter).should == 1.centimeter.cubed
(1.mm * 1.mm * 1.mm).should == 1.ml
0.1.centiliter.should == (1.cm * 1.cm * 1.cm)
(1.centimeter * 1.centimeter * 1.centimeter).measures.should == 'length^3'
(1.centimeter * 1.centimeter).measures.should == 'length^2'
(30.meters / 1.second).measures.should == 'length/time'
end

it "should reduce derived units" do
Expand Down
15 changes: 14 additions & 1 deletion spec/unit.spec
Expand Up @@ -51,19 +51,32 @@ describe Quantity::Unit do
lambda {(Quantity::Unit.for(:foot) * Quantity::Unit.for(:meter))}.should raise_error ArgumentError
end

it "should divide units" do
Quantity::Unit.for(:meter) / Quantity::Unit.for(:second) == Quantity::Unit.for('meter/second')
Quantity::Unit.for('m^2') / Quantity::Unit.for(:second) == Quantity::Unit.for('meter^2/second')
Quantity::Unit.for(:meter) / Quantity::Unit.for('second^2') == Quantity::Unit.for('meter/second^2')
Quantity::Unit.for('m^2') / Quantity::Unit.for(:m) == Quantity::Unit.for(:meter)
end

it "should allow a user to reify derived classes" do
# cthulu will warp your mind in 5 dimensions!
class Cthulu < Quantity::Unit::Derived
derived_from 'millimeter^5'
derived_from 'millimeter^5', :cthuluunit
add_unit :ohgod, 5, :ohgods
add_unit :terror, 10, :terrors
end
ohgod = Quantity::Unit.for(:ohgod)
ohgod.name.should == :ohgod
ohgod.num_unit.should == Quantity::Unit.for(:mm)
ohgod.num_power.should == 5
# these aren't Unit specs really but its easier to have them here
1.ohgod.convert('mm^5').should == (Quantity.new(5,'mm^5'))
1.terror.should == 2.ohgod
1.ohgod.convert('mm^5').should == (Quantity.new(5,'mm^5'))
1.ohgod.should == 5.cthuluunit
1.ohgod.convert('mm^5').should == 5.cthuluunit
1.ohgod.should == Quantity.new(5,'mm^5')
625.ohgod.should == 5.mm**5
Quantity.new(5,'mm^5').should == 1.ohgod
1.ohgod.to_s.should == "1 ohgod"
(Quantity.new(5,'mm^5')).should == 1.ohgod
Expand Down

0 comments on commit 4f1b181

Please sign in to comment.