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
@@ -0,0 +1,635 @@
|
|
1
|
+
require 'units/units'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
# A NumericWithUnits is the intersection of a Numeric and a UnitsHash.
|
5
|
+
#
|
6
|
+
# Unit-sensitive coding is made much easier using an object that transparently
|
7
|
+
# adds units to a common everyday Numerics. Everything that can be done to a
|
8
|
+
# Numeric is still there, but explicit and implicit unit conversions are present
|
9
|
+
# in all of the operations.
|
10
|
+
#
|
11
|
+
# With this, numbers with units can be created easily. For example,
|
12
|
+
#
|
13
|
+
# 2.feet # a length of 2 feet
|
14
|
+
# 5.inches^2 # an area of 5 square inches
|
15
|
+
# 44.5.ft/sec # a velocity of 44.5 feet per second
|
16
|
+
#
|
17
|
+
# This should provide a good starting point for using the Units framework. Also
|
18
|
+
# pay attention to the examples given in the method documentation; some of the
|
19
|
+
# dynamic features of the framework are exposed in them.
|
20
|
+
class NumericWithUnits
|
21
|
+
|
22
|
+
include Comparable
|
23
|
+
|
24
|
+
@@debug = false
|
25
|
+
|
26
|
+
def self.debug=(value)
|
27
|
+
@@debug = value
|
28
|
+
end
|
29
|
+
|
30
|
+
# A Numeric containing the numeric part of the instance
|
31
|
+
attr_accessor :numeric
|
32
|
+
# A UnitsHash containing the units part of the instance
|
33
|
+
attr_accessor :unit
|
34
|
+
attr_accessor :original # :nodoc:
|
35
|
+
|
36
|
+
# Returns a new NumericWithUnits instance whose numeric part is set to
|
37
|
+
# numeric and whose units part is set to a units hash for the unit raised
|
38
|
+
# to the provided power.
|
39
|
+
def initialize(numeric,unit,power=1)
|
40
|
+
@numeric, @unit = numeric, units_hash(unit)**power
|
41
|
+
end
|
42
|
+
|
43
|
+
def units_hash(unit) # :nodoc:
|
44
|
+
(unit.kind_of? UnitsHash) ? unit : UnitsHash.new(unit)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns a String representation of the instance, using the named format
|
48
|
+
# if provided.
|
49
|
+
#
|
50
|
+
# 15.5.minutes.to_s # 15.5 minutes
|
51
|
+
# 15.5.minutes.to_seconds.to_s # 930.0 seconds
|
52
|
+
# 15.5.minutes.in_seconds.to_s # 930.0 seconds
|
53
|
+
# 15.5.minutes.seconds.to_s # 930.0 seconds
|
54
|
+
# (15.5.minutes+1).seconds.to_s # 990.0 seconds
|
55
|
+
# (15.5.minutes.seconds+1).to_s # 931.0 seconds
|
56
|
+
# 10.feet_per_minute.to_s # 10 ft / min
|
57
|
+
# seconds_per_hour.to_s # 3600.0
|
58
|
+
# 14.5.inches.to_s(:feet_inches_and_32s) # "1 foot 2-16/32 inches"
|
59
|
+
def to_s(format = nil)
|
60
|
+
format == nil ? "#{numeric} #{unit.to_s(numeric)}" : self.format(format)
|
61
|
+
end
|
62
|
+
|
63
|
+
def promote_original # :nodoc:
|
64
|
+
@numeric, @unit = original.numeric, original.unit
|
65
|
+
end
|
66
|
+
|
67
|
+
# Compares the numeric and units parts of the instance with the value. If
|
68
|
+
# the value is a Numeric, the units are assumed to match. If the
|
69
|
+
# UnitsMeasures don't match, a UnitsException is raised.
|
70
|
+
#
|
71
|
+
# 2.ft <=> 1.yd # -1
|
72
|
+
# 3.ft <=> 1.yd # 0
|
73
|
+
# 4.ft <=> 1.yd # 1
|
74
|
+
# 4.ft <=> 3.5 # 1
|
75
|
+
# 4.ft <=> 2.minutes # UnitsException
|
76
|
+
def <=>(value)
|
77
|
+
if derived?
|
78
|
+
reduce <=> value
|
79
|
+
elsif value.kind_of? NumericWithUnits
|
80
|
+
if value.derived?
|
81
|
+
self <=> value.reduce
|
82
|
+
else
|
83
|
+
align(value).numeric <=> value.numeric
|
84
|
+
end
|
85
|
+
elsif value.kind_of? Numeric
|
86
|
+
numeric <=> value
|
87
|
+
else
|
88
|
+
raise UnitsException.new("units mismatch")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns true if the value and the instance are within a distance epsilon
|
93
|
+
# of each other. If the value is a Numeric, the units are assumed to match.
|
94
|
+
# If the UnitsMeasures don't match, a UnitsException is raised.
|
95
|
+
#
|
96
|
+
# 1.ft.approximately_equals? 0.33.yd # false
|
97
|
+
# 1.ft.approximately_equals? 0.333333.yd # true
|
98
|
+
def approximately_equals?(value,epsilon=Numeric.epsilon)
|
99
|
+
if derived?
|
100
|
+
reduce.approximately_equals?(value,epsilon)
|
101
|
+
elsif value.kind_of? NumericWithUnits
|
102
|
+
if value.derived?
|
103
|
+
approximately_equals?(value.reduce,epsilon)
|
104
|
+
else
|
105
|
+
align(value).numeric.approximately_equals?(value.numeric,epsilon)
|
106
|
+
end
|
107
|
+
elsif value.kind_of? Numeric
|
108
|
+
numeric.approximately_equals?(value,epsilon)
|
109
|
+
else
|
110
|
+
raise UnitsException.new("units mismatch")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
alias :=~ :approximately_equals?
|
115
|
+
|
116
|
+
# Unary plus returns a copy of the instance.
|
117
|
+
def +@
|
118
|
+
clone
|
119
|
+
end
|
120
|
+
|
121
|
+
# Unary minus returns a copy of the instance with its numeric part negated.
|
122
|
+
def -@
|
123
|
+
value = clone
|
124
|
+
value.numeric = -(value.numeric)
|
125
|
+
value
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns a new NumericWithUnits containing the sum of the instance and
|
129
|
+
# the value. If the value is a Numeric, the units are assumed to match.
|
130
|
+
# If the UnitsMeasures don't match, a UnitsException is raised.
|
131
|
+
#
|
132
|
+
# 6.inches + 1.foot # 18 inches
|
133
|
+
# 6.inches + 1 # 7 inches
|
134
|
+
# 6.inches + 1.sec # UnitsException
|
135
|
+
def +(value)
|
136
|
+
if derived?
|
137
|
+
reduce+value
|
138
|
+
elsif value.kind_of? NumericWithUnits
|
139
|
+
if value.derived?
|
140
|
+
self+value.reduce
|
141
|
+
else
|
142
|
+
aligned_value = align(value)
|
143
|
+
aligned_value.numeric += value.numeric
|
144
|
+
aligned_value
|
145
|
+
end
|
146
|
+
elsif value.kind_of? Numeric
|
147
|
+
NumericWithUnits.new(numeric+value,unit)
|
148
|
+
else
|
149
|
+
raise UnitsException.new("units mismatch")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns a new NumericWithUnits containing the difference between the
|
154
|
+
# instance and the value. If the value is a Numeric, the units are assumed
|
155
|
+
# to match. If the UnitsMeasures don't match, a UnitsException is raised.
|
156
|
+
#
|
157
|
+
# 6.inches - 1.foot # -6 inches
|
158
|
+
# 6.inches - 1 # 5 inches
|
159
|
+
# 6.inches - 1.sec # UnitsException
|
160
|
+
def -(value)
|
161
|
+
self + (-value)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns a new NumericWithUnits containing the product of the instance and
|
165
|
+
# the value. The units of the two factors are merged. If the value is not a
|
166
|
+
# Numeric nor NumericWithUnits, a UnitsException is thrown.
|
167
|
+
#
|
168
|
+
# 6.in * 2.ft # 1 foot^2
|
169
|
+
# 6.in * 2 # 12 inches
|
170
|
+
# 6.in * 2.sec # 12 ft sec
|
171
|
+
# 6.in * "hello" # UnitsException
|
172
|
+
def *(value)
|
173
|
+
if derived?
|
174
|
+
reduce*value
|
175
|
+
elsif value.kind_of? NumericWithUnits
|
176
|
+
if value.derived?
|
177
|
+
self*value.reduce
|
178
|
+
else
|
179
|
+
extend(value,1)
|
180
|
+
end
|
181
|
+
elsif value.kind_of? Numeric
|
182
|
+
NumericWithUnits.new(numeric*value,unit)
|
183
|
+
else
|
184
|
+
raise UnitsException.new("units mismatch")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Returns a new NumericWithUnits containing the value of the instance divided
|
189
|
+
# by the value. The units of the result are the units of the instance merged
|
190
|
+
# with the recipricol of the units of the value. If the value is neither a
|
191
|
+
# Numeric nor NumericWithUnits, a UnitsException is thrown.
|
192
|
+
#
|
193
|
+
# 6.in / 2.ft # 0.25
|
194
|
+
# 6.in / 2 # 3 inches
|
195
|
+
# 6.in / 2.sec # 3 ft / sec
|
196
|
+
# 6.in / "hello" # UnitsException
|
197
|
+
def /(value)
|
198
|
+
if derived?
|
199
|
+
reduce/value
|
200
|
+
elsif value.kind_of? NumericWithUnits
|
201
|
+
if value.derived?
|
202
|
+
self/value.reduce
|
203
|
+
else
|
204
|
+
extend(value,-1)
|
205
|
+
end
|
206
|
+
elsif value.kind_of? Numeric
|
207
|
+
NumericWithUnits.new(numeric/value,unit)
|
208
|
+
else
|
209
|
+
raise UnitsException.new("units mismatch")
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Returns a new NumericWithUnits containing the numeric and units parts of
|
214
|
+
# the instance both raised to the valueth power. If the value is not
|
215
|
+
# a Numeric, a UnitsException is thrown.
|
216
|
+
#
|
217
|
+
# 6.in ** 3 # 216 in^3
|
218
|
+
# 6.in ** 3.in # UnitsException
|
219
|
+
def **(value)
|
220
|
+
if (value.kind_of? Numeric) && !(value.kind_of? NumericWithUnits)
|
221
|
+
extend(nil,value)
|
222
|
+
else
|
223
|
+
raise UnitsException.new("units mismatch")
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Returns a new NumericWithUnits containing the numeric and units parts of
|
228
|
+
# the instance with just the units raised to the valueth power. If the value
|
229
|
+
# is not a Numeric, a UnitsException is thrown.
|
230
|
+
#
|
231
|
+
# 6.in ^ 3 # 6 in^3
|
232
|
+
# 6.in ^ 3.in # UnitsException
|
233
|
+
def ^(value)
|
234
|
+
if (value.kind_of? Numeric) && !(value.kind_of? NumericWithUnits)
|
235
|
+
NumericWithUnits.new(numeric,unit,value)
|
236
|
+
else
|
237
|
+
raise UnitsException.new("units mismatch")
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Returns a new NumericWithUnits containing the numeric part of the instance
|
242
|
+
# modulo the numeric part of the value, with the units part equal to the
|
243
|
+
# instance's units part. If the value is a Numeric, the units are assumed
|
244
|
+
# to match. If the UnitsMeasures don't match, a UnitsException is raised.
|
245
|
+
#
|
246
|
+
# 30.in % 2.ft # 0.5 feet
|
247
|
+
# 5.in % 2.3 # 0.4 inches
|
248
|
+
# 5.in % 2.sec # UnitsException
|
249
|
+
def %(value)
|
250
|
+
if derived?
|
251
|
+
reduce%value
|
252
|
+
elsif value.kind_of? NumericWithUnits
|
253
|
+
if value.derived?
|
254
|
+
self%value.reduce
|
255
|
+
else
|
256
|
+
aligned_value = align(value)
|
257
|
+
aligned_value.numeric = aligned_value.numeric % value.numeric
|
258
|
+
aligned_value
|
259
|
+
end
|
260
|
+
elsif value.kind_of? Numeric
|
261
|
+
NumericWithUnits.new(numeric % value,unit)
|
262
|
+
else
|
263
|
+
raise UnitsException.new("units mismatch")
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# Returns a new NumericWithUnits containing the numeric part of the value
|
268
|
+
# modulo the numeric part of the instance, with the units part equal to the
|
269
|
+
# instance's units part. If the value is a Numeric, the units are assumed
|
270
|
+
# to match. If the UnitsMeasures don't match, a UnitsException is raised.
|
271
|
+
#
|
272
|
+
# 30.in % 2.ft # 24 inches
|
273
|
+
# 5.in % 2.3 # 2.3 inches
|
274
|
+
# 5.in % 2.sec # UnitsException
|
275
|
+
def inv_mod(value)
|
276
|
+
if derived?
|
277
|
+
reduce.inv_mod value
|
278
|
+
elsif value.kind_of? NumericWithUnits
|
279
|
+
if value.derived?
|
280
|
+
self.inv_mod value.reduce
|
281
|
+
else
|
282
|
+
aligned_value = align(value)
|
283
|
+
aligned_value.numeric = value.numeric % aligned_value.numeric
|
284
|
+
aligned_value
|
285
|
+
end
|
286
|
+
elsif value.kind_of? Numeric
|
287
|
+
NumericWithUnits.new(value % numeric,unit)
|
288
|
+
else
|
289
|
+
raise UnitsException.new("units mismatch")
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def can_align(target,exact=true) # :nodoc:
|
294
|
+
ru, tru = reduce.unit, target.reduce.unit
|
295
|
+
!exact || (ru == tru)
|
296
|
+
end
|
297
|
+
|
298
|
+
def reduce_power(p,f,type) # :nodoc:
|
299
|
+
if type == :whole_powers
|
300
|
+
pa = p.abs
|
301
|
+
(p == 0) ? [p,f,0] : (pa < f) ? [p, f, pa/p] : [p, f, p/f]
|
302
|
+
else
|
303
|
+
(p == 0) ? [p,f,0] : [p, f, (1.0*p)/f]
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def common_power(ps) # :nodoc:
|
308
|
+
ps.values.collect {|a| a[2]}.inject {|p,e| p.abs < e.abs ? p : e}
|
309
|
+
end
|
310
|
+
|
311
|
+
def revise_power(rp,p) # :nodoc:
|
312
|
+
[rp[0], rp[1], p, rp[0]-p*rp[1]]
|
313
|
+
end
|
314
|
+
|
315
|
+
# Sets and returns the type of exponentiation merging during alignment.
|
316
|
+
def self.derived_align_type=(type)
|
317
|
+
@@derived_align_type = type
|
318
|
+
end
|
319
|
+
|
320
|
+
# Returns the type of exponentiation merging during alignment.
|
321
|
+
# * :whole_powers require exponents to have integer values
|
322
|
+
# * :fractional_powers allow exponents to have rational values
|
323
|
+
def self.derived_align_type
|
324
|
+
@@derived_align_type
|
325
|
+
end
|
326
|
+
|
327
|
+
@@derived_align_type = :whole_powers
|
328
|
+
|
329
|
+
# Returns a new NumericWithUnits whose value is equivalent to that of the
|
330
|
+
# instance, but whose units are aligned to the target, according to the
|
331
|
+
# value of derived_align_type. If all is true, then the UnitsMeasure of the
|
332
|
+
# instance and the target must match exactly, or else a UnitsException is
|
333
|
+
# raised.
|
334
|
+
#
|
335
|
+
# (80.miles_per_hour).align(1.min,false) # 1.3333333 mi / min
|
336
|
+
def align(target,all=true,type=@@derived_align_type)
|
337
|
+
if target.kind_of? Array
|
338
|
+
piece_align(target)
|
339
|
+
elsif !derived? && !target.derived?
|
340
|
+
simple_align(target,all)
|
341
|
+
else
|
342
|
+
power_align(target,all,type)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def simple_align(target,all=true) # :nodoc:
|
347
|
+
puts "simple align #{self} #{target}" if @@debug
|
348
|
+
factor = 1
|
349
|
+
target_unit = UnitsHash.new
|
350
|
+
unit.each do |tu,tv|
|
351
|
+
su = target.unit.keys.select {|u|
|
352
|
+
u.units_measure.equal? tu.units_measure }
|
353
|
+
if tu.equals.kind_of? Array
|
354
|
+
m = tu.equals.select{|u| u.unit[su[0]]}
|
355
|
+
m = tu.equals.collect{|u|
|
356
|
+
u.align(1.unite(su[0]))}.compact if m.size == 0
|
357
|
+
factor *= (m[0].numeric)**tv
|
358
|
+
target_unit[su[0]] = tv
|
359
|
+
else
|
360
|
+
if su.size == 1
|
361
|
+
e = su[0].equals
|
362
|
+
if e.kind_of? Array
|
363
|
+
m = e.select{|u|
|
364
|
+
u.unit[tu]}
|
365
|
+
m = e.equals.collect{|u|
|
366
|
+
u.align(1.unite(su[0]))}.compact if m.size == 0
|
367
|
+
factor *= (1.0/m[0].numeric)**tv
|
368
|
+
else
|
369
|
+
factor *= (1.0*tu.equals.numeric/e.numeric)**tv
|
370
|
+
end
|
371
|
+
target_unit[su[0]] = tv
|
372
|
+
else
|
373
|
+
target_unit[tu] = tv
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
raise UnitsException.new("units mismatch") if
|
378
|
+
all && (target.unit != target_unit)
|
379
|
+
nwu = NumericWithUnits.new(numeric*factor,target_unit)
|
380
|
+
puts " simple_align returning #{nwu}" if @@debug
|
381
|
+
nwu
|
382
|
+
end
|
383
|
+
|
384
|
+
def power_align(target,all=true,type=@@derived_align_type) # :nodoc:
|
385
|
+
puts "power_align #{self} #{target}" if @@debug
|
386
|
+
raise UnitsException.new("units mismatch") unless can_align(target,all)
|
387
|
+
factor = 1
|
388
|
+
target_unit = UnitsHash.new
|
389
|
+
unit_reduce = reduce
|
390
|
+
puts " unit_reduce #{unit_reduce}" if @@debug
|
391
|
+
target.unit.each do |tu,tv|
|
392
|
+
t_u = {}
|
393
|
+
tt_u = {}
|
394
|
+
tu_reduce = tu.equals.reduce
|
395
|
+
found = tu_reduce.unit.each do |ttu,ttv|
|
396
|
+
su = unit_reduce.unit.keys.select {|u|
|
397
|
+
u.units_measure.equal? ttu.units_measure }
|
398
|
+
break false if su.size == 0
|
399
|
+
tt_u[ttu] = reduce_power(unit_reduce.unit[ttu],ttv,type)
|
400
|
+
end
|
401
|
+
if found
|
402
|
+
cp = common_power(tt_u)
|
403
|
+
puts " cp #{cp}" if @@debug
|
404
|
+
tt_u.each {|k,v| tt_u[k] = revise_power(v,cp)}
|
405
|
+
t_u[tu] = tv**cp
|
406
|
+
t_factor = tu_reduce.numeric**cp
|
407
|
+
puts " t_factor #{t_factor}" if @@debug
|
408
|
+
target_unit[tu] = cp
|
409
|
+
puts " tu.equals.numeric**cp #{tu.equals.numeric**cp}" if @@debug
|
410
|
+
factor *= t_factor/(tu.equals.numeric**cp)
|
411
|
+
puts " factor #{factor}" if @@debug
|
412
|
+
tt_u.each {|k,v|
|
413
|
+
unit_reduce.numeric /= t_factor
|
414
|
+
unit_reduce.unit[k] = v[3]}
|
415
|
+
end
|
416
|
+
end
|
417
|
+
puts " unit_reduce #{unit_reduce}" if @@debug
|
418
|
+
unit_redux = unit_reduce.simple_align(self,false)
|
419
|
+
puts " unit_redux #{unit_redux}" if @@debug
|
420
|
+
result_unit = target_unit.merge(unit_redux)
|
421
|
+
raise UnitsException.new("units mismatch") if
|
422
|
+
all && (target.unit != result_unit)
|
423
|
+
nwu = NumericWithUnits.new(factor*unit_redux.numeric,result_unit)
|
424
|
+
puts " power_align returning #{nwu}" if @@debug
|
425
|
+
nwu
|
426
|
+
end
|
427
|
+
|
428
|
+
def piece_align(pieces,type=@@derived_align_type) # :nodoc:
|
429
|
+
puts "piece_align #{self} #{pieces}" if @@debug
|
430
|
+
factor = 1
|
431
|
+
target_unit = UnitsHash.new
|
432
|
+
unit_reduce = reduce
|
433
|
+
pieces.each do |p|
|
434
|
+
p.unit.each do |tu,tv|
|
435
|
+
t_u = {}
|
436
|
+
tt_u = {}
|
437
|
+
tu_reduce = tu.equals.reduce
|
438
|
+
found = tu_reduce.unit.each do |ttu,ttv|
|
439
|
+
su = unit_reduce.unit.keys.select {|u|
|
440
|
+
u.units_measure.equal? ttu.units_measure }
|
441
|
+
break false if su.size == 0
|
442
|
+
tt_u[ttu] = reduce_power(unit_reduce.unit[ttu],ttv,type)
|
443
|
+
end
|
444
|
+
if found
|
445
|
+
tt_u.each {|k,v| tt_u[k] = revise_power(v,tv)}
|
446
|
+
t_u[tu] = tv
|
447
|
+
t_factor = tu_reduce.numeric**tv
|
448
|
+
target_unit[tu] = tv
|
449
|
+
factor *= t_factor/(tu.equals.numeric**tv)
|
450
|
+
tt_u.each {|k,v|
|
451
|
+
unit_reduce.numeric /= t_factor
|
452
|
+
unit_reduce.unit[k] = v[3]}
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
raise UnitsException.new("units mismatch") if unit_reduce.unit.has_units?
|
457
|
+
nwu = NumericWithUnits.new(factor*unit_reduce.numeric,target_unit)
|
458
|
+
puts " piece_align returning #{nwu}" if @@debug
|
459
|
+
nwu
|
460
|
+
end
|
461
|
+
|
462
|
+
# Returns a new NumericWithUnits whose value is extended by raising it to the
|
463
|
+
# power, or by multiplying it by units raised to the power.
|
464
|
+
#
|
465
|
+
# 7.miles.extend(nil,2) # 49 mi^2
|
466
|
+
# 5.feet.extend(10.ft,2) # 500 ft^3
|
467
|
+
def extend(units,power)
|
468
|
+
if !units
|
469
|
+
NumericWithUnits.new(numeric**power,unit**power)
|
470
|
+
else
|
471
|
+
value = align(units,false)
|
472
|
+
extended_numeric = value.numeric*(units.numeric**power)
|
473
|
+
extended_unit = value.unit.merge(units,power)
|
474
|
+
(extended_unit.size == 0)? extended_numeric :
|
475
|
+
NumericWithUnits.new(extended_numeric,extended_unit)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
def NumericWithUnits.commutative_operator(op,old,calc) # :nodoc:
|
480
|
+
([] <<
|
481
|
+
"alias :old_#{old} :#{op}" <<
|
482
|
+
"def #{op}(value)" <<
|
483
|
+
" (value.kind_of? NumericWithUnits) ? #{calc} : old_#{old}(value)" <<
|
484
|
+
"end").
|
485
|
+
join("\r\n")
|
486
|
+
end
|
487
|
+
|
488
|
+
def NumericWithUnits.create_commutative_operators(klasses) # :nodoc:
|
489
|
+
commutative_operators ||=
|
490
|
+
([] <<
|
491
|
+
commutative_operator( "*", "multiply", "value * self" ) <<
|
492
|
+
commutative_operator( "/", "divide", "(value**-1) * self" ) <<
|
493
|
+
commutative_operator( "+", "add", "value + self" ) <<
|
494
|
+
commutative_operator( "-", "subtract", "(-value) + self" ) <<
|
495
|
+
commutative_operator( "%", "modulo", "value.inv_mod(self)" ) <<
|
496
|
+
commutative_operator( "<=>", "compare", "-(value <=> self)" ) <<
|
497
|
+
commutative_operator( ">", "gt", "(value < self)" ) <<
|
498
|
+
commutative_operator( "<", "lt", "(value > self)" ) <<
|
499
|
+
commutative_operator( ">=", "gteq", "(value <= self)" ) <<
|
500
|
+
commutative_operator( "<=", "lteq", "(value >= self)" ) <<
|
501
|
+
commutative_operator( "==", "eq", "(value == self)" ) <<
|
502
|
+
commutative_operator( "=~", "approxeq", "(value =~ self)" )).
|
503
|
+
join("\r\n")
|
504
|
+
klasses.each { |klass| klass.class_eval commutative_operators }
|
505
|
+
end
|
506
|
+
|
507
|
+
def method_missing(method,*args) # :nodoc:
|
508
|
+
begin
|
509
|
+
s = method.to_s
|
510
|
+
ms = s.split '_'
|
511
|
+
if ms[0] == 'to'
|
512
|
+
convert! s.gsub(/^to_/,"")
|
513
|
+
elsif ms[0] == 'in'
|
514
|
+
convert s.gsub(/^in_/,"")
|
515
|
+
elsif ms.select{|e| e == 'per'}.size > 0
|
516
|
+
convert_per method
|
517
|
+
else
|
518
|
+
convert s
|
519
|
+
end
|
520
|
+
rescue Exception
|
521
|
+
value = numeric.send(method,*args)
|
522
|
+
if (value.kind_of? String)
|
523
|
+
"#{value} #{unit.to_s(numeric)}"
|
524
|
+
else
|
525
|
+
NumericWithUnits.new(value,unit)
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
def convert_per method # :nodoc:
|
531
|
+
ps = method.to_s.split '_per_'
|
532
|
+
raise UnitsException.new('invalid per method') if ps.size != 2
|
533
|
+
numerator = 1.unite(ps[0])
|
534
|
+
numerator_units = Set.new numerator.unit.keys
|
535
|
+
denominator = 1.unite(ps[1])
|
536
|
+
denominator_units = Set.new denominator.unit.keys
|
537
|
+
positives = unit.keys.collect{|k| unit[k] > 0 ? k : nil}.compact!
|
538
|
+
negatives = unit.keys.collect{|k| unit[k] < 0 ? k : nil}.compact!
|
539
|
+
numerator_positives =
|
540
|
+
Set.new 1.unite(positives).align(numerator,false).unit.keys
|
541
|
+
numerator_negatives =
|
542
|
+
Set.new 1.unite(negatives).align(numerator,false).unit.keys
|
543
|
+
denominator_positives =
|
544
|
+
Set.new 1.unite(positives).align(denominator,false).unit.keys
|
545
|
+
denominator_negatives =
|
546
|
+
Set.new 1.unite(negatives).align(denominator,false).unit.keys
|
547
|
+
if (numerator_units == numerator_positives) &&
|
548
|
+
(denominator_units == denominator_negatives)
|
549
|
+
convert(ps[0]+'_and_'+ps[1])
|
550
|
+
elsif (numerator_units == numerator_negatives) &&
|
551
|
+
(denominator_units == denominator_positives)
|
552
|
+
convert(ps[0]+'_and_'+ps[1])**-1
|
553
|
+
else
|
554
|
+
raise UnitsException.new('invalid per units')
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
alias :old_kind_of? :kind_of?
|
559
|
+
|
560
|
+
# Returns true if the instance or it's numeric part is a kind of klass.
|
561
|
+
#
|
562
|
+
# 28.feet.kind_of? String # false
|
563
|
+
# 28.feet.kind_of? NumericWithUnits # true
|
564
|
+
# 28.feet.kind_of? Numeric # true
|
565
|
+
# 28.feet.kind_of? Integer # true
|
566
|
+
# 28.feet.kind_of? Float # false
|
567
|
+
# 28.0.feet.kind_of? Float # true
|
568
|
+
#
|
569
|
+
# Note while NumericWithUnits actually descends from Object, it acts as if it
|
570
|
+
# is inherited from the class of the numeric part of the instance, since it
|
571
|
+
# forwards any unknown method calls to it. In this way the duck really is a
|
572
|
+
# duck.
|
573
|
+
def kind_of?(klass)
|
574
|
+
(numeric.kind_of? klass)? true : old_kind_of?(klass)
|
575
|
+
end
|
576
|
+
|
577
|
+
# Returns a new NumericWithUnits whose numeric part is the target of the
|
578
|
+
# Numeric's unite method.
|
579
|
+
#
|
580
|
+
# 28.ft^2.unite("seconds") # 28 seconds
|
581
|
+
def unite(target_unit=nil,power=1,measure=nil)
|
582
|
+
numeric.unite(target_unit,power,measure)
|
583
|
+
end
|
584
|
+
|
585
|
+
# Returns a copy of the instance converted to the target_units. Note that
|
586
|
+
# the conversion is only with respect to the UnitsMeasures of the
|
587
|
+
# target_units - the remainder of the units will remain unconverted.
|
588
|
+
def convert(target_units=nil)
|
589
|
+
target_units ? align(1.unite(target_units),false) : clone
|
590
|
+
end
|
591
|
+
|
592
|
+
# Converts the instance itself.
|
593
|
+
def convert!(target_units=nil)
|
594
|
+
result = convert(target_units)
|
595
|
+
self.numeric, self.unit = result.numeric, result.unit
|
596
|
+
self
|
597
|
+
end
|
598
|
+
|
599
|
+
# Returns the UnitsMeasures in the units part of the instance.
|
600
|
+
def measure
|
601
|
+
unit.measure
|
602
|
+
end
|
603
|
+
|
604
|
+
# Returns a String formatted using the named format defined in the instance's
|
605
|
+
# measure. Raises a UnitsException if either the unit part of the instance
|
606
|
+
# has no defined UnitsMeasure or a format with the given name does not exist
|
607
|
+
# in that UnitsMeasure.
|
608
|
+
def format(name=nil)
|
609
|
+
if name == nil
|
610
|
+
to_s
|
611
|
+
else
|
612
|
+
raise UnitsException.new("system not explicit") if (measure == nil)
|
613
|
+
format = measure.formats[name]
|
614
|
+
raise UnitsException.new("missing format") if format == nil
|
615
|
+
format.call(self)
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
# Returns true if a component of unit part of the instance has a derived
|
620
|
+
# UnitsMeasure.
|
621
|
+
def derived?
|
622
|
+
unit.derived?
|
623
|
+
end
|
624
|
+
|
625
|
+
# Return a new NumericWithUnits that is equivalent to the instance but whose
|
626
|
+
# unit contains no derived UnitMeasures.
|
627
|
+
def reduce
|
628
|
+
puts "reduce unit.reduce #{unit.reduce}" if @@debug
|
629
|
+
numeric * unit.reduce
|
630
|
+
end
|
631
|
+
|
632
|
+
end
|
633
|
+
|
634
|
+
|
635
|
+
NumericWithUnits.create_commutative_operators [ Fixnum, Bignum, Float ]
|
data/lib/units/object.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'units/units'
|
2
|
+
|
3
|
+
class Object # :nodoc:
|
4
|
+
|
5
|
+
alias :method_missing_before_units :method_missing
|
6
|
+
|
7
|
+
def method_missing(method,*args)
|
8
|
+
begin
|
9
|
+
s = method.to_s.split('_')
|
10
|
+
if s.select{|e| e == 'per'}.size > 0
|
11
|
+
per_ratio method, args
|
12
|
+
elsif s.select{|e| e == 'in'}.size > 0
|
13
|
+
per_ratio method.to_s.sub(/_in_/,'_per_').to_sym, args
|
14
|
+
else
|
15
|
+
convert_unit_value method, args
|
16
|
+
end
|
17
|
+
rescue Exception => exception
|
18
|
+
method_missing_before_units method, args
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def convert_unit_value(method,*args)
|
23
|
+
if Units.defining?
|
24
|
+
reference = Units.make_forward_reference(method,Units.defining?)
|
25
|
+
begin
|
26
|
+
value = Units.convert 1, method
|
27
|
+
Units.release_forward_reference reference
|
28
|
+
value
|
29
|
+
rescue MissingUnitsException
|
30
|
+
Units.hold_forward_reference
|
31
|
+
rescue UnitsException => exception
|
32
|
+
units_problem("definition",exception,method,args)
|
33
|
+
rescue Exception => exception
|
34
|
+
method_missing_before_units(method,args)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
begin
|
38
|
+
Units.convert 1, method
|
39
|
+
rescue UnitsException => exception
|
40
|
+
units_problem("use",exception,method,args)
|
41
|
+
rescue Exception
|
42
|
+
method_missing_before_units(method,args)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def per_ratio(method,*args)
|
48
|
+
if Units.defining?
|
49
|
+
reference = Units.make_forward_reference(method,Units.defining?)
|
50
|
+
begin
|
51
|
+
value = convert_per_ratio method
|
52
|
+
Units.release_forward_reference reference
|
53
|
+
value
|
54
|
+
rescue MissingUnitsException
|
55
|
+
Units.hold_forward_reference
|
56
|
+
rescue UnitsException => exception
|
57
|
+
units_problem("definition",exception,method,args)
|
58
|
+
rescue Exception
|
59
|
+
method_missing_before_units(method,args)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
convert_per_ratio method
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def convert_per_ratio(method)
|
67
|
+
ps = method.to_s.split '_per_'
|
68
|
+
raise UnitsException('invalid per method') if ps.size != 2
|
69
|
+
value = (1.unite ps[1]) / (1.unite ps[0])
|
70
|
+
raise UnitsException.new("per ratio has units") if
|
71
|
+
value.kind_of? NumericWithUnits
|
72
|
+
value
|
73
|
+
end
|
74
|
+
|
75
|
+
def units_problem(state,exception,method,args)
|
76
|
+
raise exception
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|