quantity 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ # Units for the most commonly used US units. If you need to
2
+ # worry about the minitae, such as the difference between a
3
+ # fluid pint and a dry pint, pleases see the documentation
4
+ # for the quantity/systems/us modules.
5
+ class Quantity::Unit
6
+ add_unit :foot, :length, 304.8, :ft, :feet
7
+ add_unit :inch, :width, 25.4, :in, :inches
8
+ add_unit :yard, :length, 914.4, :yd, :yards
9
+ add_unit :mile, :length, 1_609_344, :miles
10
+
11
+ add_unit :pound, :mass, 453592.37, :pounds, :lb, :lbs
12
+ add_unit :ounce, :mass, 28349.5231, :ounces, :oz
13
+ add_unit :ton, :mass, 907184740, :tons
14
+
15
+ add_unit :fluid_ounce, :volume, 29.57, :floz, :ozfl
16
+ add_unit :pint, :volume, 473.18, :pint, :pints
17
+ add_unit :quart, :volume, 946.35, :qt, :quarts
18
+ add_unit :gallon, :volume, 3785.41, :gallons, :gal
19
+ end
@@ -1,28 +1,320 @@
1
1
  class Quantity
2
+ # A unit of measurement.
3
+ #
4
+ # Units are a well-defined increment of a measurement domain. Units
5
+ # measure a particular Dimension, which may be base or compound.
6
+ # Examples of units are meters and degrees celius.
7
+ #
8
+ # Units are not quantities, and their associated value only defines
9
+ # their relationship to their measurement domain. For a representation
10
+ # of a given number of units, see Quantity.
11
+ #
12
+ # There is only one type of unit. Units are simply denote a range on
13
+ # their measurement dimension, which may be compound and complicated.
14
+ #
15
+ # Units are implemented in terms of a reference unit for each dimension.
16
+ # The SI milli- unit for each of the base physical dimensions is the
17
+ # reference unit for each of the so-called base quantities.
18
+ #
19
+ # Units are known by a wide variety of abbreviations and names. Each
20
+ # unit is only instantiated once, regardless of what name it is called
21
+ # by. The cannonical name is used internally. A client Quantity
22
+ # object is responsible for remembering which name a unit was originally
23
+ # called as.
24
+ #
2
25
  class Unit
3
- autoload :Length, 'quantity/unit/length'
4
- autoload :Mass, 'quantity/unit/mass'
5
- autoload :Time, 'quantity/unit/time'
6
- autoload :Current, 'quantity/unit/current'
7
- autoload :Temperature, 'quantity/unit/temperature'
8
- autoload :Luminosity, 'quantity/unit/luminosity'
9
- autoload :Substance, 'quantity/unit/substance'
26
+ include Comparable
27
+
28
+ # All known units
29
+ @@units = {}
10
30
 
31
+ # The unit for a given symbol or string description of a compound unit
32
+ # @param [Symbol String Unit] to
33
+ # @return [Unit]
34
+ def self.for(to)
35
+ to.is_a?(Unit) ? to : @@units[to]
36
+ end
37
+
38
+ # Whether or not the given symbol or string refers to an existing unit
39
+ # @param [Symbol String Unit] to
40
+ # @return [Boolean]
41
+ def self.is_unit?(to)
42
+ to.is_a?(Unit) || @@units.has_key?(to)
43
+ end
44
+
45
+ # Register a unit with the given symbols
46
+ # @param [Unit] unit
47
+ # @param [*names]
48
+ def self.add_alias(unit,*names)
49
+ unit = Unit.for(unit) unless unit.is_a? Unit
50
+ names.each do |name|
51
+ @@units[name] = unit
52
+ end
53
+ end
54
+
55
+ # Add a unit to the system
56
+ # @param [Dimension] dimension
57
+ # @param [Symbol] name
58
+ # @param [Numeric] value
59
+ # @param [[String Symbol]] *aliases
60
+ def self.add_unit(name,dimension,value,*names)
61
+ new_unit = Unit.new({ :name => name,:dimension => Quantity::Dimension.for(dimension),:value => value})
62
+ names.each do | name |
63
+ add_alias new_unit, name
64
+ end
65
+ end
66
+ class << self ; alias_method :add, :add_unit; end
67
+
68
+ # Add a number of units to the system
69
+ # @example
70
+ # length = Quantity::Dimension.for(:length)
71
+ # Quantity::Unit.add_units do
72
+ # add :meter length 1000
73
+ # add :mm :length 1
74
+ # end
75
+ def self.add_units(&block)
76
+ self.class_eval(&block)
77
+ end
78
+
79
+ ### Instance-level methods/vars
80
+ attr_reader :name, :value, :dimension, :aliases
81
+
82
+ # All the known aliases for this Unit, i.e. name + aliases
83
+ # @return [[Symbol String]]
84
+ def names
85
+ [@name] + @aliases
86
+ end
87
+
88
+ # Can this unit be converted into the target unit?
89
+ # @param [Symbol String Unit]
90
+ # @return [Boolean]
91
+ def can_convert_to?(to)
92
+ Unit.for(to).dimension == @dimension
93
+ end
94
+
95
+ # Return the unit this unit will convert to.
96
+ # It's sometimes necessary to let the unit decide, in case a conversion such as
97
+ # meters^2 -> feet is requested, for which feet^2 should be returned.
98
+ # @param [Symbol String Unit]
99
+ # @return [Unit]
100
+ def convert(to)
101
+ Unit.for(to)
102
+ end
103
+
104
+ # Return a proc that will perform conversion from this unit to the given one
105
+ # @param [Symbol String Unit]
106
+ # @return [Unit]
107
+ def convert_proc(to)
108
+ to = convert(to)
109
+ #to = Unit.for(to)
110
+ raise ArgumentError, "Unable to find unit #{to}" unless to
111
+ unless (to.dimension == self.dimension)
112
+ raise ArgumentError, "Cannot convert #{self.dimension} to #{to.dimension}"
113
+ end
114
+ if defined?(Rational) && (@value.is_a?(Fixnum)) && (to.value.is_a?(Fixnum))
115
+ lambda do | from |
116
+ from * Rational(@value, to.value)
117
+ end
118
+ elsif defined?(Rational) && (@value.is_a?(Rational)) && (to.value.is_a?(Rational))
119
+ lambda do | from |
120
+ from * @value / to.value
121
+ end
122
+ else
123
+ lambda do | from |
124
+ from * (@value / to.value.to_f)
125
+ end
126
+ end
127
+ end
128
+
129
+ # The value for a given reference value.
130
+ # @example
131
+ # Unit.add_unit :meter, :length, :1000
132
+ # Unit.for(:meter).value_for(5000) = 5
133
+ # @param [Numeric] value
134
+ # @return [Numeric]
135
+ def value_for(reference_value)
136
+ if defined?(Rational) && (reference_value.is_a?(Fixnum)) && (@value.is_a?(Fixnum))
137
+ Rational(reference_value, @value)
138
+ elsif defined?(Rational) && (reference_value.is_a?(Rational) || reference_value.is_a?(Fixnum)) && (@value.is_a?(Rational))
139
+ reference_value / @value #Rational(reference_value, @value)
140
+ else
141
+ reference_value / @value.to_f
142
+ end
143
+ end
144
+
145
+ # A string representation of this unit at the given value
146
+ # @param [Any] value
11
147
  # @return [String]
12
- attr_reader :name
148
+ def s_for(value)
149
+ "#{value} #{@name.to_s}"
150
+ end
13
151
 
14
- alias_method :to_s, :name
152
+ def inspect
153
+ "<Unit #{@name} (#{@object_id}), value #{@value}, dimension #{@dimension}>"
154
+ end
15
155
 
16
- ##
17
- # @param [String] name
18
- def initialize(name = nil)
19
- @name = name || self.class.name.split(':').last.downcase
156
+ def <=>(other)
157
+ if other.is_a?(Unit) && other.dimension == @dimension
158
+ @value <=> other.value
159
+ elsif other.is_a?(Unit)
160
+ @name <=> other.name
161
+ else
162
+ nil
163
+ end
164
+ end
165
+
166
+ # Exponentiation
167
+ # @param other [Numeric]
168
+ # @return [Unit]
169
+ def **(other)
170
+ if other.is_a?(Fixnum) && other > 0
171
+ other == 1 ? self : self * self**(other-1)
172
+ else
173
+ raise ArgumentError, "#{self} cannot be raised to #{other} power."
174
+ end
175
+ end
176
+
177
+ # Unit multiplication.
178
+ # @param [Unit] other
179
+ # @return [Unit]
180
+ def *(other)
181
+ if other.is_a?(Unit)
182
+ units = other.units || { other.dimension => other }
183
+ units.merge!(@units || { @dimension => self })
184
+ dim = @dimension * other.dimension
185
+ existing = Unit.for(Unit.string_form(dim,units).to_sym)
186
+ existing ||= Unit.new({ :dimension => dim, :units => units })
187
+ else
188
+ raise ArgumentError, "Cannot multiply #{self} with #{other}"
189
+ end
190
+ end
191
+
192
+ # Unit division.
193
+ # @param [Unit] other
194
+ # @return [Unit]
195
+ def /(other)
196
+ if other.is_a?(Unit)
197
+ units = other.units || { other.dimension => other }
198
+ units.merge!(@units || { @dimension => self })
199
+ dim = @dimension / other.dimension
200
+ existing = Unit.for(Unit.string_form(dim,units).to_sym)
201
+ existing ||= Unit.new({ :dimension => dim, :units => units })
202
+ existing
203
+ else
204
+ raise ArgumentError, "Cannot multiply #{self} with #{other}"
205
+ end
206
+ end
207
+
208
+ # Convert a portion of this compound to another unit.
209
+ # This one is tricky, because a lot of things can be happening.
210
+ # It's valid to convert m^2 to ft^2 and to feet (ft^2), but not
211
+ # really valid to convert to ft^3.
212
+ # @param [Symbol Unit] to
213
+ # @return [Unit]
214
+ def convert(target)
215
+ to = Unit.from_string_form(target)
216
+ if (to.dimension == @dimension)
217
+ to
218
+ elsif @units && @units[to.dimension]
219
+ units = @units.merge({ to.dimension => to })
220
+ unit = Unit.for(Unit.string_form(@dimension,units).to_sym)
221
+ unit ||= Unit.new({ :dimension => @dimension, :units => units })
222
+ unit
223
+ else
224
+ raise ArgumentError, "Cannot convert #{self} to #{target}"
225
+ end
226
+ end
227
+
228
+ # Parse a string representation of a unit, such as foot^2/time^2, and return
229
+ # a compound object representing it.
230
+ def self.from_string_form(to)
231
+ if Unit.for(to)
232
+ Unit.for(to)
233
+ else
234
+ dimension_string = to.to_s.dup
235
+ units = {}
236
+ to.to_s.split(/(\^|\/|\*)/).each do | name |
237
+ next if name =~ /(\^|\/|\*)/ || name =~ /^\d$/
238
+ unit = Unit.for(name.to_sym) || Unit.for(name)
239
+ dimension_string.gsub!(name,unit.dimension.name.to_s)
240
+ units[unit.dimension] = unit
241
+ end
242
+ dimension = Dimension.for(dimension_string.to_sym)
243
+ raise ArgumentError, "Couldn't create Unit for #{to}" unless dimension && units
244
+ unit = Unit.new({ :dimension => dimension, :units => units })
245
+ add_alias(unit,unit.name.to_sym)
246
+ unit
247
+ end
248
+ end
249
+
250
+
251
+ # Higher-order units have a set of units to reference each aspect of the dimension they
252
+ # measure. This is unused in basic units.
253
+ attr_reader :units
254
+
255
+ # A new compound unit. There are two modes of operation. One provides a way to add units with
256
+ # a DSL. The other provides an options hash a little better for programming. The last provides
257
+ # a way to create a unit for a given dimension--useful for reference units.
258
+ #
259
+ # @overload initialize(opts = {})
260
+ # @param opts [String Symbol] :name
261
+ # @param opts [[Unit]] :units
262
+ # @param opts [Dimension] :dimension
263
+ # @return [Unit]
264
+ def initialize(opts)
265
+ @units = opts[:units]
266
+ @dimension = opts[:dimension]
267
+ @value = opts[:value] || calculate_value
268
+ if @dimension.nil?
269
+ raise ArgumentError, "Adding invalid unit with nil dimension (#{name} - #{dimension})"
270
+ end
271
+ unless opts[:name] || !@dimension.is_base?
272
+ raise ArgumentError, "Single-order units must be uniquely named (#{name} - #{dimension})"
273
+ end
274
+ @name = opts[:name] || string_form
275
+ self.class.add_alias(self,@name.to_sym)
276
+ raise ArgumentError, "Creating new unit with no value" unless @value
277
+ end
278
+
279
+ # calculate this unit's value compared to the reference unit
280
+ def calculate_value
281
+ value = defined?(Rational) ? Rational(1) : 1.0
282
+ @dimension.numerators.each do | component |
283
+ component.power.times do
284
+ # we might have a unit for a compound dimension, such as liters for length^3.
285
+ value *= @units[Quantity::Dimension.for(component.dimension)].value
286
+ end
287
+ end
288
+ @dimension.denominators.each do | component |
289
+ component.power.times do
290
+ value /= @units[Quantity::Dimension.for(component.dimension)].value
291
+ end
292
+ end
293
+ @value = value
294
+ end
295
+
296
+ # A vaguely human-readable form for this unit
297
+ # @return [String]
298
+ def string_form
299
+ self.class.string_form(@dimension,@units)
300
+ end
301
+
302
+ # a vaguely human-readable format for a compound unit
303
+ # @param [Dimension] dimension
304
+ # @param [{}] units
305
+ # @return [String]
306
+ def self.string_form(dimension,units)
307
+ string = dimension.string_form
308
+ units.each do | dimension, unit |
309
+ string = string.gsub(dimension.name.to_s, unit.name.to_s)
310
+ end
311
+ string
20
312
  end
21
313
 
22
- ##
23
- # @return [Symbol]
24
- def to_sym
25
- name.to_sym
314
+ # Reset the world. Useful in testing.
315
+ # @private
316
+ def self.__reset!
317
+ @@units = {}
26
318
  end
27
319
  end
28
320
  end
@@ -1,7 +1,7 @@
1
1
  class Quantity
2
2
  module VERSION
3
3
  MAJOR = 0
4
- MINOR = 0
4
+ MINOR = 1
5
5
  TINY = 0
6
6
  EXTRA = nil
7
7
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quantity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Lavender
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-12-29 00:00:00 +01:00
13
+ date: 2010-01-05 00:00:00 +01:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -33,7 +33,7 @@ dependencies:
33
33
  - !ruby/object:Gem::Version
34
34
  version: 0.5.2
35
35
  version:
36
- description: Units and quantities for Ruby.
36
+ description: " Quantity provides first-class quantities, units, and base quantities in pure ruby.\n Things like 1.meter / 1.second == 1 meter/second.\n"
37
37
  email: blavender@gmail.com
38
38
  executables: []
39
39
 
@@ -46,13 +46,14 @@ files:
46
46
  - README
47
47
  - UNLICENSE
48
48
  - VERSION
49
- - lib/quantity/unit/current.rb
50
- - lib/quantity/unit/length.rb
51
- - lib/quantity/unit/luminosity.rb
52
- - lib/quantity/unit/mass.rb
53
- - lib/quantity/unit/substance.rb
54
- - lib/quantity/unit/temperature.rb
55
- - lib/quantity/unit/time.rb
49
+ - lib/quantity/all.rb
50
+ - lib/quantity/dimension/base.rb
51
+ - lib/quantity/dimension.rb
52
+ - lib/quantity/systems/enumerable.rb
53
+ - lib/quantity/systems/imperial.rb
54
+ - lib/quantity/systems/information.rb
55
+ - lib/quantity/systems/si.rb
56
+ - lib/quantity/systems/us.rb
56
57
  - lib/quantity/unit.rb
57
58
  - lib/quantity/version.rb
58
59
  - lib/quantity.rb
@@ -1,9 +0,0 @@
1
- class Quantity
2
- class Unit
3
- ##
4
- # @see http://en.wikipedia.org/wiki/Electric_current
5
- class Current < Unit
6
- # TODO
7
- end
8
- end
9
- end
@@ -1,9 +0,0 @@
1
- class Quantity
2
- class Unit
3
- ##
4
- # @see http://en.wikipedia.org/wiki/Length
5
- class Length < Unit
6
- # TODO
7
- end
8
- end
9
- end
@@ -1,9 +0,0 @@
1
- class Quantity
2
- class Unit
3
- ##
4
- # @see http://en.wikipedia.org/wiki/Luminous_intensity
5
- class Luminosity < Unit
6
- # TODO
7
- end
8
- end
9
- end
@@ -1,9 +0,0 @@
1
- class Quantity
2
- class Unit
3
- ##
4
- # @see http://en.wikipedia.org/wiki/Mass
5
- class Mass < Unit
6
- # TODO
7
- end
8
- end
9
- end