ruby-units 0.2.1 → 0.2.2
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/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
|