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.
- data/README +204 -0
- data/VERSION +1 -1
- data/lib/quantity.rb +365 -6
- data/lib/quantity/all.rb +8 -0
- data/lib/quantity/dimension.rb +269 -0
- data/lib/quantity/dimension/base.rb +54 -0
- data/lib/quantity/systems/enumerable.rb +12 -0
- data/lib/quantity/systems/imperial.rb +10 -0
- data/lib/quantity/systems/information.rb +30 -0
- data/lib/quantity/systems/si.rb +105 -0
- data/lib/quantity/systems/us.rb +19 -0
- data/lib/quantity/unit.rb +309 -17
- data/lib/quantity/version.rb +1 -1
- metadata +11 -10
- data/lib/quantity/unit/current.rb +0 -9
- data/lib/quantity/unit/length.rb +0 -9
- data/lib/quantity/unit/luminosity.rb +0 -9
- data/lib/quantity/unit/mass.rb +0 -9
- data/lib/quantity/unit/substance.rb +0 -9
- data/lib/quantity/unit/temperature.rb +0 -9
- data/lib/quantity/unit/time.rb +0 -9
@@ -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
|
data/lib/quantity/unit.rb
CHANGED
@@ -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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
148
|
+
def s_for(value)
|
149
|
+
"#{value} #{@name.to_s}"
|
150
|
+
end
|
13
151
|
|
14
|
-
|
152
|
+
def inspect
|
153
|
+
"<Unit #{@name} (#{@object_id}), value #{@value}, dimension #{@dimension}>"
|
154
|
+
end
|
15
155
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
# @
|
24
|
-
def
|
25
|
-
|
314
|
+
# Reset the world. Useful in testing.
|
315
|
+
# @private
|
316
|
+
def self.__reset!
|
317
|
+
@@units = {}
|
26
318
|
end
|
27
319
|
end
|
28
320
|
end
|
data/lib/quantity/version.rb
CHANGED
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.
|
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:
|
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:
|
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/
|
50
|
-
- lib/quantity/
|
51
|
-
- lib/quantity/
|
52
|
-
- lib/quantity/
|
53
|
-
- lib/quantity/
|
54
|
-
- lib/quantity/
|
55
|
-
- lib/quantity/
|
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
|
data/lib/quantity/unit/length.rb
DELETED