eymiha_units 0.1.0
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/gem_package.rb +34 -0
- data/lib/units/definitions/area.rb +5 -0
- data/lib/units/definitions/length.rb +34 -0
- data/lib/units/definitions/mass.rb +16 -0
- data/lib/units/definitions/measures.rb +6 -0
- data/lib/units/definitions/time.rb +61 -0
- data/lib/units/definitions/velocity.rb +9 -0
- data/lib/units/definitions/volume.rb +27 -0
- data/lib/units/numeric.rb +88 -0
- data/lib/units/numeric_with_units.rb +635 -0
- data/lib/units/object.rb +79 -0
- data/lib/units/units.rb +229 -0
- data/lib/units/units_exception.rb +14 -0
- data/lib/units/units_hash.rb +112 -0
- data/lib/units/units_measure.rb +91 -0
- data/lib/units/units_system.rb +85 -0
- data/lib/units/units_unit.rb +60 -0
- data/lib/units.rb +4 -0
- data/rakefile.rb +2 -0
- data/test/framework.rb +7 -0
- data/test/tc_definitions.rb +49 -0
- data/test/tc_formatting.rb +41 -0
- data/test/tc_formatting_derived.rb +73 -0
- data/test/tc_measure_create.rb +59 -0
- data/test/tc_measure_derive.rb +46 -0
- data/test/tc_system_create.rb +31 -0
- data/test/tc_unit_ambiguity.rb +46 -0
- data/test/tc_unit_arithmetic.rb +41 -0
- data/test/tc_unit_create.rb +35 -0
- data/test/tc_unit_derive.rb +87 -0
- data/test/tc_unit_equality.rb +54 -0
- data/test/tc_unit_forward_reference.rb +44 -0
- data/test/tc_unit_greek.rb +163 -0
- data/test/tc_unit_hash.rb +66 -0
- data/test/tc_unit_identifiers.rb +48 -0
- data/test/tc_unit_rank.rb +53 -0
- data/test/tc_uses_1.rb +212 -0
- data/test/tc_uses_2.rb +149 -0
- metadata +118 -0
data/lib/units/units.rb
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'eymiha'
|
2
|
+
|
3
|
+
require 'forward_referencing'
|
4
|
+
|
5
|
+
require 'units/units_measure'
|
6
|
+
require 'units/units_exception'
|
7
|
+
require 'units/numeric'
|
8
|
+
require 'units/numeric_with_units'
|
9
|
+
require 'units/units_hash'
|
10
|
+
require 'units/object'
|
11
|
+
|
12
|
+
# The Units framework
|
13
|
+
#
|
14
|
+
# Units is the top level that you'll typically never have to deal with
|
15
|
+
# directly. While it provides a few chunks of general functionality such
|
16
|
+
# as getting defined units and ranking, most of its guts are devoted to
|
17
|
+
# defining new UnitsMeasures - unless you're setting up your own sets of
|
18
|
+
# specialized units, you will hardly know it's here.
|
19
|
+
class Units
|
20
|
+
|
21
|
+
@@debug = false
|
22
|
+
|
23
|
+
def self.debug=(value)
|
24
|
+
@@debug = value
|
25
|
+
end
|
26
|
+
|
27
|
+
extend ForwardReferencing
|
28
|
+
start_forward_referencing
|
29
|
+
|
30
|
+
@@measures = {}
|
31
|
+
@@units = {}
|
32
|
+
@@defining = nil
|
33
|
+
@@holding = nil
|
34
|
+
|
35
|
+
# Returns an Array of the defined UnitsMeasures.
|
36
|
+
def Units.units_measures
|
37
|
+
@@measures.keys
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates or extends a UnitsMeasure.
|
41
|
+
def Units.create(name, &block)
|
42
|
+
measure = (@@measures[name.to_s] ||= UnitsMeasure.new)
|
43
|
+
block.call measure if block_given?
|
44
|
+
measure
|
45
|
+
end
|
46
|
+
|
47
|
+
# Creates or extends a UnitsMeasure derived from other UnitsMeasures.
|
48
|
+
def Units.derive(name, target, &block)
|
49
|
+
measure = ( @@measures[name.to_s] =
|
50
|
+
if target.kind_of? UnitsMeasure
|
51
|
+
if target.derived && (derived = find_by_derivation target.derived)
|
52
|
+
derived
|
53
|
+
else
|
54
|
+
target
|
55
|
+
end
|
56
|
+
else
|
57
|
+
Units.create(target.to_s)
|
58
|
+
end )
|
59
|
+
block.call measure if block_given?
|
60
|
+
measure
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the UnitsMeasure with the given derivation.
|
64
|
+
def Units.find_by_derivation(derivation)
|
65
|
+
matches = @@measures.values.uniq.select { |measure|
|
66
|
+
if measure.derived
|
67
|
+
measure == derivation
|
68
|
+
elsif derivation.size == 1
|
69
|
+
a = derivation.to_a[0]
|
70
|
+
a[0] == measure and a[1] == 1
|
71
|
+
else
|
72
|
+
false
|
73
|
+
end
|
74
|
+
}
|
75
|
+
case matches.size
|
76
|
+
when 0 then nil
|
77
|
+
when 1 then matches[0]
|
78
|
+
else raise UnitsException.new(
|
79
|
+
"Multiple UnitsMeasures with same derivation found")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Removes the named UnitsMeasure from the Units framework.
|
84
|
+
def Units.delete(name)
|
85
|
+
@@measures.delete name.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
# Clears the Units framework of all defined elements.
|
89
|
+
def Units.clear
|
90
|
+
@@measures.clear
|
91
|
+
@@units.clear
|
92
|
+
forward_references_clear
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns the number of defined UnitsMeasures.
|
97
|
+
def Units.size
|
98
|
+
@@measures.size
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the named UnitsMeasure.
|
102
|
+
def Units.[](name)
|
103
|
+
@@measures[name.to_s]
|
104
|
+
end
|
105
|
+
|
106
|
+
def Units.method_missing(method,*args) # :nodoc:
|
107
|
+
measure = self[method]
|
108
|
+
raise UnitsException.new("UnitsMeasure '#{method}' undefined") if !measure
|
109
|
+
measure
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns an Array containing the names of a given UnitsMeasure.
|
113
|
+
def Units.names_of(units_measure)
|
114
|
+
@@measures.keys.select { |name| @@measures[name].equal? units_measure }
|
115
|
+
end
|
116
|
+
|
117
|
+
def Units.add_unit(unit,unit_identifier=nil) # :nodoc:
|
118
|
+
if unit_identifier
|
119
|
+
if (element = @@units[unit_identifier])
|
120
|
+
@@units[unit_identifier] += [ unit ] unless element.index(unit)
|
121
|
+
else
|
122
|
+
@@units[unit_identifier] = [ unit ]
|
123
|
+
end
|
124
|
+
else
|
125
|
+
add_unit unit, unit.name
|
126
|
+
add_unit unit, unit.plural
|
127
|
+
unit.abbrevs.each { |abbrev| add_unit unit, abbrev }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns an Array of UnitsUnit associated with a singular, plural or
|
132
|
+
# abbreviated name.
|
133
|
+
def Units.lookup(unit_identifier)
|
134
|
+
@@units[unit_identifier.to_s] || [ ]
|
135
|
+
end
|
136
|
+
|
137
|
+
# Answers the question of whether a UnitsMeasure is being defined with the
|
138
|
+
# instance if true, or nil if false.
|
139
|
+
def Units.defining?
|
140
|
+
@@defining
|
141
|
+
end
|
142
|
+
|
143
|
+
def Units.defining(measure) # :nodoc:
|
144
|
+
@@defining = measure
|
145
|
+
end
|
146
|
+
|
147
|
+
def Units.convert(numeric,unit_identifier) # :nodoc:
|
148
|
+
puts "Units:convert #{numeric} #{unit_identifier}" if @@debug
|
149
|
+
if (candidates = lookup(unit_identifier)).size == 0
|
150
|
+
puts @@units.keys.sort.join(" ") if @@debug
|
151
|
+
puts " no candidates!" if @@debug
|
152
|
+
raise MissingUnitsException.new(unit_identifier.to_s)
|
153
|
+
elsif !defining?
|
154
|
+
if candidates.size > 1
|
155
|
+
raise AmbiguousUnitsException.new(unit_identifier.to_s)
|
156
|
+
else
|
157
|
+
unit = candidates[0]
|
158
|
+
NumericWithUnits.new(numeric,unit)
|
159
|
+
end
|
160
|
+
else
|
161
|
+
if candidates.size == 1
|
162
|
+
units = candidates
|
163
|
+
else
|
164
|
+
units = candidates.select { |candidate|
|
165
|
+
@@defining == candidate.units_system.units_measure }
|
166
|
+
units = candidates.select { |candidate|
|
167
|
+
@@defining.derived[candidate.units_measure] } if units.size == 0
|
168
|
+
end
|
169
|
+
case units.size
|
170
|
+
when 0 then
|
171
|
+
raise MissingUnitsException.new(unit_identifier.to_s)
|
172
|
+
when 1 then
|
173
|
+
unit = units[0]
|
174
|
+
if unit.equals.kind_of? Array
|
175
|
+
element = unit.equals[0]
|
176
|
+
value = NumericWithUnits.
|
177
|
+
new(numeric*element.numeric,element.unit)
|
178
|
+
else
|
179
|
+
value = NumericWithUnits.
|
180
|
+
new(numeric*unit.equals.numeric,unit.equals.unit)
|
181
|
+
end
|
182
|
+
value.original = numeric.unite(unit)
|
183
|
+
value
|
184
|
+
else
|
185
|
+
raise AmbiguousUnitsException.new(unit_identifier.to_s)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def Units.make_forward_reference(method,context) # :nodoc:
|
191
|
+
@@holding ? nil : create_forward_reference(method,context)
|
192
|
+
end
|
193
|
+
|
194
|
+
def Units.release_forward_reference(reference = nil) # :nodoc:
|
195
|
+
remove_forward_reference(reference) if reference != nil
|
196
|
+
end
|
197
|
+
|
198
|
+
def Units.hold_forward_reference(hold = true) # :nodoc:
|
199
|
+
@@holding = hold
|
200
|
+
end
|
201
|
+
|
202
|
+
def Units.holding_forward_reference? # :nodoc:
|
203
|
+
@@holding
|
204
|
+
end
|
205
|
+
|
206
|
+
def Units.establish_forward_reference_context(context) # :nodoc:
|
207
|
+
defining context
|
208
|
+
end
|
209
|
+
|
210
|
+
# Given a Hash of units to raking weights, a set of samples, and
|
211
|
+
# optionally a block that returns rankings, returns an Array of units
|
212
|
+
# ordered by best weighted fit over the samples. Like golf, low scores
|
213
|
+
# rank best. In absence of a block, ranking is based on the number of
|
214
|
+
# unit values whose magnitudes are between 1 and 10.
|
215
|
+
def Units.rank(unit_choices = {}, samples = [], &numeric_ranker) # :yields: n
|
216
|
+
if block_given?
|
217
|
+
scores = {}
|
218
|
+
samples.each {|s|
|
219
|
+
unit_choices.each {|uc,w|
|
220
|
+
scores[uc] = (scores[uc] || 0) + w*(yield s.convert(uc).numeric) } }
|
221
|
+
scores.sort {|e1,e2| -(e1[1] <=> e2[1])}.collect {|s| s[0]}
|
222
|
+
else
|
223
|
+
rank(unit_choices,samples) { |n|
|
224
|
+
e = ((((n.abs)-5.5).abs-4.5).at_least(0))
|
225
|
+
(e == 0) ? 0 : (e == 1)? 0 : -(e + 1/(1-e)) }
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Raised when a general problem has occurred in the Units framework or some
|
2
|
+
# other error has occurred during its use.
|
3
|
+
class UnitsException < Exception
|
4
|
+
end
|
5
|
+
|
6
|
+
|
7
|
+
# Raised when a unit was expected but was not found.
|
8
|
+
class MissingUnitsException < UnitsException
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
# Raised when a unit was expected but more than one was found.
|
13
|
+
class AmbiguousUnitsException < UnitsException
|
14
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require "units/units"
|
2
|
+
|
3
|
+
# The unit part of a NumericWithUnits is a UnitsHash - a Hash from UnitsUnit
|
4
|
+
# instances to powers.
|
5
|
+
class UnitsHash < Hash
|
6
|
+
|
7
|
+
@@debug = false
|
8
|
+
|
9
|
+
def self.debug=(value)
|
10
|
+
@@debug = value
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(unit = nil,power = 1) # :nodoc
|
14
|
+
if unit
|
15
|
+
if unit.kind_of? UnitsUnit
|
16
|
+
self[unit] = power
|
17
|
+
elsif unit.kind_of? UnitsHash
|
18
|
+
unit.each { |k,v| self[k] = v*power }
|
19
|
+
else
|
20
|
+
raise UnitsException.new("invalid unit: #{unit}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a String reprentation of the instance. If there is only one item in
|
26
|
+
# the UnitsHash, whole names are rendered; otherwise abbreviations are used.
|
27
|
+
def to_s(numeric = 1,use_abbrevs = false)
|
28
|
+
if size == 1 &&
|
29
|
+
(su = select {|k,v| v == 1}).size == 1 &&
|
30
|
+
!use_abbrevs
|
31
|
+
su = su.to_a[0][0]
|
32
|
+
((numeric == 1)? su.name : su.plural).gsub(/_/,' ')
|
33
|
+
else
|
34
|
+
abbrevs_to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def abbrevs_to_s # :nodoc:
|
39
|
+
p = select {|k,v| v > 0}.sort{|e1,e2| e1[1] <=> e2[1]}.collect {|e|
|
40
|
+
"#{e[0].abbrevs[0] || e[0].name}#{(e[1] == 1) ? "" : "^#{e[1]}"}"}
|
41
|
+
n = select {|k,v| v < 0}.sort{|e1,e2| e1[1] <=> e2[1]}.collect {|e|
|
42
|
+
"#{e[0].abbrevs[0] || e[0].name}#{(e[1] == -1) ? "" : "^#{-e[1]}"}"}
|
43
|
+
numerator = (p.size > 0)? p.join(" ") : (n.size > 0)? "1" : ""
|
44
|
+
denominator = (n.size > 0)? ((p.size > 0)? " / " : "/")+n.join(" ") : ""
|
45
|
+
"#{numerator}#{denominator}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Raises and returns the instance after each of its exponents has been raised
|
49
|
+
# by the given power.
|
50
|
+
def power!(power)
|
51
|
+
self.each { |k,v| self[k] = v*power }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a new UnitsHash whose value is the instance raised to the given
|
55
|
+
# power.
|
56
|
+
def **(power)
|
57
|
+
clone.power!(power)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns a new UnitsHash whose value is the instance's units and powers have
|
61
|
+
# been merged with the given value raised to the power.
|
62
|
+
def merge(value,power=1)
|
63
|
+
clone.merge!(value,power)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Merges and returns the instance after the value raised to the power has
|
67
|
+
# been merged into it.
|
68
|
+
def merge!(value,power=1)
|
69
|
+
value.unit.each { |k,v|
|
70
|
+
self[k] = (self[k] || 0) + v*power
|
71
|
+
delete k if self[k] == 0
|
72
|
+
}
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
alias :* :merge
|
77
|
+
|
78
|
+
# Returns the UnitsMeasure of the instance if defined.
|
79
|
+
def measure
|
80
|
+
measure = UnitsMeasure.new
|
81
|
+
each { |unit,power|
|
82
|
+
measure.merge_derivation unit.units_system.units_measure => power }
|
83
|
+
Units.find_by_derivation(measure.derived)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns true is any of the instance's components are derived.
|
87
|
+
def derived?
|
88
|
+
keys.select{|unit| unit.units_system.units_measure.derived != nil}.size > 0
|
89
|
+
end
|
90
|
+
|
91
|
+
# Return a NumericWithUnits that represents the instance when all derived
|
92
|
+
# units have been replaced with the units from which they derive.
|
93
|
+
def reduce
|
94
|
+
puts "UnitsHash:reduce #{to_s}" if @@debug
|
95
|
+
factor = 1.0
|
96
|
+
new_unit = UnitsHash.new
|
97
|
+
each do |unit,power|
|
98
|
+
puts " #{unit} #{power}" if @@debug
|
99
|
+
factor *= unit.equals.numeric**power
|
100
|
+
puts " #{factor}" if @@debug
|
101
|
+
new_unit.merge!(unit.equals,power)
|
102
|
+
end
|
103
|
+
factor.unite new_unit
|
104
|
+
end
|
105
|
+
|
106
|
+
# Return true if the instance is not equal to 1, ie. has at least one non-zero
|
107
|
+
# exponent.
|
108
|
+
def has_units?
|
109
|
+
(self.select {|k,v| v != 0.0}).size > 0
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'units/units_system'
|
2
|
+
require 'methodic_hash'
|
3
|
+
|
4
|
+
# A UnitsMeasure groups sets of units that measure the same quality.
|
5
|
+
class UnitsMeasure < MethodicHash
|
6
|
+
|
7
|
+
# A Hash of UnitsMeasures to powers that represent the bases for this
|
8
|
+
# UnitsMeasure if it is derived.
|
9
|
+
attr_reader :derived
|
10
|
+
attr_writer :derived # :nodoc:
|
11
|
+
|
12
|
+
# A Methodic Hash mapping names of formats to procs that implement them.
|
13
|
+
attr_reader :formats
|
14
|
+
|
15
|
+
def initialize # :nodoc:
|
16
|
+
@formats = MethodicHash.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Defines or extends a UnitsSystem. During definition, if forward
|
20
|
+
# references between entities may occur and when encountered, are stored.
|
21
|
+
# Upon completion of the definition, if any existing unresolved still
|
22
|
+
# exist, an attempt is made to resolve them.
|
23
|
+
def system(name,&block)
|
24
|
+
Units.defining self
|
25
|
+
system = (self[name] ||= UnitsSystem.new(self,name))
|
26
|
+
block.call system if block_given?
|
27
|
+
Units.resolve_forward_references
|
28
|
+
Units.defining nil
|
29
|
+
system
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the names by which this UnitsMeasure is known.
|
33
|
+
def names
|
34
|
+
Units.names_of self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a new UnitsMeasure equal to the instance raised to the given
|
38
|
+
# power. This is typically used to derive UnitsMeasures - for example,
|
39
|
+
# if length is a UnitsMeasure, then length**3 could be used as the target
|
40
|
+
# to derive volume.
|
41
|
+
def **(exponent)
|
42
|
+
UnitsMeasure.new.merge_derivation(self => exponent)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a new UnitsMeasure equal to the instance divided by another
|
46
|
+
# UnitsMeasure. This is typically used to derive UnitsMeasures - for
|
47
|
+
# example, if mass and volume are UnitsMeasures, then mass/volume could be
|
48
|
+
# used as the target to derive density.
|
49
|
+
def /(divisor)
|
50
|
+
UnitsMeasure.new.merge_derivation(self).merge_derivation(divisor,-1)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns a new UnitsMeasure equal to the instance multiplied by another
|
54
|
+
# UnitsMeasure. This is typically used to derive UnitsMeasures - for
|
55
|
+
# example, if length is a UnitsMeasure, then length*length could be
|
56
|
+
# used as the target to derive area.
|
57
|
+
def *(factor)
|
58
|
+
UnitsMeasure.new.merge_derivation(self).merge_derivation(factor)
|
59
|
+
end
|
60
|
+
|
61
|
+
def merge_derivation derivation, multiplier=1 # :nodoc:
|
62
|
+
current = (self.derived ||= {})
|
63
|
+
if derivation.kind_of? UnitsMeasure
|
64
|
+
if derivation.derived
|
65
|
+
derivation.derived.each {|key,value|
|
66
|
+
current[key] = (current[key] || 0) + multiplier*value }
|
67
|
+
else
|
68
|
+
current[derivation] = (current[derivation] || 0) + multiplier
|
69
|
+
end
|
70
|
+
elsif derivation.kind_of? Hash
|
71
|
+
derivation.each {|key,value|
|
72
|
+
current[key] = ((current[key] || 0 ) + multiplier*value) }
|
73
|
+
else
|
74
|
+
raise UnitsException.new(
|
75
|
+
"Cannot add #{derivation.self_name} to derivation")
|
76
|
+
end
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a String containing the names of this UnitsMeasure and the
|
81
|
+
# namse of the UnitsSystems defined within it.
|
82
|
+
def to_s
|
83
|
+
"#{self_name} #{names} #{keys}"
|
84
|
+
end
|
85
|
+
|
86
|
+
# Associates the name of a format with an output formatter.
|
87
|
+
def format(options)
|
88
|
+
@formats[options[:name]] = options[:format]
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'units/units_unit'
|
2
|
+
require 'methodic_hash'
|
3
|
+
|
4
|
+
# A UnitsSystem groups units that measure the same quality and are
|
5
|
+
# conceptually organized together within a UnitsMeasure. Its a MethodicHash
|
6
|
+
# of unit names to UnitsUnit instances.
|
7
|
+
class UnitsSystem < MethodicHash
|
8
|
+
|
9
|
+
# The UnitsMeasure to which this instance is associated.
|
10
|
+
attr_reader :units_measure
|
11
|
+
# The name of this UnitsSystem
|
12
|
+
attr_reader :name
|
13
|
+
|
14
|
+
def initialize(units_measure,name) # :nodoc:
|
15
|
+
@units_measure = units_measure
|
16
|
+
@name = name
|
17
|
+
end
|
18
|
+
|
19
|
+
# Defines a new UnitsUnit. If ten-based or two-based greeking is defined,
|
20
|
+
# it is applied.
|
21
|
+
def unit(attributes)
|
22
|
+
unit = nil
|
23
|
+
if !Units.holding_forward_reference?
|
24
|
+
unit = UnitsUnit.new self, attributes
|
25
|
+
self[unit.name] = unit
|
26
|
+
Units.add_unit unit
|
27
|
+
case unit.greek
|
28
|
+
when :ten then greek_ten unit
|
29
|
+
when :two then greek_two unit
|
30
|
+
end
|
31
|
+
else
|
32
|
+
Units.hold_forward_reference nil
|
33
|
+
end
|
34
|
+
Units.continue_forward_reference_resolution
|
35
|
+
unit
|
36
|
+
end
|
37
|
+
|
38
|
+
# Associates the name of a format with an output formatter in the instance's
|
39
|
+
# UnitsMeasure.
|
40
|
+
def format(options)
|
41
|
+
@units_measure.format(options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def greek_ten(base) # :nodoc:
|
45
|
+
greek_unit base, "yocto", "y", 0.000000000000000000000001
|
46
|
+
greek_unit base, "zepto", "z", 0.000000000000000000001
|
47
|
+
greek_unit base, "atto", "a", 0.000000000000000001
|
48
|
+
greek_unit base, "femto", "f", 0.000000000000001
|
49
|
+
greek_unit base, "pico", "p", 0.000000000001
|
50
|
+
greek_unit base, "nano", "n", 0.000000001
|
51
|
+
greek_unit base, "micro", "u", 0.000001
|
52
|
+
greek_unit base, "milli", "m", 0.001
|
53
|
+
greek_unit base, "centi", "c", 0.01
|
54
|
+
greek_unit base, "deci", "d", 0.1
|
55
|
+
greek_unit base, "deca", "da", 10.0
|
56
|
+
greek_unit base, "hecto", "h", 100.0
|
57
|
+
greek_unit base, "kilo", "k", 1000.0
|
58
|
+
greek_unit base, "mega", "M", 1000000.0
|
59
|
+
greek_unit base, "giga", "G", 1000000000.0
|
60
|
+
greek_unit base, "tera", "T", 1000000000000.0
|
61
|
+
greek_unit base, "peta", "P", 1000000000000000.0
|
62
|
+
greek_unit base, "exa", "E", 1000000000000000000.0
|
63
|
+
greek_unit base, "zetta", "Z", 1000000000000000000000.0
|
64
|
+
greek_unit base, "yotta", "Y", 1000000000000000000000000.0
|
65
|
+
end
|
66
|
+
|
67
|
+
def greek_two(base) # :nodoc:
|
68
|
+
greek_unit base, "kilo", "k", 2**10
|
69
|
+
greek_unit base, "mega", "m", 2**20
|
70
|
+
greek_unit base, "giga", "g", 2**30
|
71
|
+
greek_unit base, "tera", "t", 2**40
|
72
|
+
greek_unit base, "peta", "p", 2**50
|
73
|
+
greek_unit base, "exa", "e", 2**60
|
74
|
+
greek_unit base, "zetta", "z", 2**70
|
75
|
+
greek_unit base, "yotta", "y", 2**80
|
76
|
+
end
|
77
|
+
|
78
|
+
def greek_unit(base,prefix,abbrev_prefix,scalar) # :nodoc:
|
79
|
+
unit :name => prefix+base.name,
|
80
|
+
:plural => prefix+base.plural,
|
81
|
+
:abbrevs => base.abbrevs.collect { |abbrev| abbrev_prefix+abbrev },
|
82
|
+
:equals => base.equals * scalar
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'methodic_hash'
|
2
|
+
|
3
|
+
require 'units/numeric_with_units'
|
4
|
+
|
5
|
+
# A unit in the context of a particular UnitsSystem. It's a MethodicHash of
|
6
|
+
# its named qualities.
|
7
|
+
class UnitsUnit < MethodicHash
|
8
|
+
|
9
|
+
# The UnitsSystem to which this instance is associated.
|
10
|
+
attr_reader :units_system
|
11
|
+
|
12
|
+
def initialize(units_system,attributes) # :nodoc:
|
13
|
+
@units_system = units_system
|
14
|
+
merge! attributes
|
15
|
+
normalize
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(unit) # :nodoc:
|
19
|
+
self.equal? unit
|
20
|
+
end
|
21
|
+
|
22
|
+
def normalize # :nodoc:
|
23
|
+
raise UnitsException.new("UnitUnits must have a name attribute") unless
|
24
|
+
self.name
|
25
|
+
self.name = self.name.to_s
|
26
|
+
add_plural
|
27
|
+
add_abbrevs
|
28
|
+
add_equals
|
29
|
+
equals.each { |n| n.promote_original } if equals.kind_of? Array
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_plural # :nodoc:
|
34
|
+
self.plural =
|
35
|
+
(self.no_plural == true) ? self.name :
|
36
|
+
(plural = self.plural) ? plural.to_s : self.name+'s'
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_abbrevs(attribute = nil) # :nodoc:
|
40
|
+
if attribute == nil
|
41
|
+
self.abbrevs = add_abbrevs(:abbrev)+add_abbrevs(:abbrevs)
|
42
|
+
elsif (value = self[attribute])
|
43
|
+
self.delete(attribute)
|
44
|
+
(value.kind_of? Array) ? value.collect {|e| e.to_s } : [ value.to_s ]
|
45
|
+
else
|
46
|
+
[ ]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_equals # :nodoc:
|
51
|
+
self.equals = NumericWithUnits.new(1,self) unless self.equals
|
52
|
+
end
|
53
|
+
|
54
|
+
# returns the UnitsMeasure containing the UnitsSystem to which this instance
|
55
|
+
# belongs.
|
56
|
+
def units_measure
|
57
|
+
units_system.units_measure
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
data/lib/units.rb
ADDED
data/rakefile.rb
ADDED
data/test/framework.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require 'units'
|
4
|
+
|
5
|
+
class TC_definitions < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_definitions
|
8
|
+
|
9
|
+
assert(Units.forward_references.size == 0) # all the definitions loaded
|
10
|
+
|
11
|
+
t = 5.years
|
12
|
+
assert(t.to_s == "5 years")
|
13
|
+
assert(t.in_months.to_s == "60 months")
|
14
|
+
assert(t.in_months.in_days.to_s == "1800 days")
|
15
|
+
assert(t.in_days.to_s == "1825 days")
|
16
|
+
assert(t.in_days.in_months.to_s == "60.8333333333333 months")
|
17
|
+
assert(t.in_hours.to_s == "43800.0 hours")
|
18
|
+
assert(t.in_weeks.to_s == "260 weeks")
|
19
|
+
assert(t.in_months.in_weeks.to_s == "240 weeks")
|
20
|
+
assert(t.in_days.in_weeks.to_s == "260.714285714286 weeks")
|
21
|
+
assert(t.in_days == 260.weeks+5.days)
|
22
|
+
|
23
|
+
assert(days_in_january.to_s == "31.0")
|
24
|
+
assert(hours_in_january.to_s == "744.0")
|
25
|
+
assert(hours_per_january == 744)
|
26
|
+
assert(1.january.hours == 744.hours)
|
27
|
+
assert(january.hours == 744.hours)
|
28
|
+
assert(february.hours == 672.hours)
|
29
|
+
assert(july.weeks.to_s == "4.42857142857143 weeks")
|
30
|
+
assert(july.format(:weeks_and_days).to_s == "4 weeks 3 days")
|
31
|
+
assert(july.to_s(:weeks_and_days) == "4 weeks 3 days")
|
32
|
+
|
33
|
+
assert(speed_of_light.in_mi_s.to_s == "186282.024486427 mi / s")
|
34
|
+
assert(4.5.ly.in_mi.to_s == "26435654658917.8 miles")
|
35
|
+
assert(4.5.ly.AU.to_s == "284389.813364457 astronomical units")
|
36
|
+
assert(speed_of_light.miles.to_s == "186282.024486427 mi / s")
|
37
|
+
assert(speed_of_light.feet_nanosecond.to_s == "0.983569089288333 ft / ns")
|
38
|
+
assert(4.5.unite(["light_years"]).AU.to_s ==
|
39
|
+
"284389.813364457 astronomical units")
|
40
|
+
assert(4.5.light_years.AU.to_s ==
|
41
|
+
"284389.813364457 astronomical units")
|
42
|
+
assert(4.5.light_years.astronomical_units.to_s ==
|
43
|
+
"284389.813364457 astronomical units")
|
44
|
+
assert(1.speed_of_light.to_s == "1 speed of light")
|
45
|
+
assert(4.5.speed_of_light.to_s == "4.5 speed of light")
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'test/framework'
|
3
|
+
|
4
|
+
require 'units'
|
5
|
+
|
6
|
+
class TC_formatting < Test::Unit::TestCase
|
7
|
+
|
8
|
+
understands UnitsTest
|
9
|
+
|
10
|
+
def test_fractions
|
11
|
+
|
12
|
+
Units.create :length do |m|
|
13
|
+
m.system :english do |s|
|
14
|
+
s.unit :name => :inch, :plural => :inches, :abbrev => :in
|
15
|
+
s.unit :name => :foot, :plural => :feet, :abbrev => :ft,
|
16
|
+
:equals => 12.inches
|
17
|
+
s.unit :name => :yard, :abbrevs => :yd, :equals => 3.feet
|
18
|
+
s.unit :name => :mile, :abbrevs => :mi, :equals => 1760.yards
|
19
|
+
s.format :name => :feet_inches_and_32s,
|
20
|
+
:format => lambda { |u|
|
21
|
+
ru = u.inch.round_to_nearest 32
|
22
|
+
feet = ru.feet.floor
|
23
|
+
inches = (ru-feet).inches
|
24
|
+
"#{feet} #{inches.with_fraction 32}" }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
l = 13.28090635.ft
|
29
|
+
assert(l.measure == Units.length)
|
30
|
+
assert(3.46875.with_fraction(32) == "3-15/32")
|
31
|
+
assert(l.format(:feet_inches_and_32s) == "13 feet 3-12/32 inches")
|
32
|
+
|
33
|
+
almost = 1.999.ft
|
34
|
+
assert(almost.inch.round_to_nearest(32) == 24.0.in)
|
35
|
+
|
36
|
+
almost = 1.996.ft
|
37
|
+
assert(almost.inch.round_to_nearest(32) == 23.9375.in)
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|