# Code Sharing - Units (abandoned child)

**URL:** <https://rubytalk.org/t/code-sharing-units-abandoned-child/20543>\
**Category:** ruby-talk\
**Created:** [1 September 2005 03:41 UTC](https://rubytalk.org/t/code-sharing-units-abandoned-child/20543 "2005-09-01T03:41:51Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![Gavin\_Kistner2](https://avatars.discourse-cdn.com/v4/letter/g/b9e5f3/32.png) [@Gavin\_Kistner2](https://rubytalk.org/u/Gavin_Kistner2)\
**Post date:** [1 September 2005 03:41 UTC](https://rubytalk.org/t/code-sharing-units-abandoned-child/20543/1 "2005-09-01T03:41:51Z")

</div>

Last night I was sleepily trying to calculate the sustained transfer rate my web server would need to maintain to reach my quoted quota of 300GB/month transfter. It sparked an idea, and this morning I played around with some inferential unit conversion code. I don't have the energy to finish it off (it's more than just polish), but thought I'd share it anyhow. I like the syntax it allows 🙂

Example code usage:

Units.add\_conversion( 60.seconds, 1.minute )  
Units.add\_conversion( 1.mile\_per\_hour, 1.46666667.feet\_per\_second )  
puts ( 32.feet\_per\_second\_second \* 1.5.minutes.in\_seconds ).in\_miles\_per\_hour  
#=\> 1963.63635917355 miles/hour

distance = 3.feet  
time = 1.second  
rate = distance / time  
puts rate, rate / 3  
#=\> 3 feet/second  
#=\> 1 foot/second

puts 18.camels \* 12.days / 4.cows + 89.widget\_jobbers  
#=\> ((54 camel\*day/cow) + (89 jobber\*widget))

Things it doesn't do that it should, IMO:  
1) More robust pluralization/singularization of nouns

2) Accept scalar factors/divisors.

3) Automatically search for a path between two conversions.  
(For example, if it knows how to convert from GB/s to MB/s and from MB/s to kB/s, it should be smart enough to know how to find the path from GB/s to kB/s.)

4) Extend Numeric operators to turn the tables around if the right operand is a Quantity or Expression.

5) All sorts of fun symbolic math with Expressions

6) Use conversions to flatten Expressions with convertible-units.  
(For example, (1.hour + 30.minutes) should be able to be automatically converted into a single Quantity using only hours or minutes.)

IMO it shouldn't know anything about any sort of units a priori, but instead require things like Unit.add\_conversion( 1.mile\_per\_hour, 1.mph ).

module Units  
&nbsp;&nbsp;&nbsp;def method\_missing( name, \*args )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;top\_units, bot\_units = Units.from\_string( name, self!=1 )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Quantity.new( self, top\_units, bot\_units )  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;def Units.add\_conversion( q1, q2 )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@conversions ||= {}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(@conversions[q1.units] ||= {})[q2.units] = 1.0 \* q2.value / q1.value  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(@conversions[q2.units] ||= {})[q1.units] = 1.0 \* q1.value / q2.value  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;def Units.convert( q1, units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;convert\_to = ( @conversions ||= {} )[q1.units]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if convert\_to && factor = convert\_to[units]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Quantity.new( q1.value \* factor, \*units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise "I don't know how to convert from #{q1.units} to #{units}"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;def Units.from\_string( description, singularize=true )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;top = []  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bot = []  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;section = top  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;description.to\_s.split( '\_' ).each{ |unit|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case unit  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when 'in'  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise "Cannot create 'in' units (reserved for conversion)"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when 'per'  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;section = bot  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;section \<\< ( singularize ? unit.singular : unit )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return [top, bot]  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;class Quantity  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;attr\_reader :value, :units, :top\_units, :bot\_units

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def initialize( value, top\_units=[], bot\_units=[] )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@value = value  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@top\_units = [].concat( top\_units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@bot\_units = [].concat( bot\_units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@units = [@top\_units, @bot\_units]

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Simplify  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;removed = []  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@bot\_units.each\_with\_index{ |divisor, div\_i|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if i = @top\_units.index( divisor )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@top\_units.delete\_at( i )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;removed \<\< div\_i  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless removed.empty?  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;removed.each{ |divisor\_index|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@bot\_units.delete\_at( divisor\_index )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@top\_units.sort!  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@bot\_units.sort!  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def dup  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value, @top\_units, @bot\_units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def method\_missing( name, \*args )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( name = name.to\_s ) =~ /^in\_/  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Units.convert( self, Units.from\_string( name.sub( /^in\_/, '' ) ) )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;top, bot = Units.from\_string( name )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value, name, @top\_units + top, @bot\_units + bot )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def same\_units\_as?( other )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return false unless other.respond\_to? :units  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.units == other.units  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def combine\_units( \*quantities )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;quantities.each{ |q|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@top\_units.concat( q.top\_units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@bot\_units.concat( q.bot\_units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def +( other )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if self.same\_units\_as?( other )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value + other.value, @top\_units, @bot\_units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Expression.new( self, :+, other )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def -( other )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if self.same\_units\_as?( other )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value - other.value, @top\_units, @bot\_units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Expression.new( self, :-, other )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def \*( other )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if other.respond\_to? :units  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value \* other.value, @top\_units + other.top\_units, @bot\_units + other.bot\_units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value \* other, @top\_units, @bot\_units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def /( other )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if other.respond\_to? :units  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value / other.value, @top\_units + other.bot\_units, @bot\_units + other.top\_units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value / other, @top\_units, @bot\_units )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def to\_s  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wrap = @top\_units.length \> 1 or @bot\_units.length \> 0  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out = wrap ? '(' : ''  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out \<\< "#@value "  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if @value != 1 && @top\_units.length == 1  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out \<\< @top\_units.first.plural  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out \<\< @top\_units.join( '\*' )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless @bot\_units.empty?  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out \<\< '/'  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out \<\< @bot\_units.join( '\*' )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out \<\< ')' if wrap  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;class Expression  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;attr\_reader :o1, :op, :o2  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def initialize( o1, op, o2 )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@o1 = o1  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@op = op  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@o2 = o2  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def to\_s  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"(#@o1 #@op #@o2)"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;end

end

class String  
&nbsp;&nbsp;&nbsp;def singular  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.gsub( /(([hs])e)?s$/, '\2' ).gsub( 'feet', 'foot' )  
&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;def plural  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out = self + ( ( self =~ /[hs]$/ ) ? 'es' : 's' )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out.gsub( 'foots', 'feet' )  
&nbsp;&nbsp;&nbsp;end  
end

class Numeric  
&nbsp;&nbsp;&nbsp;include Units  
end

---

<div class="post-metadata">

**Author:** ![Ara.T.Howard6](https://avatars.discourse-cdn.com/v4/letter/a/958977/32.png) [@Ara.T.Howard6](https://rubytalk.org/u/Ara.T.Howard6)\
**Post date:** [1 September 2005 04:15 UTC](https://rubytalk.org/t/code-sharing-units-abandoned-child/20543/2 "2005-09-01T04:15:34Z")

</div>

you might be interested in this:

&nbsp;&nbsp;&nbsp;[http://raa.ruby-lang.org/project/quanty/](http://raa.ruby-lang.org/project/quanty/)

you're code looks very interesting too though...

cheers.

-a

> **···**
>
> On Thu, 1 Sep 2005, Gavin Kistner wrote:
> 
> > Last night I was sleepily trying to calculate the sustained transfer rate my web server would need to maintain to reach my quoted quota of 300GB/month transfter. It sparked an idea, and this morning I played around with some inferential unit conversion code. I don't have the energy to finish it off (it's more than just polish), but thought I'd share it anyhow. I like the syntax it allows 🙂
> > 
> > Example code usage:
> > 
> > Units.add\_conversion( 60.seconds, 1.minute )  
> > Units.add\_conversion( 1.mile\_per\_hour, 1.46666667.feet\_per\_second )  
> > puts ( 32.feet\_per\_second\_second \* 1.5.minutes.in\_seconds ).in\_miles\_per\_hour  
> > #=\> 1963.63635917355 miles/hour
> > 
> > distance = 3.feet  
> > time = 1.second  
> > rate = distance / time  
> > puts rate, rate / 3  
> > #=\> 3 feet/second  
> > #=\> 1 foot/second
> > 
> > puts 18.camels \* 12.days / 4.cows + 89.widget\_jobbers  
> > #=\> ((54 camel\*day/cow) + (89 jobber\*widget))
> 
> # --
> 
> > email :: ara [dot] t [dot] howard [at] noaa [dot] gov  
> > phone :: 303.497.6469  
> > Your life dwells amoung the causes of death  
> > Like a lamp standing in a strong breeze. --Nagarjuna
> 
> ===============================================================================

---

<div class="post-metadata">

**Author:** ![Jeff\_Wood](https://avatars.discourse-cdn.com/v4/letter/j/9fc29f/32.png) [@Jeff\_Wood](https://rubytalk.org/u/Jeff_Wood)\
**Post date:** [1 September 2005 05:14 UTC](https://rubytalk.org/t/code-sharing-units-abandoned-child/20543/3 "2005-09-01T05:14:32Z")

</div>

Gavin Kistner wrote:

> Last night I was sleepily trying to calculate the sustained transfer rate my web server would need to maintain to reach my quoted quota of 300GB/month transfter. It sparked an idea, and this morning I played around with some inferential unit conversion code. I don't have the energy to finish it off (it's more than just polish), but thought I'd share it anyhow. I like the syntax it allows 🙂
> 
> Example code usage:
> 
> Units.add\_conversion( 60.seconds, 1.minute )  
> Units.add\_conversion( 1.mile\_per\_hour, 1.46666667.feet\_per\_second )  
> puts ( 32.feet\_per\_second\_second \* 1.5.minutes.in\_seconds ).in\_miles\_per\_hour  
> #=\> 1963.63635917355 miles/hour
> 
> distance = 3.feet  
> time = 1.second  
> rate = distance / time  
> puts rate, rate / 3  
> #=\> 3 feet/second  
> #=\> 1 foot/second
> 
> puts 18.camels \* 12.days / 4.cows + 89.widget\_jobbers  
> #=\> ((54 camel\*day/cow) + (89 jobber\*widget))
> 
> Things it doesn't do that it should, IMO:  
> 1) More robust pluralization/singularization of nouns
> 
> 2) Accept scalar factors/divisors.
> 
> 3) Automatically search for a path between two conversions.  
> (For example, if it knows how to convert from GB/s to MB/s and from MB/s to kB/s, it should be smart enough to know how to find the path from GB/s to kB/s.)
> 
> 4) Extend Numeric operators to turn the tables around if the right operand is a Quantity or Expression.
> 
> 5) All sorts of fun symbolic math with Expressions
> 
> 6) Use conversions to flatten Expressions with convertible-units.  
> (For example, (1.hour + 30.minutes) should be able to be automatically converted into a single Quantity using only hours or minutes.)
> 
> IMO it shouldn't know anything about any sort of units a priori, but instead require things like Unit.add\_conversion( 1.mile\_per\_hour, 1.mph ).
> 
> module Units  
> &nbsp;&nbsp;def method\_missing( name, \*args )  
> &nbsp;&nbsp;&nbsp;&nbsp;top\_units, bot\_units = Units.from\_string( name, self!=1 )  
> &nbsp;&nbsp;&nbsp;&nbsp;Quantity.new( self, top\_units, bot\_units )  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def Units.add\_conversion( q1, q2 )  
> &nbsp;&nbsp;&nbsp;&nbsp;@conversions ||= {}  
> &nbsp;&nbsp;&nbsp;&nbsp;(@conversions[q1.units] ||= {})[q2.units] = 1.0 \* q2.value / q1.value  
> &nbsp;&nbsp;&nbsp;&nbsp;(@conversions[q2.units] ||= {})[q1.units] = 1.0 \* q1.value / q2.value  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def Units.convert( q1, units )  
> &nbsp;&nbsp;&nbsp;&nbsp;convert\_to = ( @conversions ||= {} )[q1.units]  
> &nbsp;&nbsp;&nbsp;&nbsp;if convert\_to && factor = convert\_to[units]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Quantity.new( q1.value \* factor, \*units )  
> &nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise "I don't know how to convert from #{q1.units} to #{units}"  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def Units.from\_string( description, singularize=true )  
> &nbsp;&nbsp;&nbsp;&nbsp;top =   
> &nbsp;&nbsp;&nbsp;&nbsp;bot =   
> &nbsp;&nbsp;&nbsp;&nbsp;section = top  
> &nbsp;&nbsp;&nbsp;&nbsp;description.to\_s.split( '\_' ).each{ |unit|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case unit  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when 'in'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise "Cannot create 'in' units (reserved for conversion)"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when 'per'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;section = bot  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;section \<\< ( singularize ? unit.singular : unit )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;}  
> &nbsp;&nbsp;&nbsp;&nbsp;return [top, bot]  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;class Quantity  
> &nbsp;&nbsp;&nbsp;&nbsp;attr\_reader :value, :units, :top\_units, :bot\_units
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize( value, top\_units=, bot\_units= )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@value = value  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@top\_units = .concat( top\_units )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@bot\_units = .concat( bot\_units )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@units = [@top\_units, @bot\_units]
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Simplify  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;removed =   
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@bot\_units.each\_with\_index{ |divisor, div\_i|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if i = @top\_units.index( divisor )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@top\_units.delete\_at( i )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;removed \<\< div\_i  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless removed.empty?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;removed.each{ |divisor\_index|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@bot\_units.delete\_at( divisor\_index )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@top\_units.sort!  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@bot\_units.sort!  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def dup  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value, @top\_units, @bot\_units )  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def method\_missing( name, \*args )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( name = name.to\_s ) =~ /^in\_/  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Units.convert( self, Units.from\_string( name.sub( /^in\_/, '' ) ) )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;top, bot = Units.from\_string( name )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value, name, @top\_units + top, @bot\_units + bot )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def same\_units\_as?( other )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return false unless other.respond\_to? :units  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.units == other.units  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def combine\_units( \*quantities )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;quantities.each{ |q|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@top\_units.concat( q.top\_units )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@bot\_units.concat( q.bot\_units )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def +( other )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if self.same\_units\_as?( other )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value + other.value, @top\_units, @bot\_units )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Expression.new( self, :+, other )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def -( other )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if self.same\_units\_as?( other )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value - other.value, @top\_units, @bot\_units )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Expression.new( self, :-, other )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def \*( other )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if other.respond\_to? :units  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value \* other.value, @top\_units + other.top\_units, @bot\_units + other.bot\_units )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value \* other, @top\_units, @bot\_units )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def /( other )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if other.respond\_to? :units  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value / other.value, @top\_units + other.bot\_units, @bot\_units + other.top\_units )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.class.new( @value / other, @top\_units, @bot\_units )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wrap = @top\_units.length \> 1 or @bot\_units.length \> 0  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out = wrap ? '(' : ''  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out \<\< "#@value "  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if @value != 1 && @top\_units.length == 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out \<\< @top\_units.first.plural  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out \<\< @top\_units.join( '\*' )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless @bot\_units.empty?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out \<\< '/'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out \<\< @bot\_units.join( '\*' )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out \<\< ')' if wrap  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;class Expression  
> &nbsp;&nbsp;&nbsp;&nbsp;attr\_reader :o1, :op, :o2  
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize( o1, op, o2 )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@o1 = o1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@op = op  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@o2 = o2  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"(#@o1 #@op #@o2)"  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end
> 
> end
> 
> class String  
> &nbsp;&nbsp;def singular  
> &nbsp;&nbsp;&nbsp;&nbsp;self.gsub( /(([hs])e)?s$/, '\2' ).gsub( 'feet', 'foot' )  
> &nbsp;&nbsp;end  
> &nbsp;&nbsp;def plural  
> &nbsp;&nbsp;&nbsp;&nbsp;out = self + ( ( self =~ /[hs]$/ ) ? 'es' : 's' )  
> &nbsp;&nbsp;&nbsp;&nbsp;out.gsub( 'foots', 'feet' )  
> &nbsp;&nbsp;end  
> end
> 
> class Numeric  
> &nbsp;&nbsp;include Units  
> end

... You might want to look at the Mega modules ( [http://mega.rubyforge.org](http://mega.rubyforge.org) ) ... Then you can see the similar functionality they've already built, and maybe where you can fit your stuff in.

j.

---

<div class="post-metadata">

**Author:** ![Adam\_Sanderson](https://avatars.discourse-cdn.com/v4/letter/a/2acd7d/32.png) [@Adam\_Sanderson](https://rubytalk.org/u/Adam_Sanderson)\
**Post date:** [1 September 2005 23:41 UTC](https://rubytalk.org/t/code-sharing-units-abandoned-child/20543/4 "2005-09-01T23:41:24Z")

</div>

Damnit, ruby is too much fun. I was just thinking I was going to  
need to do some simple unit conversions, and behold.

&nbsp;&nbsp;I have seen some other modules that do this, but your code looks very  
natural to use. For the singular and plural issue, check out the way  
they do it in Rails, it works pretty well. I've used it a few times.

I might play with this a little today.  
&nbsp;&nbsp;.adam sanderson

---

<div class="post-metadata">

**Author:** ![Gavin\_Kistner2](https://avatars.discourse-cdn.com/v4/letter/g/b9e5f3/32.png) [@Gavin\_Kistner2](https://rubytalk.org/u/Gavin_Kistner2)\
**Post date:** [1 September 2005 13:27 UTC](https://rubytalk.org/t/code-sharing-units-abandoned-child/20543/5 "2005-09-01T13:27:55Z")

</div>

Er, I must have missed it...where is there a module in that collection that does anything like this?

> **···**
>
> On Aug 31, 2005, at 11:14 PM, Jeff Wood wrote:
> 
> > Gavin Kistner wrote:
> > 
> > > Units.add\_conversion( 60.seconds, 1.minute )  
> > > Units.add\_conversion( 1.mile\_per\_hour, 1.46666667.feet\_per\_second )  
> > > puts ( 32.feet\_per\_second\_second \* 1.5.minutes.in\_seconds ).in\_miles\_per\_hour  
> > > #=\> 1963.63635917355 miles/hour
> > 
> > ... You might want to look at the Mega modules ( [http://mega.rubyforge.org](http://mega.rubyforge.org) ) ... Then you can see the similar functionality they've already built, and maybe where you can fit your stuff in.
