Experimental unit conversion for Ruby 1.9
MIT License
= 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:
= 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{60km + 10mi} # => 76.09344km puts u{sin(45°)} # => 0.707106781186547 x = u{120km/h} puts x.to(u{mi/h}) # => 74.5645430684801mi/h
Text strings can also be used to define units:
puts Units.u('60km + 10mi') # => 76.09344km puts Units.u('sin(45°)') # => 0.707106781186547 x = Units.u('120km/h') puts x.to(Units.u('mi/h')) # => 74.5645430684801mi/h
And also as the right operand of binary arithmetic operators:
puts Units.u('20km')/'h' # => 20.0km/h
New units can be defined with +Units.define+
Units.define :kph, 1, Units.u{km/h} puts Units.u{270kph.to(m/s)} # => 75.0m/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
puts u{gConst.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.
puts Units.with_constants(:g){kgg} # => 9.80665(kg*m)/s**2
puts Units.with_constants(:c){1GeV/c**2}.to(:kg) # => 1.782661844855044e-27kg
Units.with_constants :c, :G, :hbar do puts sqrt(hbarG/c**3) # => 1.6161992557033346e-35m puts sqrt(hbarc/G) # => 2.176509252445312e-08kg puts sqrt(hbarG/c**5) # => 5.391060423886096e-44s 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
== Copyright
Copyright (c) 2009 Javier Goizueta. See LICENSE for details.