Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added generic SI for everything.

  • Loading branch information...
commit 19c2f1aa4daef1fbf213fb0be795143ef8882b61 1 parent c55008b
@bhuga authored
View
2  .yardopts
@@ -9,5 +9,5 @@
AUTHORS
UNLICENSE
VERSION
-
+lib/quantity.rb lib/**/*.rb
View
20 Rakefile
@@ -1,8 +1,20 @@
#!/usr/bin/env ruby
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), 'lib')))
require 'rubygems'
-begin
- require 'rakefile' # http://github.com/bendiken/rakefile
-rescue LoadError => e
-end
require 'quantity'
+require 'spec'
+require 'spec/rake/spectask'
+require 'yard'
+
+
+desc "Run specs"
+Spec::Rake::SpecTask.new('spec') do |t|
+ t.spec_files = FileList['spec/*.spec']
+ t.spec_opts = ["-cfn"]
+end
+
+desc "package yardocs"
+YARD::Rake::YardocTask.new('yard') do |t|
+ # see .yardopts for the action
+end
+
View
74 lib/quantity.rb
@@ -4,35 +4,34 @@
# A quantity of something. Quantities are immutable; conversions and other operations return
# a new quantity.
#
-# @example General Use
-# require 'quantity/si'
+# ## General Use
+# require 'quantity/si'
#
-# 12.meters #=> Quantity
-# 12.meters.measures #=> :length
-# 12.meters.units #=> :meters
-# 12.meters.unit #=> Quantity::Unit::Length
-# 12.meters.in_centimeters == 1200.centimeters #=> true
-# 12.meters == 12 #=> true
-# 12 == 12.meters #=> false (careful!)
-# 12.meters == 12.centimeters #=> false
-# 12.meters + 5.centimeters == 12.05.meters #=> true
-# 12.meters.in_picograms #=> raises ArgumentError
+# 12.meters #=> Quantity
+# 12.meters.measures #=> :length
+# 12.meters.units #=> :meters
+# 12.meters.unit #=> Quantity::Unit::Length
+# 12.meters.in_centimeters == 1200.centimeters #=> true
+# 12.meters == 12 #=> true
+# 12.meters == 12.centimeters #=> false
+# 12.meters + 5.centimeters == 12.05.meters #=> true
+# 12.meters.in_picograms #=> raises ArgumentError
#
-# @example Derived Units
-# require 'quantity/si'
-# speed_of_light = 299_752_458.meters / 1.second #=>Quantity::Unit::Derived
-# speed_of_light.measures #=> "meters per second"
-# speed_of_light.units #=> "meters per second"
+# ## Derived Units
+# require 'quantity/si'
+# speed_of_light = 299_752_458.meters / 1.second #=>Quantity::Unit::Derived
+# speed_of_light.measures #=> "meters per second"
+# speed_of_light.units #=> "meters per second"
#
-# ludicrous_speed = speed_of_light * 1000
-# ludicrous_speed.measures #=> "meters per second" #TODO: velocity, accleration ?
-# ludicrous_speed.to_s #=> "299752458000 meters per second"
+# ludicrous_speed = speed_of_light * 1000
+# ludicrous_speed.measures #=> "meters per second" #TODO: velocity, accleration ?
+# ludicrous_speed.to_s #=> "299752458000 meters per second"
#
-# four_square_meters = 2.meters * 2.meters
-# four_square_meters.measures #=> "meters squared" #TODO: area ?
-# four_square_meteres.units #=> "meters squared"
-# # the magic only goes so far
-# four_square_meteres * 2.meters.measures #=> "meters squared * meters" # the magic only goes so far
+# four_square_meters = 2.meters * 2.meters
+# four_square_meters.measures #=> "meters squared" #TODO: area ?
+# four_square_meteres.units #=> "meters squared"
+# # the magic only goes so far
+# four_square_meteres * 2.meters.measures #=> "meters squared * meters"
#
# If the default to_s isn't what you want, you can buld it with 12.meters.value and 12.meters.units
#
@@ -171,7 +170,7 @@ def convert(to)
end
end
- ##
+ #
# :method to_unit
# Convert this quantity to another quantity.
# unit can be any unit that measures the same thing as this quantity, i.e.
@@ -180,19 +179,20 @@ def convert(to)
# @raises ArgumentError
# @return [Quantity]
- # @private
- # this creates the conversion methods of .to_* and .in_*
- 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}"
+ # this creates the conversion methods of .to_* and .in_*
+ # @private
+ 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
- else
- raise NoMethodError, "Undefined method `#{method}` for #{self}:#{self.class}"
end
- end
+
end
# @private
View
86 lib/quantity/si.rb
@@ -0,0 +1,86 @@
+# SI units for Length, Mass, Luminosity, Current, Substance,
+# Temperature, and Time. Units from yocto- to yotta- are supplied.
+#
+# Ångstroms are also supplied for Length.
+#
+# Volume (liters) is also part of this, since it follows the same pattern.
+#
+# The 'reference' unit is millimeters. Units larger than millimeters
+# constructed via Fixnums/Bignums, such as 2.meters, will be stored with
+# Fixnum / Bignum accuracy. Smaller items, such as 35.femtometers, will
+# be stored with (rationals / floats ?). FIXME
+#
+# @see http://physics.nist.gov/cuu/Units/units.html
+# @see http://physics.nist.gov/cuu/Units/current.html
+# @see http://physics.nist.gov/cuu/Units/prefixes.html
+module SI
+ prefixes = {}
+ units = {}
+ aliases = {}
+
+ prefixes['yotta'] = 10 ** 27
+ prefixes['zetta'] = 10 ** 24
+ prefixes['exa'] = 10 ** 21
+ prefixes['peta'] = 10 ** 18
+ prefixes['tera'] = 10 ** 15
+ prefixes['giga'] = 10 ** 12
+ prefixes['mega'] = 10 ** 9
+ prefixes['kilo'] = 10 ** 6
+ prefixes['hecto'] = 10 ** 5
+ prefixes['deca'] = 10 ** 4
+ prefixes[''] = 10 ** 3
+ prefixes['deci'] = 10 ** 2
+ prefixes['centi'] = 10
+ # milli is the reference point for SI-measured units
+ prefixes['micro'] = 10 ** -3
+ prefixes['nano'] = 10 ** -6
+ prefixes['pico'] = 10 ** -9
+ prefixes['femto'] = 10 ** -12
+ prefixes['atto'] = 10 ** -15
+ prefixes['zepto'] = 10 ** -18
+ prefixes['yocto'] = 10 ** -21
+
+ units['meter'] = Quantity::Unit::Length
+ units['gram'] = Quantity::Unit::Mass
+ units['liter'] = Quantity::Unit::Volume
+ units['second'] = Quantity::Unit::Time
+ units['kelvin'] = Quantity::Unit::Temperature
+ units['candela'] = Quantity::Unit::Luminosity
+ units['ampere'] = Quantity::Unit::Current
+ units['mole'] = Quantity::Unit::Substance
+
+ aliases['ampere'] = ['amp', 'amps', 'A']
+ aliases['liter'] = ['litre', 'litres']
+ aliases['candela'] = ['cd']
+ aliases['mole'] = ['mol']
+ aliases['kelvin'] = ['K']
+
+ units.each do | unit, classname |
+ classname.class_eval do
+ prefixes.each do | prefix, value |
+ add_unit "#{prefix + unit}".to_sym, value, "#{prefix + unit}s".to_sym
+ if aliases[unit]
+ add_alias "#{prefix + unit}".to_sym, *(aliases[unit])
+ end
+ end
+ end
+ end
+
+ class Quantity::Unit::Length
+ add_alias :kilometer, :km
+ add_alias :meter, :m
+ add_alias :nanometer, :nm
+ add_unit :angstrom, 10 ** -7, :angstroms
+ end
+
+ class Quantity::Unit::Mass
+ add_alias :kilogram, :kg
+ add_alias :gram, :g
+ add_alias :milligram, :mg
+ end
+
+ class Quantity::Unit::Volume
+ add_alias :liter, :l
+ end
+
+end
View
91 lib/quantity/unit.rb
@@ -1,4 +1,50 @@
class Quantity
+ # A unit of measurement for a quantity. 'Unit' is a base from which more
+ # specific classes are built, such as 'Length' and 'Mass'.
+ #
+ # Most use cases won't require working with unit classes directly.
+ # The quantity class contains helper methods to get at all of the
+ # relevant information.
+ #
+ # A concrete class will instantiate various units of measurement for
+ # a particular measurement domain, i.e. 'Meters' and 'Inches' are
+ # instances of Quantity::Unit::Length. These known classes are
+ # singletons--every quantity of meters shares the same meters unit
+ # instance.
+ #
+ # Multiplication and division will create a Quantity::Unit::Derived
+ # instance. This instance will be something along the lines of
+ # 'meters per second'. Allowing particular derived units, such
+ # as 'meters per second' to be defined as 'velocity' and treated
+ # as first-class measurement domains is on the to-do list.
+ #
+ # ## Adding new units
+ # Each measurement type (such as length or mass) has a reference, from
+ # which all other units are derived. This unit is declared by fiat
+ # and not changeable without lots of work. Generally speaking, the reference
+ # is the milli version of the SI unit for that domain (milligrams, milliliters,
+ # etc).
+ #
+ # A DSL exists for defining reference and additional units. The
+ # Quantity library comes with a number of base units you can see for
+ # inspiration, but it's also easy to add your own units to the existing
+ # measurement systems.
+ #
+ # class Quantity
+ # class Unit
+ # class Length
+ # # a furlong is 201168 millimeters
+ # add_unit :furlong, 201168, :furlongs
+ # end
+ # end
+ # end
+ #
+ # Alternately:
+ #
+ # Quantity::Unit::Length.add_unit :furlong, 201168, :furlongs
+ #
+ # If you've added some fun ones, fork, commit and request on github.
+ #
class Unit
autoload :Length, 'quantity/unit/length'
autoload :Mass, 'quantity/unit/mass'
@@ -7,39 +53,65 @@ class Unit
autoload :Temperature, 'quantity/unit/temperature'
autoload :Luminosity, 'quantity/unit/luminosity'
autoload :Substance, 'quantity/unit/substance'
+ autoload :Volume, 'quantity/unit/volume'
+ # list of units by names and aliases
+ # @private
@@units_hash = {}
- ##
+ # Check if a unit exists for the given symbol
# @param [Symbol] name
+ # @return [Boolean]
def self.is_unit?(symbol)
@@units_hash.has_key?(symbol)
end
- ##
- # @param: [Unit Symbol] Unit, name or alias of unit
+ # Unit for a given symbol
+ # @param [Unit Symbol] Unit, name or alias of unit
+ # @return [Unit]
def self.for(unit)
unit.is_a?(Unit) ? unit : @@units_hash[unit]
end
+ # Adds some methods to children when they extend this class.
+ # @private
def self.inherited(child)
child.class_eval do
+ # All units for this measured type
@@units = []
+
+ # Reference for this measured type
@@reference = nil
+
+ # @return [Symbol]
def measures
self.class.name.split(':').last.downcase.to_sym
end
+
+ # @param [Symbol] name
+ # @param [Array] *aliases
def self.reference(name, *aliases)
unit = self.new(name, 1)
@reference = unit
@@units_hash[name] = unit
- aliases.each { | name | @@units_hash[name] = unit }
+ add_alias name, *aliases
end
+
+ # @param [Symbol] name
+ # @param [Numeric] value
+ # @param [Array] *aliases
def self.add_unit(name, value, *aliases)
unit = self.new(name, value)
@@units_hash[name] = unit
- aliases.each { | name | @@units_hash[name] = unit }
+ add_alias name, *aliases
end
+
+ # @param [Symbol] original
+ # @param [Array] *aliases
+ def self.add_alias(original, *aliases)
+ aliases.each { | name | @@units_hash[name] = self.for(original) }
+ end
+
##
# @param [String] name
def initialize(name, value)
@@ -51,7 +123,13 @@ def initialize(name, value)
# instance methods
- attr_reader :name, :value
+ # The name of this unit, such as ":meters"
+ attr_reader :name
+
+ # The multiplier to represent this unit in terms of this measurement type's
+ # reference unit, such as '1000' for meters when the reference is millimeters.
+ attr_reader :value
+
# @return [String]
alias_method :to_s, :name
@@ -61,7 +139,6 @@ def to_sym
name.to_sym
end
- ##
# @param [Numeric] return a string representing this numeric as this unit
# @return [String]
def s_for(s)
View
5 lib/quantity/unit/length.rb
@@ -1,12 +1,9 @@
class Quantity
class Unit
- ##
+ # A unit of Length. The Length reference unit is millimeters.
# @see http://en.wikipedia.org/wiki/Length
class Length < Unit
reference :millimeter, :millimeters, :m
- add_unit :centimeter, 10, :cm, :centimeters
- add_unit :meter, 1000, :m, :meters
- add_unit :kilometer, 1_000_000, :km, :kilometers
end
end
end
View
3  lib/quantity/unit/mass.rb
@@ -4,9 +4,6 @@ class Unit
# @see http://en.wikipedia.org/wiki/Mass
class Mass < Unit
reference :milligram, :mg, :milligrams
- add_unit :picogram, 0.000000001, :pg, :picograms
- add_unit :gram, 1000, :pg, :kilograms
- add_unit :kilogram, 1000000, :kg, :kilograms
end
end
end
View
9 lib/quantity/unit/volume.rb
@@ -0,0 +1,9 @@
+class Quantity
+ class Unit
+ ##
+ # @see http://en.wikipedia.org/wiki/Volume
+ class Volume < Unit
+ reference :milliliter, :ml, :milliliters, :millilitre, :millilitres
+ end
+ end
+end
View
7 spec/quantity.spec
@@ -5,6 +5,7 @@ require 'quantity/imperial'
require 'quantity/unit'
require 'quantity/unit/length'
require 'quantity/unit/mass'
+require 'quantity/si'
describe Quantity do
@@ -63,12 +64,14 @@ describe Quantity do
it "should multiply any items" do
(2.meters * 2.meters).should == 4
- (2.meters * 2.meters).unit.name == "meter^2"
- (2.meters * 2.meters).unit.measures == "meter^2"
+ (2.meters * 2.meters).unit.name == "meter squared"
+ (2.meters * 2.meters).unit.measures == "meter squared"
end
it "should divide any items" do
(2.meters / 2.picograms).should == 5
+ (2.meters / 2.picograms).measures.should == "meters per picogram"
+ (2.meters / 2.picograms).units.should == "meters per picogram"
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.