Ruby Units-System
Units of measure conversions for Ruby, using Ruby objects and Ruby syntax rather than text strings.
There are a number of Ruby units libraries, but I don’t think they take this approach (I haven’t done much research, though.)
There’s a couple of caveats for using this module from Ruby 1.8:
-
To use unit names that start with uppercase letters, the UseBlocks module must be included in the scope (either global scope or a module or class definitions) where the expressions are goign to be used. Alternatively, expressions such as
u{W}
could be replaced by eitheru{W()}
,u{self.W}
(method invocation) oru{self::W}
(qualified constant). Units defined in text form, such asu('W*h')
will work regardless of whether UseBlocks is included or not. -
UTF-8 characters are liberally used in identifiers, so the code must be executed with the
-Ku
option (or setting$KCODE='UTF8'
before requiring this library.)
Usage examples
Ruby 1.9
For use with Ruby 1.9, this gem can be used simply by requiring:
require 'units-system'
For versions other than 1.9.1 this is needed as in Ruby 1.8:
include Units::UseBlocks # allow access to capitalized unit names from units/u blocks
Ruby 1.8
This library has been designed for Ruby 1.9; when using it under older versions of Ruby there’s a couple of precautions to be taken to use it (which can be used with Ruby 1.9 too):
$KCODE = 'UTF8' # avoid errors when parsing the required library under Ruby 1.8
require 'units-system'
include Units::UseBlocks # allow access to capitalized unit names from units/u blocks
Depending on you installation you may have to “require ‘rubygems’ first.
The following examples use UTF-8 code; so they can should be used with a “encoding: utf-8” comment at the top of the file for Ruby 1.9, and/or with the ruby command line “-Ku” option for Ruby 1.8.
To work with units a units
block can be used. Beware: in it self
is changed, so outer self methods or instance variables are not accessible, unless assigned to local variables.
require 'units-system'
Units.units do
# In the units environment predefined variables are available for all units and they
# can be combined arithmetically:
x = 3*m/s
puts x # => 3.0*m/s
# Note that SI prefixes (k for kilo, etc.) can be used as part of the unit names:
x += 17*km/h
puts x # => 7.72222222222222*m/s
puts x.to(km/h) # => 27.8*km/h
puts x.magnitude # => 7.72222222222222
# Let's use some unit powers: convert 3 cubic meters to litres:
puts (3*m**3).to(l) # => 3000.0*l
# Now let's convert some imperial units to SI:
puts (100*mi/h).to_si # => 44.704*m/s
# Note that +in+ is a Ruby keyword, so to use inches you must use +self.in+:
puts (10*cm).to(self.in) # => 3.93700787401575*in
# ...or use the alternative nonstandard name +inch+
puts (10*cm).to(inch) # => 3.93700787401575*inch
# Now let's use derived units, e.g. power units:
x = 10*kW
# show a verbose description of the measure:
puts x.describe # => 10.0 kiloWatt
# convert to base units
puts x.base # => 10000.0*(m**2*kg)/s**3
# a more natural notation can be used instead of the default Ruby syntax:
puts x.base.abr # => 10000.0 (m^2 kg)/s^3
# Note that unit names that start with uppercase letters are OK:
# (but see the notes on UseBlocks above if this doesn't work)
puts 11*W # => 11.0*W
puts (2*Mg).to(kg) # => 2000.0*kg
# Let's use kilograms-force (kiloponds) (not a SI unit)
x = 10*kgf
puts x # => 10.0*kgf
# conversion to SI units uses the SI unit of force the newton N (which is a derived unit)
puts x.to_si # => 98.0665*N
# conversion to base units substitutes derived units for base units
puts x.base # => 98066.5*(g*m)/s**2
# but g (gram) is not a base SI unit, to get SI base units we must:
puts x.base.to_si # => 98.0665*(kg*m)/s**2
# And now, for some trigonometry fun! (note the use of unicode characters)
x = 90*°
puts x # => 90.0*°
puts x.to(rad) # => 1.5707963267949*rad
puts sin(x) # => 1.0
puts sin(45*°+30*′+10*″) # => 0.713284429355996
puts asin(0.5) # => 0.523598775598299*rad
puts asin(0.5).to(°) # => 30.0*°
puts asin(0.5).in(°) # => 30.0
puts atan2(10*cm, 0.1*m).to(°) # => 45.0*°
# Temperature conversions may be absolute (convert levels of temperature)
# or relative (convert differences of temperature)
# When a measure has a single unit of temperature, conversion is absolute:
puts (20*°C).to(K) # => 293.15*K
puts (20*°C).to(°F) # => 67.9999999999999*°F
puts (20*mK).to(°C) # => -273.13*°C
# In other cases conversion is relative:
puts (2*°C/h).to(K/h) # => 2.0*K/h
puts (2*°C/h).to(°F/h) # => 3.6*°F/h
# To force the relative conversion of a single temperature pass a second argument to to():
puts (20*°C).to(K,:relative) # => 20.0*K
puts (20*°C).to(°F,:relative) # => 36.0*°F
puts (20*mK).to(°C,:relative) # => 0.02*°C
end
For short expressions, the abbreviation Units.u
can be used instead of Units.units
include Units
puts u{60*km + 10*mi} # => 76.09344*km
puts u{sin(45*°)} # => 0.707106781186547
x = u{120*km/h}
puts x.to(u{mi/h}) # => 74.5645430684801*mi/h
Text strings can also be used to define units:
puts Units.u('60*km + 10*mi') # => 76.09344*km
puts Units.u('sin(45*°)') # => 0.707106781186547
x = Units.u('120*km/h')
puts x.to(Units.u('mi/h')) # => 74.5645430684801*mi/h
And also as the right operand of binary arithmetic operators:
puts Units.u('20*km')/'h' # => 20.0*km/h
New units can be defined with Units.define
Units.define :kph, 1, Units.u{km/h}
puts Units.u{270*kph.to(m/s)} # => 75.0*m/s
Constants
Constants could be define practically as units, but to avoid introducing too much noise in the units namespace, they can be defined separately with:
Units.constant :g, 'standard gravity', u{9.80665*m/s**2}
A constant can be used anywhere with the Units::Const prefix:
puts Units::Const.g # => 9.80665*m/s**2
# gram-force:
puts u{g*Const.g} # => 9.80665*(g*m)/s**2
To avoid using the prefix, constants to be used unprefixed can be declared with a with_constants
; Note in the first example, that by introducing a constant named g
we’re hiding the gram units and would not be able to use it in the block.
# kilopond:
puts Units.with_constants(:g){kg*g} # => 9.80665*(kg*m)/s**2
# 1 GeV mass
puts Units.with_constants(:c){1*GeV/c**2}.to(:kg) # => 1.782661844855044e-27*kg
# Planck units
Units.with_constants :c, :G, :hbar do
puts sqrt(*G/c**3) # => 1.6161992557033346e-35*m
puts sqrt(*c/G) # => 2.176509252445312e-08*kg
puts sqrt(*G/c**5) # => 5.391060423886096e-44*s
end
Caveat
Note that Ruby variable definition rules imply that this:
m = Units.u{m}
Results is a nil value (the outer m assignment defines a local m variable even before executing the block, so the m in the block refers to that, yet-unassigned, variable and not to the meter unit)
Note on Patches/Pull Requests
-
Fork the project.
-
Make your feature addition or bug fix.
-
Add tests for it. This is important so I don’t break it in a future version unintentionally.
-
Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
-
Send me a pull request. Bonus points for topic branches.
Copyright
Copyright © 2009 Javier Goizueta. See LICENSE for details.