ruby-units 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +8 -1
- data/lib/ruby-units.rb +140 -103
- data/lib/units.rb +4 -0
- data/test/test_ruby-units.rb +24 -0
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -91,6 +91,13 @@ Change Log for Ruby-units
|
|
91
91
|
* Can pass a strftime format string to to_s to format time
|
92
92
|
output
|
93
93
|
* can use U'1 mm' or '1 mm'.u to specify units now
|
94
|
-
|
94
|
+
|
95
|
+
2006-09-19 0.2.2 * tweaked temperature handling a bit. Now enter
|
96
|
+
temperatures like this:
|
97
|
+
'0 tempC'.unit #=> 273.15 degK
|
98
|
+
They will always be converted to kelvin to avoid
|
99
|
+
problems when temperatures are used in equations.
|
100
|
+
* added Time.in("5 min")
|
101
|
+
* added Unit.to_unit to simplify some calls
|
95
102
|
|
96
103
|
|
data/lib/ruby-units.rb
CHANGED
@@ -2,7 +2,7 @@ require 'mathn'
|
|
2
2
|
require 'rational'
|
3
3
|
require 'date'
|
4
4
|
require 'parsedate'
|
5
|
-
# = Ruby Units 0.2.
|
5
|
+
# = Ruby Units 0.2.2
|
6
6
|
#
|
7
7
|
# Copyright 2006 by Kevin C. Olbrich, Ph.D.
|
8
8
|
#
|
@@ -73,7 +73,6 @@ class Unit < Numeric
|
|
73
73
|
include Comparable
|
74
74
|
attr_accessor :scalar, :numerator, :denominator, :signature, :base_scalar
|
75
75
|
|
76
|
-
|
77
76
|
def to_yaml_properties
|
78
77
|
%w{@scalar @numerator @denominator @signature @base_scalar}
|
79
78
|
end
|
@@ -122,14 +121,15 @@ class Unit < Numeric
|
|
122
121
|
raise ArgumentError, "Invalid Unit Format"
|
123
122
|
end
|
124
123
|
self.update_base_scalar
|
124
|
+
self.replace_temperature
|
125
125
|
self.freeze
|
126
126
|
end
|
127
127
|
|
128
|
-
def
|
129
|
-
|
130
|
-
@denominator = other.denominator.clone
|
128
|
+
def to_unit
|
129
|
+
self
|
131
130
|
end
|
132
|
-
|
131
|
+
alias :unit :to_unit
|
132
|
+
|
133
133
|
# Returns 'true' if the Unit is represented in base units
|
134
134
|
def is_base?
|
135
135
|
return true if @signature == 400 && @numerator.size == 1 && @numerator[0] =~ /(celcius|kelvin|farenheit|rankine)/
|
@@ -143,6 +143,7 @@ class Unit < Numeric
|
|
143
143
|
#convert to base SI units
|
144
144
|
def to_base
|
145
145
|
return self if self.is_base?
|
146
|
+
# return self.to('degK') if self.units =~ /temp(C|K|F|R)/
|
146
147
|
num = []
|
147
148
|
den = []
|
148
149
|
q = @scalar
|
@@ -262,8 +263,9 @@ class Unit < Numeric
|
|
262
263
|
def +(other)
|
263
264
|
if Unit === other
|
264
265
|
if self =~ other then
|
265
|
-
|
266
|
-
|
266
|
+
a = self.to_base
|
267
|
+
b = other.to_base
|
268
|
+
Unit.new(:scalar=>(a.scalar + b.scalar), :numerator=>a.numerator, :denominator=>b.denominator).to(self)
|
267
269
|
else
|
268
270
|
raise ArgumentError, "Incompatible Units"
|
269
271
|
end
|
@@ -280,13 +282,14 @@ class Unit < Numeric
|
|
280
282
|
def -(other)
|
281
283
|
if Unit === other
|
282
284
|
if self =~ other then
|
283
|
-
|
284
|
-
|
285
|
+
a = self.to_base
|
286
|
+
b = other.to_base
|
287
|
+
Unit.new(:scalar=>(a.scalar - b.scalar), :numerator=>a.numerator, :denominator=>b.denominator).to(self)
|
285
288
|
else
|
286
289
|
raise ArgumentError, "Incompatible Units"
|
287
290
|
end
|
288
291
|
elsif Time === other
|
289
|
-
other
|
292
|
+
other - self
|
290
293
|
else
|
291
294
|
x,y = coerce(other)
|
292
295
|
x - y
|
@@ -411,34 +414,35 @@ class Unit < Numeric
|
|
411
414
|
return self if other.nil?
|
412
415
|
return self if TrueClass === other
|
413
416
|
return self if FalseClass === other
|
414
|
-
if String === other && other =~ /temp(K|C|R|F)/
|
417
|
+
if (Unit === other && other.units =~ /temp(K|C|R|F)/) || (String === other && other =~ /temp(K|C|R|F)/)
|
415
418
|
raise ArgumentError, "Receiver is not a temperature unit" unless self.signature==400
|
416
419
|
return self.to_base.to(other) unless self.is_base?
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
420
|
+
start_unit = self.units
|
421
|
+
target_unit = other.units rescue other
|
422
|
+
q=case start_unit
|
423
|
+
when 'degC':
|
424
|
+
case target_unit
|
421
425
|
when 'tempC' : @scalar
|
422
426
|
when 'tempK' : @scalar + 273.15
|
423
427
|
when 'tempF' : @scalar * (9.0/5.0) + 32.0
|
424
428
|
when 'tempR' : @scalar * (9.0/5.0) + 491.67
|
425
429
|
end
|
426
|
-
when
|
427
|
-
case
|
430
|
+
when 'degK':
|
431
|
+
case target_unit
|
428
432
|
when 'tempC' : @scalar - 273.15
|
429
433
|
when 'tempK' : @scalar
|
430
434
|
when 'tempF' : @scalar * (9.0/5.0) - 459.67
|
431
435
|
when 'tempR' : @scalar * (9.0/5.0)
|
432
436
|
end
|
433
|
-
when
|
434
|
-
case
|
437
|
+
when 'degF':
|
438
|
+
case target_unit
|
435
439
|
when 'tempC' : (@scalar-32)*(5.0/9.0)
|
436
440
|
when 'tempK' : (@scalar+459.67)*(5.0/9.0)
|
437
441
|
when 'tempF' : @scalar
|
438
442
|
when 'tempR' : @scalar + 459.67
|
439
443
|
end
|
440
|
-
when
|
441
|
-
case
|
444
|
+
when 'degR':
|
445
|
+
case target_unit
|
442
446
|
when 'tempC' : @scalar*(5.0/9.0) -273.15
|
443
447
|
when 'tempK' : @scalar*(5.0/9.0)
|
444
448
|
when 'tempF' : @scalar - 459.67
|
@@ -447,6 +451,7 @@ class Unit < Numeric
|
|
447
451
|
else
|
448
452
|
raise ArgumentError, "Unknown temperature conversion requested #{self.numerator}"
|
449
453
|
end
|
454
|
+
target_unit =~ /temp(C|K|F|R)/
|
450
455
|
Unit.new("#{q} deg#{$1}")
|
451
456
|
else
|
452
457
|
case other
|
@@ -468,84 +473,11 @@ class Unit < Numeric
|
|
468
473
|
Unit.new(:scalar=>q, :numerator=>target.numerator, :denominator=>target.denominator)
|
469
474
|
end
|
470
475
|
end
|
471
|
-
|
472
|
-
alias
|
473
|
-
|
474
|
-
def unit_signature_vector
|
475
|
-
return self.to_base.unit_signature_vector unless self.is_base?
|
476
|
-
result = self
|
477
|
-
y = [:length, :time, :temperature, :mass, :current, :substance, :luminosity, :currency, :memory, :angle]
|
478
|
-
vector = Array.new(y.size,0)
|
479
|
-
y.each_with_index do |units,index|
|
480
|
-
vector[index] = result.numerator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
|
481
|
-
vector[index] -= result.denominator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
|
482
|
-
end
|
483
|
-
vector
|
484
|
-
end
|
485
|
-
|
486
|
-
# calculates the unit signature id for use in comparing compatible units and simplification
|
487
|
-
# the signature is based on a simple classification of units and is based on the following publication
|
488
|
-
#
|
489
|
-
# Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering,
|
490
|
-
# 21(8), Aug 1995, pp.651-661
|
491
|
-
# doi://10.1109/32.403789
|
492
|
-
# http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr.
|
493
|
-
#
|
494
|
-
def unit_signature
|
495
|
-
vector = unit_signature_vector
|
496
|
-
vector.each_with_index {|item,index| vector[index] = item * 20**index}
|
497
|
-
@signature=vector.inject(0) {|sum,n| sum+n}
|
498
|
-
end
|
499
|
-
|
476
|
+
alias :>> :to
|
477
|
+
alias :convert_to :to
|
478
|
+
|
500
479
|
# Eliminates terms in the passed numerator and denominator. Expands out prefixes and applies them to the
|
501
480
|
# scalar. Returns a hash that can be used to initialize a new Unit object.
|
502
|
-
def self.eliminate_terms(q, n, d)
|
503
|
-
num = n.clone
|
504
|
-
den = d.clone
|
505
|
-
|
506
|
-
num.delete_if {|v| v == '<1>'}
|
507
|
-
den.delete_if {|v| v == '<1>'}
|
508
|
-
combined = Hash.new(0)
|
509
|
-
|
510
|
-
i = 0
|
511
|
-
loop do
|
512
|
-
break if i > num.size
|
513
|
-
if @@PREFIX_VALUES.has_key? num[i]
|
514
|
-
k = [num[i],num[i+1]]
|
515
|
-
i += 2
|
516
|
-
else
|
517
|
-
k = num[i]
|
518
|
-
i += 1
|
519
|
-
end
|
520
|
-
combined[k] += 1 unless k.nil? || k == '<1>'
|
521
|
-
end
|
522
|
-
|
523
|
-
j = 0
|
524
|
-
loop do
|
525
|
-
break if j > den.size
|
526
|
-
if @@PREFIX_VALUES.has_key? den[j]
|
527
|
-
k = [den[j],den[j+1]]
|
528
|
-
j += 2
|
529
|
-
else
|
530
|
-
k = den[j]
|
531
|
-
j += 1
|
532
|
-
end
|
533
|
-
combined[k] -= 1 unless k.nil? || k == '<1>'
|
534
|
-
end
|
535
|
-
|
536
|
-
num = []
|
537
|
-
den = []
|
538
|
-
combined.each do |key,value|
|
539
|
-
case
|
540
|
-
when value > 0 : value.times {num << key}
|
541
|
-
when value < 0 : value.abs.times {den << key}
|
542
|
-
end
|
543
|
-
end
|
544
|
-
num = ["<1>"] if num.empty?
|
545
|
-
den = ["<1>"] if den.empty?
|
546
|
-
{:scalar=>q, :numerator=>num.flatten.compact, :denominator=>den.flatten.compact}
|
547
|
-
end
|
548
|
-
|
549
481
|
# converts the unit back to a float if it is unitless
|
550
482
|
def to_f
|
551
483
|
return @scalar.to_f if self.unitless?
|
@@ -644,6 +576,7 @@ class Unit < Numeric
|
|
644
576
|
time_point - self
|
645
577
|
end
|
646
578
|
end
|
579
|
+
alias :before_now :before
|
647
580
|
|
648
581
|
# 'min'.since(time)
|
649
582
|
def since(time_point = ::Time.now)
|
@@ -667,11 +600,6 @@ class Unit < Numeric
|
|
667
600
|
end
|
668
601
|
end
|
669
602
|
|
670
|
-
# '5 min'.from_now
|
671
|
-
def from_now
|
672
|
-
self.from
|
673
|
-
end
|
674
|
-
|
675
603
|
# '5 min'.from(time)
|
676
604
|
def from(time_point = ::Time.now)
|
677
605
|
raise ArgumentError, "Must specify a Time" unless time_point
|
@@ -682,6 +610,7 @@ class Unit < Numeric
|
|
682
610
|
end
|
683
611
|
end
|
684
612
|
alias :after :from
|
613
|
+
alias :from_now :from
|
685
614
|
|
686
615
|
def succ
|
687
616
|
raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i
|
@@ -689,6 +618,10 @@ class Unit < Numeric
|
|
689
618
|
Unit.new([q, @numerator, @denominator])
|
690
619
|
end
|
691
620
|
|
621
|
+
# Protected and Private Functions that should only be called from this class
|
622
|
+
protected
|
623
|
+
|
624
|
+
|
692
625
|
def update_base_scalar
|
693
626
|
if self.is_base?
|
694
627
|
@base_scalar = @scalar
|
@@ -700,6 +633,7 @@ class Unit < Numeric
|
|
700
633
|
end
|
701
634
|
end
|
702
635
|
|
636
|
+
|
703
637
|
def coerce(other)
|
704
638
|
case other
|
705
639
|
when Unit : [other, self]
|
@@ -708,8 +642,107 @@ class Unit < Numeric
|
|
708
642
|
end
|
709
643
|
end
|
710
644
|
|
645
|
+
|
646
|
+
# calculates the unit signature vector used by unit_signature
|
647
|
+
def unit_signature_vector
|
648
|
+
return self.to_base.unit_signature_vector unless self.is_base?
|
649
|
+
result = self
|
650
|
+
y = [:length, :time, :temperature, :mass, :current, :substance, :luminosity, :currency, :memory, :angle]
|
651
|
+
vector = Array.new(y.size,0)
|
652
|
+
y.each_with_index do |units,index|
|
653
|
+
vector[index] = result.numerator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
|
654
|
+
vector[index] -= result.denominator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
|
655
|
+
end
|
656
|
+
vector
|
657
|
+
end
|
658
|
+
|
659
|
+
def replace_temperature
|
660
|
+
return self unless self.signature == 400 && self.units =~ /temp(R|K|F|C)/
|
661
|
+
un = $1
|
662
|
+
puts "self:#{self} #{un}"
|
663
|
+
target = self.units
|
664
|
+
@numerator = case un
|
665
|
+
when 'R' : ['<rankine>']
|
666
|
+
when 'C' : ['<celcius>']
|
667
|
+
when 'F' : ['<farenheit>']
|
668
|
+
when 'K' : ['<kelvin>']
|
669
|
+
end
|
670
|
+
r= self.to("tempK")
|
671
|
+
@numerator = r.numerator
|
672
|
+
@denominator = r.denominator
|
673
|
+
@scalar = r.scalar
|
674
|
+
end
|
675
|
+
|
676
|
+
|
711
677
|
private
|
712
678
|
|
679
|
+
def initialize_copy(other)
|
680
|
+
@numerator = other.numerator.clone
|
681
|
+
@denominator = other.denominator.clone
|
682
|
+
end
|
683
|
+
|
684
|
+
# calculates the unit signature id for use in comparing compatible units and simplification
|
685
|
+
# the signature is based on a simple classification of units and is based on the following publication
|
686
|
+
#
|
687
|
+
# Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering,
|
688
|
+
# 21(8), Aug 1995, pp.651-661
|
689
|
+
# doi://10.1109/32.403789
|
690
|
+
# http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr.
|
691
|
+
#
|
692
|
+
def unit_signature
|
693
|
+
vector = unit_signature_vector
|
694
|
+
vector.each_with_index {|item,index| vector[index] = item * 20**index}
|
695
|
+
@signature=vector.inject(0) {|sum,n| sum+n}
|
696
|
+
end
|
697
|
+
|
698
|
+
def self.eliminate_terms(q, n, d)
|
699
|
+
num = n.clone
|
700
|
+
den = d.clone
|
701
|
+
|
702
|
+
num.delete_if {|v| v == '<1>'}
|
703
|
+
den.delete_if {|v| v == '<1>'}
|
704
|
+
combined = Hash.new(0)
|
705
|
+
|
706
|
+
i = 0
|
707
|
+
loop do
|
708
|
+
break if i > num.size
|
709
|
+
if @@PREFIX_VALUES.has_key? num[i]
|
710
|
+
k = [num[i],num[i+1]]
|
711
|
+
i += 2
|
712
|
+
else
|
713
|
+
k = num[i]
|
714
|
+
i += 1
|
715
|
+
end
|
716
|
+
combined[k] += 1 unless k.nil? || k == '<1>'
|
717
|
+
end
|
718
|
+
|
719
|
+
j = 0
|
720
|
+
loop do
|
721
|
+
break if j > den.size
|
722
|
+
if @@PREFIX_VALUES.has_key? den[j]
|
723
|
+
k = [den[j],den[j+1]]
|
724
|
+
j += 2
|
725
|
+
else
|
726
|
+
k = den[j]
|
727
|
+
j += 1
|
728
|
+
end
|
729
|
+
combined[k] -= 1 unless k.nil? || k == '<1>'
|
730
|
+
end
|
731
|
+
|
732
|
+
num = []
|
733
|
+
den = []
|
734
|
+
combined.each do |key,value|
|
735
|
+
case
|
736
|
+
when value > 0 : value.times {num << key}
|
737
|
+
when value < 0 : value.abs.times {den << key}
|
738
|
+
end
|
739
|
+
end
|
740
|
+
num = ["<1>"] if num.empty?
|
741
|
+
den = ["<1>"] if den.empty?
|
742
|
+
{:scalar=>q, :numerator=>num.flatten.compact, :denominator=>den.flatten.compact}
|
743
|
+
end
|
744
|
+
|
745
|
+
|
713
746
|
# parse a string into a unit object.
|
714
747
|
# Typical formats like :
|
715
748
|
# "5.6 kg*m/s^2"
|
@@ -926,6 +959,10 @@ class Time
|
|
926
959
|
end
|
927
960
|
end
|
928
961
|
|
962
|
+
def self.in(duration)
|
963
|
+
Time.now + duration.to_unit
|
964
|
+
end
|
965
|
+
|
929
966
|
alias :unit_sub :-
|
930
967
|
def -(other)
|
931
968
|
if Unit === other
|
data/lib/units.rb
CHANGED
@@ -93,6 +93,10 @@ UNIT_DEFINITIONS = {
|
|
93
93
|
'<celcius>' => [%w{degC celcius Celcius}, 1.0, :temperature, %w{<kelvin>}],
|
94
94
|
'<farenheit>' => [%w{degF farenheit Farenheit}, 1.8, :temperature, %w{<kelvin>}],
|
95
95
|
'<rankine>' => [%w{degR rankine Rankine}, 1.8, :temperature, %w{<kelvin>}],
|
96
|
+
'<temp-K>' => [%w{tempK}, 1.0, :temperature, %w{<kelvin>}],
|
97
|
+
'<temp-C>' => [%w{tempC}, 1.0, :temperature, %w{<kelvin>}],
|
98
|
+
'<temp-F>' => [%w{tempF}, 1.0, :temperature, %w{<kelvin>}],
|
99
|
+
'<temp-R>' => [%w{tempR}, 1.0, :temperature, %w{<kelvin>}],
|
96
100
|
|
97
101
|
#time
|
98
102
|
'<second>'=> [%w{s sec second seconds}, 1.0, :time, %w{<second>}],
|
data/test/test_ruby-units.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'test/unit'
|
2
|
+
require 'rubygems'
|
2
3
|
require 'ruby-units'
|
4
|
+
require 'yaml'
|
5
|
+
require 'uncertain'
|
3
6
|
|
4
7
|
class Unit < Numeric
|
5
8
|
@@USER_DEFINITIONS = {'<inchworm>' => [%w{inworm inchworm}, 0.0254, :length, %w{<meter>} ],
|
@@ -10,6 +13,27 @@ end
|
|
10
13
|
|
11
14
|
class TestRubyUnits < Test::Unit::TestCase
|
12
15
|
|
16
|
+
def test_to_yaml
|
17
|
+
unit = "1 mm".u
|
18
|
+
assert_equal "--- !ruby/object:Unit \nscalar: 1.0\nnumerator: \n- <milli>\n- <meter>\ndenominator: \n- <1>\nsignature: 1\nbase_scalar: 0.001\n", unit.to_yaml
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_time
|
22
|
+
a = Time.now
|
23
|
+
assert_equal "s", a.to_unit.units
|
24
|
+
assert_equal a + 3600, a + "1 h".unit
|
25
|
+
assert_equal a - 3600, a - "1 h".unit
|
26
|
+
b = Unit(a) + "1 h".unit
|
27
|
+
assert_in_delta Time.now - "1 h".unit, "1 h".ago, 1
|
28
|
+
assert_in_delta Time.now + 3600, "1 h".from_now, 1
|
29
|
+
assert_in_delta "1 h".unit + Time.now, "1 h".from_now, 1
|
30
|
+
assert_in_delta Time.now - 3600, "1 h".before_now, 1
|
31
|
+
assert_in_delta (Time.now.unit - Time.now).unit.scalar, 0, 1
|
32
|
+
|
33
|
+
assert_equal "60 min", "min".until(Time.now + 3600).to_s
|
34
|
+
assert_equal "01:00", "min".since(Time.now - 3600).to_s("%H:%M")
|
35
|
+
end
|
36
|
+
|
13
37
|
def test_clone
|
14
38
|
unit1= "1 mm".unit
|
15
39
|
unit2 = unit1.clone
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: ruby-units
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.2.
|
7
|
-
date: 2006-09-
|
6
|
+
version: 0.2.2
|
7
|
+
date: 2006-09-19 00:00:00 -04:00
|
8
8
|
summary: A model that performs unit conversions and unit math
|
9
9
|
require_paths:
|
10
10
|
- lib
|