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.
@@ -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 ]
@@ -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