ruby-units 1.4.3 → 1.4.4
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.txt +6 -0
- data/README.md +19 -6
- data/VERSION +1 -1
- data/lib/ruby-units.rb +8 -13
- data/lib/ruby_units.rb +8 -13
- data/lib/ruby_units/array.rb +4 -4
- data/lib/ruby_units/cache.rb +2 -2
- data/lib/ruby_units/definition.rb +6 -6
- data/lib/ruby_units/fixnum.rb +2 -2
- data/lib/ruby_units/math.rb +12 -12
- data/lib/ruby_units/namespaced.rb +16 -0
- data/lib/ruby_units/numeric.rb +3 -3
- data/lib/ruby_units/string.rb +5 -5
- data/lib/ruby_units/time.rb +9 -8
- data/lib/ruby_units/unit.rb +1408 -1385
- data/lib/ruby_units/unit_definitions.rb +3 -3
- data/lib/ruby_units/unit_definitions/base.rb +16 -16
- data/lib/ruby_units/unit_definitions/prefix.rb +2 -2
- data/lib/ruby_units/unit_definitions/standard.rb +268 -268
- data/lib/ruby_units/version.rb +7 -5
- data/ruby-units.gemspec +3 -2
- metadata +4 -3
data/CHANGELOG.txt
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
Change Log for Ruby-units
|
2
2
|
=========================
|
3
|
+
2013-07-19 1.4.4 * Fix issue #4 -- .best_prefix method
|
4
|
+
* Fix issue #60 -- Consider placing Unit in a module
|
5
|
+
* Fix issue #75 -- Siemens is kind of conductance not resistance
|
6
|
+
* Fix issue #36 -- Don't require spaces in units
|
7
|
+
* Fix issue #68 -- ditto
|
8
|
+
* Fix issue #16 -- ditto
|
3
9
|
2013-06-11 1.4.3 * Fix issue #70 -- Support passing Time objects to Time.at
|
4
10
|
* Fix issue #72 -- Remove non-existent RakeFile from gemspec
|
5
11
|
* Fix issue #71 -- Fix YAML test failure
|
data/README.md
CHANGED
@@ -2,9 +2,7 @@
|
|
2
2
|
|
3
3
|
[](http://travis-ci.org/olbrich/ruby-units)
|
4
4
|
|
5
|
-
Kevin C. Olbrich, Ph.D.
|
6
|
-
|
7
|
-
[Sciwerks.com](http://www.sciwerks.com)
|
5
|
+
Kevin C. Olbrich, Ph.D.
|
8
6
|
|
9
7
|
Project page: [http://github.com/olbrich/ruby-units](http://github.com/olbrich/ruby-units)
|
10
8
|
|
@@ -34,9 +32,9 @@ This package may be installed using: `gem install ruby-units`
|
|
34
32
|
## Rules:
|
35
33
|
1. only 1 quantity per unit (with 2 exceptions... 6'5" and '8 lbs 8 oz')
|
36
34
|
2. use SI notation when possible
|
37
|
-
3.
|
35
|
+
3. spaces in units are allowed, but ones like '11/m' will be recognized as '11 1/m'.
|
38
36
|
|
39
|
-
## Unit
|
37
|
+
## Unit compatibility:
|
40
38
|
Many methods require that the units of two operands are compatible. Compatible units are those that can be easily converted into each other, such as 'meters' and 'feet'.
|
41
39
|
|
42
40
|
unit1 =~ unit2 #=> true if units are compatible
|
@@ -170,4 +168,19 @@ This is useful for changing display names, adding aliases, etc.
|
|
170
168
|
Unit.redefine!("cup") do |cup|
|
171
169
|
cup.display_name = "cup"
|
172
170
|
end
|
173
|
-
|
171
|
+
|
172
|
+
|
173
|
+
### Namespaced Class
|
174
|
+
|
175
|
+
Sometimes the default class 'Unit' may conflict with other gems or applications. Internally ruby-units defines itself using the RubyUnits namespace.
|
176
|
+
The actual class of a unit is the RubyUnits::Unit. For simplicity and backwards compatiblity, the '::Unit' class is defined as an alias to '::RubyUnits::Unit'.
|
177
|
+
|
178
|
+
To load ruby-units without this alias...
|
179
|
+
|
180
|
+
require 'ruby-units/namespaced'
|
181
|
+
|
182
|
+
When using bundler...
|
183
|
+
|
184
|
+
gem 'ruby-units', require: 'namespaced'
|
185
|
+
|
186
|
+
Note: when using the namespaced version, the Unit('unit string') helper will not be defined.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.4.
|
1
|
+
1.4.4
|
data/lib/ruby-units.rb
CHANGED
@@ -1,14 +1,9 @@
|
|
1
1
|
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
require 'ruby_units/object'
|
11
|
-
require 'ruby_units/string'
|
12
|
-
require 'ruby_units/unit'
|
13
|
-
require 'ruby_units/fixnum'
|
14
|
-
require 'ruby_units/unit_definitions'
|
2
|
+
|
3
|
+
require_relative 'ruby_units/namespaced'
|
4
|
+
|
5
|
+
# only include the Unit('unit') helper if we aren't fully namespaced
|
6
|
+
require_relative 'ruby_units/object'
|
7
|
+
|
8
|
+
|
9
|
+
Unit = RubyUnits::Unit
|
data/lib/ruby_units.rb
CHANGED
@@ -1,14 +1,9 @@
|
|
1
1
|
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
require 'ruby_units/object'
|
11
|
-
require 'ruby_units/string'
|
12
|
-
require 'ruby_units/unit'
|
13
|
-
require 'ruby_units/fixnum'
|
14
|
-
require 'ruby_units/unit_definitions'
|
2
|
+
|
3
|
+
require_relative 'ruby_units/namespaced'
|
4
|
+
|
5
|
+
# only include the Unit('unit') helper if we aren't fully namespaced
|
6
|
+
require_relative 'ruby_units/object'
|
7
|
+
|
8
|
+
|
9
|
+
Unit = RubyUnits::Unit
|
data/lib/ruby_units/array.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
class Array
|
2
2
|
# Construct a unit from an array
|
3
|
-
# @example [1, 'mm'].to_unit => Unit("1 mm")
|
4
|
-
# @return (see Unit#initialize)
|
3
|
+
# @example [1, 'mm'].to_unit => RubyUnits::Unit("1 mm")
|
4
|
+
# @return (see RubyUnits::Unit#initialize)
|
5
5
|
# @param [Object] other convert to same units as passed
|
6
6
|
def to_unit(other = nil)
|
7
|
-
other ? Unit.new(self).convert_to(other) : Unit.new(self)
|
7
|
+
other ? RubyUnits::Unit.new(self).convert_to(other) : RubyUnits::Unit.new(self)
|
8
8
|
end
|
9
9
|
alias :unit :to_unit
|
10
10
|
alias :u :to_unit
|
11
|
-
end
|
11
|
+
end
|
data/lib/ruby_units/cache.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
class Unit < Numeric
|
1
|
+
class RubyUnits::Unit < Numeric
|
2
2
|
|
3
3
|
# Handle the definition of units
|
4
4
|
class Definition
|
@@ -36,8 +36,8 @@ class Unit < Numeric
|
|
36
36
|
@aliases ||= (_definition[0] || [_name])
|
37
37
|
@scalar ||= _definition[1]
|
38
38
|
@kind ||= _definition[2]
|
39
|
-
@numerator ||= _definition[3] || Unit::UNITY_ARRAY
|
40
|
-
@denominator ||= _definition[4] || Unit::UNITY_ARRAY
|
39
|
+
@numerator ||= _definition[3] || RubyUnits::Unit::UNITY_ARRAY
|
40
|
+
@denominator ||= _definition[4] || RubyUnits::Unit::UNITY_ARRAY
|
41
41
|
@display_name ||= @aliases.first
|
42
42
|
end
|
43
43
|
|
@@ -90,11 +90,11 @@ class Unit < Numeric
|
|
90
90
|
# units are base units if the scalar is one, and the unit is defined in terms of itself.
|
91
91
|
# @return [Boolean]
|
92
92
|
def base?
|
93
|
-
(self.denominator == Unit::UNITY_ARRAY) &&
|
94
|
-
(self.numerator != Unit::UNITY_ARRAY) &&
|
93
|
+
(self.denominator == RubyUnits::Unit::UNITY_ARRAY) &&
|
94
|
+
(self.numerator != RubyUnits::Unit::UNITY_ARRAY) &&
|
95
95
|
(self.numerator.size == 1) &&
|
96
96
|
(self.scalar == 1) &&
|
97
97
|
(self.numerator.first == self.name)
|
98
98
|
end
|
99
99
|
end
|
100
|
-
end
|
100
|
+
end
|
data/lib/ruby_units/fixnum.rb
CHANGED
@@ -8,7 +8,7 @@ if RUBY_VERSION < "1.9"
|
|
8
8
|
# @return [Unit, Integer]
|
9
9
|
def quo_with_units(other)
|
10
10
|
case other
|
11
|
-
when Unit
|
11
|
+
when RubyUnits::Unit
|
12
12
|
self * other.inverse
|
13
13
|
else
|
14
14
|
quo_without_units(other)
|
@@ -19,4 +19,4 @@ if RUBY_VERSION < "1.9"
|
|
19
19
|
alias / quo_with_units
|
20
20
|
end
|
21
21
|
# :nocov_19:
|
22
|
-
end
|
22
|
+
end
|
data/lib/ruby_units/math.rb
CHANGED
@@ -7,7 +7,7 @@ module Math
|
|
7
7
|
alias :unit_sqrt :sqrt
|
8
8
|
# @return [Numeric]
|
9
9
|
def sqrt(n)
|
10
|
-
if Unit === n
|
10
|
+
if RubyUnits::Unit === n
|
11
11
|
(n**(Rational(1,2))).to_unit
|
12
12
|
else
|
13
13
|
unit_sqrt(n)
|
@@ -23,7 +23,7 @@ module Math
|
|
23
23
|
alias :unit_cbrt :cbrt
|
24
24
|
# @return [Numeric]
|
25
25
|
def cbrt(n)
|
26
|
-
if Unit === n
|
26
|
+
if RubyUnits::Unit === n
|
27
27
|
(n**(Rational(1,3))).to_unit
|
28
28
|
else
|
29
29
|
unit_cbrt(n)
|
@@ -39,7 +39,7 @@ module Math
|
|
39
39
|
alias :unit_sin :sin
|
40
40
|
# @return [Numeric]
|
41
41
|
def sin(n)
|
42
|
-
Unit === n ? unit_sin(n.convert_to('radian').scalar) : unit_sin(n)
|
42
|
+
RubyUnits::Unit === n ? unit_sin(n.convert_to('radian').scalar) : unit_sin(n)
|
43
43
|
end
|
44
44
|
# @return [Numeric]
|
45
45
|
module_function :unit_sin
|
@@ -49,7 +49,7 @@ module Math
|
|
49
49
|
alias :unit_cos :cos
|
50
50
|
# @return [Numeric]
|
51
51
|
def cos(n)
|
52
|
-
Unit === n ? unit_cos(n.convert_to('radian').scalar) : unit_cos(n)
|
52
|
+
RubyUnits::Unit === n ? unit_cos(n.convert_to('radian').scalar) : unit_cos(n)
|
53
53
|
end
|
54
54
|
# @return [Numeric]
|
55
55
|
module_function :unit_cos
|
@@ -59,7 +59,7 @@ module Math
|
|
59
59
|
alias :unit_sinh :sinh
|
60
60
|
# @return [Numeric]
|
61
61
|
def sinh(n)
|
62
|
-
Unit === n ? unit_sinh(n.convert_to('radian').scalar) : unit_sinh(n)
|
62
|
+
RubyUnits::Unit === n ? unit_sinh(n.convert_to('radian').scalar) : unit_sinh(n)
|
63
63
|
end
|
64
64
|
# @return [Numeric]
|
65
65
|
module_function :unit_sinh
|
@@ -69,7 +69,7 @@ module Math
|
|
69
69
|
alias :unit_cosh :cosh
|
70
70
|
# @return [Numeric]
|
71
71
|
def cosh(n)
|
72
|
-
Unit === n ? unit_cosh(n.convert_to('radian').scalar) : unit_cosh(n)
|
72
|
+
RubyUnits::Unit === n ? unit_cosh(n.convert_to('radian').scalar) : unit_cosh(n)
|
73
73
|
end
|
74
74
|
# @return [Numeric]
|
75
75
|
module_function :unit_cosh
|
@@ -79,7 +79,7 @@ module Math
|
|
79
79
|
alias :unit_tan :tan
|
80
80
|
# @return [Numeric]
|
81
81
|
def tan(n)
|
82
|
-
Unit === n ? unit_tan(n.convert_to('radian').scalar) : unit_tan(n)
|
82
|
+
RubyUnits::Unit === n ? unit_tan(n.convert_to('radian').scalar) : unit_tan(n)
|
83
83
|
end
|
84
84
|
# @return [Numeric]
|
85
85
|
module_function :tan
|
@@ -89,7 +89,7 @@ module Math
|
|
89
89
|
alias :unit_tanh :tanh
|
90
90
|
# @return [Numeric]
|
91
91
|
def tanh(n)
|
92
|
-
Unit === n ? unit_tanh(n.convert_to('radian').scalar) : unit_tanh(n)
|
92
|
+
RubyUnits::Unit === n ? unit_tanh(n.convert_to('radian').scalar) : unit_tanh(n)
|
93
93
|
end
|
94
94
|
# @return [Numeric]
|
95
95
|
module_function :unit_tanh
|
@@ -100,7 +100,7 @@ module Math
|
|
100
100
|
# Convert parameters to consistent units and perform the function
|
101
101
|
# @return [Numeric]
|
102
102
|
def hypot(x,y)
|
103
|
-
if Unit === x && Unit === y
|
103
|
+
if RubyUnits::Unit === x && RubyUnits::Unit === y
|
104
104
|
(x**2 + y**2)**(1/2)
|
105
105
|
else
|
106
106
|
unit_hypot(x,y)
|
@@ -115,9 +115,9 @@ module Math
|
|
115
115
|
# @return [Numeric]
|
116
116
|
def atan2(x,y)
|
117
117
|
case
|
118
|
-
when (x.is_a?(Unit) && y.is_a?(Unit)) && (x !~ y)
|
119
|
-
raise ArgumentError, "Incompatible Units"
|
120
|
-
when (x.is_a?(Unit) && y.is_a?(Unit)) && (x =~ y)
|
118
|
+
when (x.is_a?(RubyUnits::Unit) && y.is_a?(RubyUnits::Unit)) && (x !~ y)
|
119
|
+
raise ArgumentError, "Incompatible RubyUnits::Units"
|
120
|
+
when (x.is_a?(RubyUnits::Unit) && y.is_a?(RubyUnits::Unit)) && (x =~ y)
|
121
121
|
Math::unit_atan2(x.base_scalar, y.base_scalar)
|
122
122
|
else
|
123
123
|
Math::unit_atan2(x,y)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
3
|
+
|
4
|
+
# require_relative this file to avoid creating an class alias from Unit to RubyUnits::Unit
|
5
|
+
require_relative 'version'
|
6
|
+
require_relative 'definition'
|
7
|
+
require_relative 'cache'
|
8
|
+
require_relative 'array'
|
9
|
+
require_relative 'date'
|
10
|
+
require_relative 'time'
|
11
|
+
require_relative 'math'
|
12
|
+
require_relative 'numeric'
|
13
|
+
require_relative 'string'
|
14
|
+
require_relative 'unit'
|
15
|
+
require_relative 'fixnum'
|
16
|
+
require_relative 'unit_definitions'
|
data/lib/ruby_units/numeric.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
class Numeric
|
2
2
|
# make a unitless unit with a given scalar
|
3
|
-
# @return (see Unit#initialize)
|
3
|
+
# @return (see RubyUnits::Unit#initialize)
|
4
4
|
def to_unit(other = nil)
|
5
|
-
other ? Unit.new(self, other) : Unit.new(self)
|
5
|
+
other ? RubyUnits::Unit.new(self, other) : RubyUnits::Unit.new(self)
|
6
6
|
end
|
7
7
|
alias :unit :to_unit
|
8
8
|
alias :u :to_unit
|
9
|
-
end
|
9
|
+
end
|
data/lib/ruby_units/string.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'time'
|
2
2
|
class String
|
3
3
|
# make a string into a unit
|
4
|
-
# @return (see Unit#initialize)
|
4
|
+
# @return (see RubyUnits::Unit#initialize)
|
5
5
|
def to_unit(other = nil)
|
6
|
-
other ? Unit.new(self).convert_to(other) : Unit.new(self)
|
6
|
+
other ? RubyUnits::Unit.new(self).convert_to(other) : RubyUnits::Unit.new(self)
|
7
7
|
end
|
8
8
|
alias :unit :to_unit
|
9
9
|
alias :u :to_unit
|
@@ -16,7 +16,7 @@ class String
|
|
16
16
|
def %(*args)
|
17
17
|
return "" if self.empty?
|
18
18
|
case
|
19
|
-
when args.first.is_a?(Unit)
|
19
|
+
when args.first.is_a?(RubyUnits::Unit)
|
20
20
|
args.first.to_s(self)
|
21
21
|
when (!defined?(Uncertain).nil? && args.first.is_a?(Uncertain))
|
22
22
|
args.first.to_s(self)
|
@@ -27,8 +27,8 @@ class String
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
# @param (see Unit#convert_to)
|
31
|
-
# @return (see Unit#convert_to)
|
30
|
+
# @param (see RubyUnits::Unit#convert_to)
|
31
|
+
# @return (see RubyUnits::Unit#convert_to)
|
32
32
|
def convert_to(other)
|
33
33
|
self.unit.convert_to(other)
|
34
34
|
end
|
data/lib/ruby_units/time.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'time'
|
1
2
|
#
|
2
3
|
# Time math is handled slightly differently. The difference is considered to be an exact duration if
|
3
4
|
# the subtracted value is in hours, minutes, or seconds. It is rounded to the nearest day if the offset
|
@@ -13,21 +14,21 @@ class Time
|
|
13
14
|
# epoch
|
14
15
|
# @param [Time] arg
|
15
16
|
# @param [Integer] ms
|
16
|
-
# @return [Unit, Time]
|
17
|
+
# @return [RubyUnits::Unit, Time]
|
17
18
|
def self.at(arg,ms=0)
|
18
19
|
case arg
|
19
20
|
when Time
|
20
21
|
unit_time_at(arg)
|
21
|
-
when Unit
|
22
|
+
when RubyUnits::Unit
|
22
23
|
unit_time_at(arg.convert_to("s").scalar, ms)
|
23
24
|
else
|
24
25
|
unit_time_at(arg, ms)
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
|
-
# @return (see Unit#initialize)
|
29
|
+
# @return (see RubyUnits::Unit#initialize)
|
29
30
|
def to_unit(other = nil)
|
30
|
-
other ? Unit.new(self).convert_to(other) : Unit.new(self)
|
31
|
+
other ? RubyUnits::Unit.new(self).convert_to(other) : RubyUnits::Unit.new(self)
|
31
32
|
end
|
32
33
|
alias :unit :to_unit
|
33
34
|
alias :u :to_unit
|
@@ -39,10 +40,10 @@ class Time
|
|
39
40
|
end
|
40
41
|
|
41
42
|
alias :unit_add :+
|
42
|
-
# @return [Unit, Time]
|
43
|
+
# @return [RubyUnits::Unit, Time]
|
43
44
|
def +(other)
|
44
45
|
case other
|
45
|
-
when Unit
|
46
|
+
when RubyUnits::Unit
|
46
47
|
other = other.convert_to('d').round.convert_to('s') if ['y', 'decade', 'century'].include? other.units
|
47
48
|
begin
|
48
49
|
unit_add(other.convert_to('s').scalar)
|
@@ -63,10 +64,10 @@ class Time
|
|
63
64
|
|
64
65
|
alias :unit_sub :-
|
65
66
|
|
66
|
-
# @return [Unit, Time]
|
67
|
+
# @return [RubyUnits::Unit, Time]
|
67
68
|
def -(other)
|
68
69
|
case other
|
69
|
-
when Unit
|
70
|
+
when RubyUnits::Unit
|
70
71
|
other = other.convert_to('d').round.convert_to('s') if ['y', 'decade', 'century'].include? other.units
|
71
72
|
begin
|
72
73
|
unit_sub(other.convert_to('s').scalar)
|
data/lib/ruby_units/unit.rb
CHANGED
@@ -23,1569 +23,1592 @@ end
|
|
23
23
|
#
|
24
24
|
# To add or override a unit definition, add a code block like this..
|
25
25
|
# @example Define a new unit
|
26
|
-
# Unit.define("foobar") do |unit|
|
26
|
+
# RubyUnits::Unit.define("foobar") do |unit|
|
27
27
|
# unit.aliases = %w{foo fb foo-bar}
|
28
|
-
# unit.definition = Unit("1 baz")
|
28
|
+
# unit.definition = RubyUnits::Unit.new("1 baz")
|
29
29
|
# end
|
30
30
|
#
|
31
31
|
# @todo fix class variables so they conform to standard naming conventions and refactor away as many of them as possible
|
32
32
|
# @todo pull caching out into its own class
|
33
33
|
# @todo refactor internal representation of units
|
34
34
|
# @todo method to determine best natural prefix
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@@PREFIX_MAP = {}
|
40
|
-
@@UNIT_MAP = {}
|
41
|
-
@@UNIT_VALUES = {}
|
42
|
-
@@UNIT_REGEX = nil
|
43
|
-
@@UNIT_MATCH_REGEX = nil
|
44
|
-
UNITY = '<1>'
|
45
|
-
UNITY_ARRAY = [UNITY]
|
46
|
-
FEET_INCH_REGEX = /(\d+)\s*(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inches)/
|
47
|
-
TIME_REGEX = /(\d+)*:(\d+)*:*(\d+)*[:,]*(\d+)*/
|
48
|
-
LBS_OZ_REGEX = /(\d+)\s*(?:#|lbs|pounds|pound-mass)+[\s,]*(\d+)\s*(?:oz|ounces)/
|
49
|
-
SCI_NUMBER = %r{([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)}
|
50
|
-
RATIONAL_NUMBER = /\(?([+-]?\d+)\/(\d+)\)?/
|
51
|
-
COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/
|
52
|
-
NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/
|
53
|
-
UNIT_STRING_REGEX = /#{SCI_NUMBER}*\s*([^\/]*)\/*(.+)*/
|
54
|
-
TOP_REGEX = /([^ \*]+)(?:\^|\*\*)([\d-]+)/
|
55
|
-
BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/
|
56
|
-
UNCERTAIN_REGEX = /#{SCI_NUMBER}\s*\+\/-\s*#{SCI_NUMBER}\s(.+)/
|
57
|
-
COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/
|
58
|
-
RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(.+)?/
|
59
|
-
KELVIN = ['<kelvin>']
|
60
|
-
FAHRENHEIT = ['<fahrenheit>']
|
61
|
-
RANKINE = ['<rankine>']
|
62
|
-
CELSIUS = ['<celsius>']
|
63
|
-
@@TEMP_REGEX = nil
|
64
|
-
SIGNATURE_VECTOR = [
|
65
|
-
:length,
|
66
|
-
:time,
|
67
|
-
:temperature,
|
68
|
-
:mass,
|
69
|
-
:current,
|
70
|
-
:substance,
|
71
|
-
:luminosity,
|
72
|
-
:currency,
|
73
|
-
:memory,
|
74
|
-
:angle
|
75
|
-
]
|
76
|
-
@@KINDS = {
|
77
|
-
-312078 => :elastance,
|
78
|
-
-312058 => :resistance,
|
79
|
-
-312038 => :inductance,
|
80
|
-
-152040 => :magnetism,
|
81
|
-
-152038 => :magnetism,
|
82
|
-
-152058 => :potential,
|
83
|
-
-7997 => :specific_volume,
|
84
|
-
-79 => :snap,
|
85
|
-
-59 => :jolt,
|
86
|
-
-39 => :acceleration,
|
87
|
-
-38 => :radiation,
|
88
|
-
-20 => :frequency,
|
89
|
-
-19 => :speed,
|
90
|
-
-18 => :viscosity,
|
91
|
-
-17 => :volumetric_flow,
|
92
|
-
-1 => :wavenumber,
|
93
|
-
0 => :unitless,
|
94
|
-
1 => :length,
|
95
|
-
2 => :area,
|
96
|
-
3 => :volume,
|
97
|
-
20 => :time,
|
98
|
-
400 => :temperature,
|
99
|
-
7941 => :yank,
|
100
|
-
7942 => :power,
|
101
|
-
7959 => :pressure,
|
102
|
-
7962 => :energy,
|
103
|
-
7979 => :viscosity,
|
104
|
-
7961 => :force,
|
105
|
-
7981 => :momentum,
|
106
|
-
7982 => :angular_momentum,
|
107
|
-
7997 => :density,
|
108
|
-
7998 => :area_density,
|
109
|
-
8000 => :mass,
|
110
|
-
152020 => :radiation_exposure,
|
111
|
-
159999 => :magnetism,
|
112
|
-
160000 => :current,
|
113
|
-
160020 => :charge,
|
114
|
-
312058 => :resistance,
|
115
|
-
312078 => :capacitance,
|
116
|
-
3199980 => :activity,
|
117
|
-
3199997 => :molar_concentration,
|
118
|
-
3200000 => :substance,
|
119
|
-
63999998 => :illuminance,
|
120
|
-
64000000 => :luminous_power,
|
121
|
-
1280000000 => :currency,
|
122
|
-
25600000000 => :memory,
|
123
|
-
511999999980 => :angular_velocity,
|
124
|
-
512000000000 => :angle
|
125
|
-
}
|
126
|
-
@@cached_units = {}
|
127
|
-
@@base_unit_cache = {}
|
128
|
-
|
129
|
-
# setup internal arrays and hashes
|
130
|
-
# @return [true]
|
131
|
-
def self.setup
|
132
|
-
self.clear_cache
|
35
|
+
module RubyUnits
|
36
|
+
class Unit < Numeric
|
37
|
+
VERSION = Unit::Version::STRING
|
38
|
+
@@definitions = {}
|
133
39
|
@@PREFIX_VALUES = {}
|
134
40
|
@@PREFIX_MAP = {}
|
135
|
-
@@UNIT_VALUES = {}
|
136
41
|
@@UNIT_MAP = {}
|
42
|
+
@@UNIT_VALUES = {}
|
137
43
|
@@UNIT_REGEX = nil
|
138
44
|
@@UNIT_MATCH_REGEX = nil
|
139
|
-
|
45
|
+
UNITY = '<1>'
|
46
|
+
UNITY_ARRAY = [UNITY]
|
47
|
+
FEET_INCH_REGEX = /(\d+)\s*(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inches)/
|
48
|
+
TIME_REGEX = /(\d+)*:(\d+)*:*(\d+)*[:,]*(\d+)*/
|
49
|
+
LBS_OZ_REGEX = /(\d+)\s*(?:#|lbs|pounds|pound-mass)+[\s,]*(\d+)\s*(?:oz|ounces)/
|
50
|
+
SCI_NUMBER = %r{([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)}
|
51
|
+
RATIONAL_NUMBER = /\(?([+-]?\d+)\/(\d+)\)?/
|
52
|
+
COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/
|
53
|
+
NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/
|
54
|
+
UNIT_STRING_REGEX = /#{SCI_NUMBER}*\s*([^\/]*)\/*(.+)*/
|
55
|
+
TOP_REGEX = /([^ \*]+)(?:\^|\*\*)([\d-]+)/
|
56
|
+
BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/
|
57
|
+
UNCERTAIN_REGEX = /#{SCI_NUMBER}\s*\+\/-\s*#{SCI_NUMBER}\s(.+)/
|
58
|
+
COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/
|
59
|
+
RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(.+)?/
|
60
|
+
KELVIN = ['<kelvin>']
|
61
|
+
FAHRENHEIT = ['<fahrenheit>']
|
62
|
+
RANKINE = ['<rankine>']
|
63
|
+
CELSIUS = ['<celsius>']
|
64
|
+
@@TEMP_REGEX = nil
|
65
|
+
SIGNATURE_VECTOR = [
|
66
|
+
:length,
|
67
|
+
:time,
|
68
|
+
:temperature,
|
69
|
+
:mass,
|
70
|
+
:current,
|
71
|
+
:substance,
|
72
|
+
:luminosity,
|
73
|
+
:currency,
|
74
|
+
:information,
|
75
|
+
:angle
|
76
|
+
]
|
77
|
+
@@KINDS = {
|
78
|
+
-312078 => :elastance,
|
79
|
+
-312058 => :resistance,
|
80
|
+
-312038 => :inductance,
|
81
|
+
-152040 => :magnetism,
|
82
|
+
-152038 => :magnetism,
|
83
|
+
-152058 => :potential,
|
84
|
+
-7997 => :specific_volume,
|
85
|
+
-79 => :snap,
|
86
|
+
-59 => :jolt,
|
87
|
+
-39 => :acceleration,
|
88
|
+
-38 => :radiation,
|
89
|
+
-20 => :frequency,
|
90
|
+
-19 => :speed,
|
91
|
+
-18 => :viscosity,
|
92
|
+
-17 => :volumetric_flow,
|
93
|
+
-1 => :wavenumber,
|
94
|
+
0 => :unitless,
|
95
|
+
1 => :length,
|
96
|
+
2 => :area,
|
97
|
+
3 => :volume,
|
98
|
+
20 => :time,
|
99
|
+
400 => :temperature,
|
100
|
+
7941 => :yank,
|
101
|
+
7942 => :power,
|
102
|
+
7959 => :pressure,
|
103
|
+
7962 => :energy,
|
104
|
+
7979 => :viscosity,
|
105
|
+
7961 => :force,
|
106
|
+
7981 => :momentum,
|
107
|
+
7982 => :angular_momentum,
|
108
|
+
7997 => :density,
|
109
|
+
7998 => :area_density,
|
110
|
+
8000 => :mass,
|
111
|
+
152020 => :radiation_exposure,
|
112
|
+
159999 => :magnetism,
|
113
|
+
160000 => :current,
|
114
|
+
160020 => :charge,
|
115
|
+
312058 => :conductance,
|
116
|
+
312078 => :capacitance,
|
117
|
+
3199980 => :activity,
|
118
|
+
3199997 => :molar_concentration,
|
119
|
+
3200000 => :substance,
|
120
|
+
63999998 => :illuminance,
|
121
|
+
64000000 => :luminous_power,
|
122
|
+
1280000000 => :currency,
|
123
|
+
25600000000 => :information,
|
124
|
+
511999999980 => :angular_velocity,
|
125
|
+
512000000000 => :angle
|
126
|
+
}
|
127
|
+
@@cached_units = {}
|
128
|
+
@@base_unit_cache = {}
|
129
|
+
|
130
|
+
# setup internal arrays and hashes
|
131
|
+
# @return [true]
|
132
|
+
def self.setup
|
133
|
+
self.clear_cache
|
134
|
+
@@PREFIX_VALUES = {}
|
135
|
+
@@PREFIX_MAP = {}
|
136
|
+
@@UNIT_VALUES = {}
|
137
|
+
@@UNIT_MAP = {}
|
138
|
+
@@UNIT_REGEX = nil
|
139
|
+
@@UNIT_MATCH_REGEX = nil
|
140
|
+
@@PREFIX_REGEX = nil
|
141
|
+
|
142
|
+
@@definitions.each do |name, definition|
|
143
|
+
self.use_definition(definition)
|
144
|
+
end
|
140
145
|
|
141
|
-
|
142
|
-
|
146
|
+
RubyUnits::Unit.new(1)
|
147
|
+
return true
|
143
148
|
end
|
144
149
|
|
145
|
-
Unit.new(1)
|
146
|
-
return true
|
147
|
-
end
|
148
|
-
|
149
150
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
151
|
+
# determine if a unit is already defined
|
152
|
+
# @param [String] unit
|
153
|
+
# @return [Boolean]
|
154
|
+
def self.defined?(unit)
|
155
|
+
self.definitions.values.any? { |d| d.aliases.include?(unit) }
|
156
|
+
end
|
156
157
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
158
|
+
# return the unit definition for a unit
|
159
|
+
# @param [String] unit
|
160
|
+
# @return [RubyUnits::Unit::Definition, nil]
|
161
|
+
def self.definition(_unit)
|
162
|
+
unit = (_unit =~ /^<.+>$/) ? _unit : "<#{_unit}>"
|
163
|
+
return @@definitions[unit]
|
164
|
+
end
|
164
165
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
166
|
+
# return a list of all defined units
|
167
|
+
# @return [Array]
|
168
|
+
def self.definitions
|
169
|
+
return @@definitions
|
170
|
+
end
|
170
171
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
172
|
+
# @param [RubyUnits::Unit::Definition|String] unit_definition
|
173
|
+
# @param [Block] block
|
174
|
+
# @return [RubyUnits::Unit::Definition]
|
175
|
+
# @raise [ArgumentError] when passed a non-string if using the block form
|
176
|
+
# Unpack a unit definition and add it to the array of defined units
|
177
|
+
#
|
178
|
+
# @example Block form
|
179
|
+
# RubyUnits::Unit.define('foobar') do |foobar|
|
180
|
+
# foobar.definition = RubyUnits::Unit.new("1 baz")
|
181
|
+
# end
|
182
|
+
#
|
183
|
+
# @example RubyUnits::Unit::Definition form
|
184
|
+
# unit_definition = RubyUnits::Unit::Definition.new("foobar") {|foobar| foobar.definition = RubyUnits::Unit.new("1 baz")}
|
185
|
+
# RubyUnits::Unit.define(unit_definition)
|
186
|
+
def self.define(unit_definition, &block)
|
187
|
+
if block_given?
|
188
|
+
raise ArgumentError, "When using the block form of RubyUnits::Unit.define, pass the name of the unit" unless unit_definition.instance_of?(String)
|
189
|
+
unit_definition = RubyUnits::Unit::Definition.new(unit_definition, &block)
|
190
|
+
end
|
191
|
+
RubyUnits::Unit.definitions[unit_definition.name] = unit_definition
|
192
|
+
RubyUnits::Unit.use_definition(unit_definition)
|
193
|
+
return unit_definition
|
194
|
+
end
|
194
195
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
196
|
+
# @param [String] name Name of unit to redefine
|
197
|
+
# @param [Block] block
|
198
|
+
# @raise [ArgumentError] if a block is not given
|
199
|
+
# @yield [RubyUnits::Unit::Definition]
|
200
|
+
# @return (see RubyUnits::Unit.define)
|
201
|
+
# Get the definition for a unit and allow it to be redefined
|
202
|
+
def self.redefine!(name, &block)
|
203
|
+
raise ArgumentError, "A block is required to redefine a unit" unless block_given?
|
204
|
+
unit_definition = self.definition(name)
|
205
|
+
yield unit_definition
|
206
|
+
self.define(unit_definition)
|
207
|
+
end
|
207
208
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
209
|
+
# @param [String] name of unit to undefine
|
210
|
+
# @return (see RubyUnits::Unit.setup)
|
211
|
+
# Undefine a unit. Will not raise an exception for unknown units.
|
212
|
+
def self.undefine!(unit)
|
213
|
+
@@definitions.delete("<#{unit}>")
|
214
|
+
RubyUnits::Unit.setup
|
215
|
+
end
|
215
216
|
|
216
|
-
|
217
|
+
include Comparable
|
217
218
|
|
218
|
-
|
219
|
-
|
219
|
+
# @return [Numeric]
|
220
|
+
attr_accessor :scalar
|
220
221
|
|
221
|
-
|
222
|
-
|
222
|
+
# @return [Array]
|
223
|
+
attr_accessor :numerator
|
223
224
|
|
224
|
-
|
225
|
-
|
225
|
+
# @return [Array]
|
226
|
+
attr_accessor :denominator
|
226
227
|
|
227
|
-
|
228
|
-
|
228
|
+
# @return [Integer]
|
229
|
+
attr_accessor :signature
|
229
230
|
|
230
|
-
|
231
|
-
|
231
|
+
# @return [Numeric]
|
232
|
+
attr_accessor :base_scalar
|
232
233
|
|
233
|
-
|
234
|
-
|
234
|
+
# @return [Array]
|
235
|
+
attr_accessor :base_numerator
|
235
236
|
|
236
|
-
|
237
|
-
|
237
|
+
# @return [Array]
|
238
|
+
attr_accessor :base_denominator
|
238
239
|
|
239
|
-
|
240
|
-
|
240
|
+
# @return [String]
|
241
|
+
attr_accessor :output
|
241
242
|
|
242
|
-
|
243
|
-
|
243
|
+
# @return [String]
|
244
|
+
attr_accessor :unit_name
|
244
245
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
246
|
+
# needed to make complex units play nice -- otherwise not detected as a complex_generic
|
247
|
+
# @param [Class]
|
248
|
+
# @return [Boolean]
|
249
|
+
def kind_of?(klass)
|
250
|
+
self.scalar.kind_of?(klass)
|
251
|
+
end
|
251
252
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
253
|
+
# Used to copy one unit to another
|
254
|
+
# @param [Unit] from Unit to copy definition from
|
255
|
+
# @return [Unit]
|
256
|
+
def copy(from)
|
257
|
+
@scalar = from.scalar
|
258
|
+
@numerator = from.numerator
|
259
|
+
@denominator = from.denominator
|
260
|
+
@is_base = from.is_base?
|
261
|
+
@signature = from.signature
|
262
|
+
@base_scalar = from.base_scalar
|
263
|
+
@unit_name = from.unit_name rescue nil
|
264
|
+
return self
|
265
|
+
end
|
265
266
|
|
266
|
-
|
267
|
-
|
267
|
+
if RUBY_VERSION < "1.9"
|
268
|
+
# :nocov_19:
|
268
269
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
270
|
+
# a list of properties to emit to yaml
|
271
|
+
# @return [Array]
|
272
|
+
def to_yaml_properties
|
273
|
+
%w{@scalar @numerator @denominator @signature @base_scalar}
|
274
|
+
end
|
274
275
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
276
|
+
# basically a copy of the basic to_yaml. Needed because otherwise it ends up coercing the object to a string
|
277
|
+
# before YAML'izing it.
|
278
|
+
# @param [Hash] opts
|
279
|
+
# @return [String]
|
280
|
+
def to_yaml(opts = {})
|
281
|
+
YAML::quick_emit(object_id, opts) do |out|
|
282
|
+
out.map(taguri, to_yaml_style) do |map|
|
283
|
+
for m in to_yaml_properties do
|
284
|
+
map.add(m[1..-1], instance_variable_get(m))
|
285
|
+
end
|
284
286
|
end
|
285
287
|
end
|
286
288
|
end
|
289
|
+
# :nocov_19:
|
287
290
|
end
|
288
|
-
# :nocov_19:
|
289
|
-
end
|
290
291
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
292
|
+
# Create a new Unit object. Can be initialized using a String, a Hash, an Array, Time, DateTime
|
293
|
+
#
|
294
|
+
# @example Valid options include:
|
295
|
+
# "5.6 kg*m/s^2"
|
296
|
+
# "5.6 kg*m*s^-2"
|
297
|
+
# "5.6 kilogram*meter*second^-2"
|
298
|
+
# "2.2 kPa"
|
299
|
+
# "37 degC"
|
300
|
+
# "1" -- creates a unitless constant with value 1
|
301
|
+
# "GPa" -- creates a unit with scalar 1 with units 'GPa'
|
302
|
+
# "6'4\""" -- recognized as 6 feet + 4 inches
|
303
|
+
# "8 lbs 8 oz" -- recognized as 8 lbs + 8 ounces
|
304
|
+
# [1, 'kg']
|
305
|
+
# {:scalar => 1, :numerator=>'kg'}
|
306
|
+
#
|
307
|
+
# @param [Unit,String,Hash,Array,Date,Time,DateTime] options
|
308
|
+
# @return [Unit]
|
309
|
+
# @raise [ArgumentError] if absolute value of a temperature is less than absolute zero
|
310
|
+
# @raise [ArgumentError] if no unit is specified
|
311
|
+
# @raise [ArgumentError] if an invalid unit is specified
|
312
|
+
def initialize(*options)
|
313
|
+
@scalar = nil
|
314
|
+
@base_scalar = nil
|
315
|
+
@unit_name = nil
|
316
|
+
@signature = nil
|
317
|
+
@output = {}
|
318
|
+
raise ArgumentError, "Invalid Unit Format" if options[0].nil?
|
319
|
+
if options.size == 2
|
320
|
+
# options[0] is the scalar
|
321
|
+
# options[1] is a unit string
|
322
|
+
begin
|
323
|
+
cached = @@cached_units[options[1]] * options[0]
|
324
|
+
copy(cached)
|
325
|
+
rescue
|
326
|
+
initialize("#{options[0]} #{(options[1].units rescue options[1])}")
|
327
|
+
end
|
328
|
+
return
|
326
329
|
end
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
330
|
+
if options.size == 3
|
331
|
+
options[1] = options[1].join if options[1].kind_of?(Array)
|
332
|
+
options[2] = options[2].join if options[2].kind_of?(Array)
|
333
|
+
begin
|
334
|
+
cached = @@cached_units["#{options[1]}/#{options[2]}"] * options[0]
|
335
|
+
copy(cached)
|
336
|
+
rescue
|
337
|
+
initialize("#{options[0]} #{options[1]}/#{options[2]}")
|
338
|
+
end
|
339
|
+
return
|
337
340
|
end
|
338
|
-
return
|
339
|
-
end
|
340
341
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
end
|
371
|
-
self.update_base_scalar
|
372
|
-
raise ArgumentError, "Temperatures must not be less than absolute zero" if self.is_temperature? && self.base_scalar < 0
|
373
|
-
unary_unit = self.units || ""
|
374
|
-
if options.first.instance_of?(String)
|
375
|
-
opt_scalar, opt_units = Unit.parse_into_numbers_and_units(options[0])
|
376
|
-
unless @@cached_units.keys.include?(opt_units) || (opt_units =~ /(#{Unit.temp_regex})|(pounds|lbs[ ,]\d+ ounces|oz)|('\d+")|(ft|feet[ ,]\d+ in|inch|inches)|%|(#{TIME_REGEX})|i\s?(.+)?|±|\+\/-/)
|
377
|
-
@@cached_units[opt_units] = (self.scalar == 1 ? self : opt_units.unit) if opt_units && !opt_units.empty?
|
342
|
+
case options[0]
|
343
|
+
when Unit
|
344
|
+
copy(options[0])
|
345
|
+
return
|
346
|
+
when Hash
|
347
|
+
@scalar = options[0][:scalar] || 1
|
348
|
+
@numerator = options[0][:numerator] || UNITY_ARRAY
|
349
|
+
@denominator = options[0][:denominator] || UNITY_ARRAY
|
350
|
+
@signature = options[0][:signature]
|
351
|
+
when Array
|
352
|
+
initialize(*options[0])
|
353
|
+
return
|
354
|
+
when Numeric
|
355
|
+
@scalar = options[0]
|
356
|
+
@numerator = @denominator = UNITY_ARRAY
|
357
|
+
when Time
|
358
|
+
@scalar = options[0].to_f
|
359
|
+
@numerator = ['<second>']
|
360
|
+
@denominator = UNITY_ARRAY
|
361
|
+
when DateTime, Date
|
362
|
+
@scalar = options[0].ajd
|
363
|
+
@numerator = ['<day>']
|
364
|
+
@denominator = UNITY_ARRAY
|
365
|
+
when /^\s*$/
|
366
|
+
raise ArgumentError, "No Unit Specified"
|
367
|
+
when String
|
368
|
+
parse(options[0])
|
369
|
+
else
|
370
|
+
raise ArgumentError, "Invalid Unit Format"
|
378
371
|
end
|
372
|
+
self.update_base_scalar
|
373
|
+
raise ArgumentError, "Temperatures must not be less than absolute zero" if self.is_temperature? && self.base_scalar < 0
|
374
|
+
unary_unit = self.units || ""
|
375
|
+
if options.first.instance_of?(String)
|
376
|
+
opt_scalar, opt_units = RubyUnits::Unit.parse_into_numbers_and_units(options[0])
|
377
|
+
unless @@cached_units.keys.include?(opt_units) || (opt_units =~ /(#{RubyUnits::Unit.temp_regex})|(pounds|lbs[ ,]\d+ ounces|oz)|('\d+")|(ft|feet[ ,]\d+ in|inch|inches)|%|(#{TIME_REGEX})|i\s?(.+)?|±|\+\/-/)
|
378
|
+
@@cached_units[opt_units] = (self.scalar == 1 ? self : opt_units.unit) if opt_units && !opt_units.empty?
|
379
|
+
end
|
380
|
+
end
|
381
|
+
unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /#{RubyUnits::Unit.temp_regex}/) then
|
382
|
+
@@cached_units[unary_unit] = (self.scalar == 1 ? self : unary_unit.unit)
|
383
|
+
end
|
384
|
+
[@scalar, @numerator, @denominator, @base_scalar, @signature, @is_base].each { |x| x.freeze }
|
385
|
+
return self
|
379
386
|
end
|
380
|
-
unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /#{Unit.temp_regex}/) then
|
381
|
-
@@cached_units[unary_unit] = (self.scalar == 1 ? self : unary_unit.unit)
|
382
|
-
end
|
383
|
-
[@scalar, @numerator, @denominator, @base_scalar, @signature, @is_base].each {|x| x.freeze}
|
384
|
-
return self
|
385
|
-
end
|
386
387
|
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
388
|
+
# @todo: figure out how to handle :counting units. This method should probably return :counting instead of :unitless for 'each'
|
389
|
+
# return the kind of the unit (:mass, :length, etc...)
|
390
|
+
# @return [Symbol]
|
391
|
+
def kind
|
392
|
+
return @@KINDS[self.signature]
|
393
|
+
end
|
393
394
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
395
|
+
# @private
|
396
|
+
# @return [Hash]
|
397
|
+
def self.cached
|
398
|
+
return @@cached_units
|
399
|
+
end
|
399
400
|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
401
|
+
# @private
|
402
|
+
# @return [true]
|
403
|
+
def self.clear_cache
|
404
|
+
@@cached_units = {}
|
405
|
+
@@base_unit_cache = {}
|
406
|
+
RubyUnits::Unit.new(1)
|
407
|
+
return true
|
408
|
+
end
|
408
409
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
410
|
+
# @private
|
411
|
+
# @return [Hash]
|
412
|
+
def self.base_unit_cache
|
413
|
+
return @@base_unit_cache
|
414
|
+
end
|
414
415
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
416
|
+
# @example parse strings
|
417
|
+
# "1 minute in seconds"
|
418
|
+
# @param [String] input
|
419
|
+
# @return [Unit]
|
420
|
+
def self.parse(input)
|
421
|
+
first, second = input.scan(/(.+)\s(?:in|to|as)\s(.+)/i).first
|
422
|
+
return second.nil? ? first.unit : first.unit.convert_to(second)
|
423
|
+
end
|
423
424
|
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
end
|
428
|
-
alias :unit :to_unit
|
429
|
-
|
430
|
-
# Is this unit in base form?
|
431
|
-
# @return [Boolean]
|
432
|
-
def is_base?
|
433
|
-
return @is_base if defined? @is_base
|
434
|
-
@is_base = (@numerator + @denominator).compact.uniq.
|
435
|
-
map {|unit| Unit.definition(unit)}.
|
436
|
-
all? {|element| element.unity? || element.base? }
|
437
|
-
return @is_base
|
438
|
-
end
|
439
|
-
alias :base? :is_base?
|
440
|
-
|
441
|
-
# convert to base SI units
|
442
|
-
# results of the conversion are cached so subsequent calls to this will be fast
|
443
|
-
# @return [Unit]
|
444
|
-
# @todo this is brittle as it depends on the display_name of a unit, which can be changed
|
445
|
-
def to_base
|
446
|
-
return self if self.is_base?
|
447
|
-
if @@UNIT_MAP[self.units] =~ /\A<(?:temp|deg)[CRF]>\Z/
|
448
|
-
if RUBY_VERSION < "1.9"
|
449
|
-
# :nocov_19:
|
450
|
-
@signature = @@KINDS.index(:temperature)
|
451
|
-
# :nocov_19:
|
452
|
-
else
|
453
|
-
#:nocov:
|
454
|
-
@signature = @@KINDS.key(:temperature)
|
455
|
-
#:nocov:
|
456
|
-
end
|
457
|
-
base = case
|
458
|
-
when self.is_temperature?
|
459
|
-
self.convert_to('tempK')
|
460
|
-
when self.is_degree?
|
461
|
-
self.convert_to('degK')
|
462
|
-
end
|
463
|
-
return base
|
425
|
+
# @return [Unit]
|
426
|
+
def to_unit
|
427
|
+
self
|
464
428
|
end
|
465
429
|
|
466
|
-
|
467
|
-
return cached if cached
|
430
|
+
alias :unit :to_unit
|
468
431
|
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
num << @@UNIT_VALUES[unit][:numerator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:numerator]
|
478
|
-
den << @@UNIT_VALUES[unit][:denominator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:denominator]
|
479
|
-
end
|
432
|
+
# Is this unit in base form?
|
433
|
+
# @return [Boolean]
|
434
|
+
def is_base?
|
435
|
+
return @is_base if defined? @is_base
|
436
|
+
@is_base = (@numerator + @denominator).compact.uniq.
|
437
|
+
map { |unit| RubyUnits::Unit.definition(unit) }.
|
438
|
+
all? { |element| element.unity? || element.base? }
|
439
|
+
return @is_base
|
480
440
|
end
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
441
|
+
|
442
|
+
alias :base? :is_base?
|
443
|
+
|
444
|
+
# convert to base SI units
|
445
|
+
# results of the conversion are cached so subsequent calls to this will be fast
|
446
|
+
# @return [Unit]
|
447
|
+
# @todo this is brittle as it depends on the display_name of a unit, which can be changed
|
448
|
+
def to_base
|
449
|
+
return self if self.is_base?
|
450
|
+
if @@UNIT_MAP[self.units] =~ /\A<(?:temp|deg)[CRF]>\Z/
|
451
|
+
if RUBY_VERSION < "1.9"
|
452
|
+
# :nocov_19:
|
453
|
+
@signature = @@KINDS.index(:temperature)
|
454
|
+
# :nocov_19:
|
455
|
+
else
|
456
|
+
#:nocov:
|
457
|
+
@signature = @@KINDS.key(:temperature)
|
458
|
+
#:nocov:
|
459
|
+
end
|
460
|
+
base = case
|
461
|
+
when self.is_temperature?
|
462
|
+
self.convert_to('tempK')
|
463
|
+
when self.is_degree?
|
464
|
+
self.convert_to('degK')
|
465
|
+
end
|
466
|
+
return base
|
488
467
|
end
|
489
|
-
end
|
490
468
|
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
# Generate human readable output.
|
501
|
-
# If the name of a unit is passed, the unit will first be converted to the target unit before output.
|
502
|
-
# some named conversions are available
|
503
|
-
#
|
504
|
-
# @example
|
505
|
-
# unit.to_s(:ft) - outputs in feet and inches (e.g., 6'4")
|
506
|
-
# unit.to_s(:lbs) - outputs in pounds and ounces (e.g, 8 lbs, 8 oz)
|
507
|
-
#
|
508
|
-
# You can also pass a standard format string (i.e., '%0.2f')
|
509
|
-
# or a strftime format string.
|
510
|
-
#
|
511
|
-
# output is cached so subsequent calls for the same format will be fast
|
512
|
-
#
|
513
|
-
# @param [Symbol] target_units
|
514
|
-
# @return [String]
|
515
|
-
def to_s(target_units=nil)
|
516
|
-
out = @output[target_units]
|
517
|
-
if out
|
518
|
-
return out
|
519
|
-
else
|
520
|
-
case target_units
|
521
|
-
when :ft
|
522
|
-
inches = self.convert_to("in").scalar.to_int
|
523
|
-
out = "#{(inches / 12).truncate}\'#{(inches % 12).round}\""
|
524
|
-
when :lbs
|
525
|
-
ounces = self.convert_to("oz").scalar.to_int
|
526
|
-
out = "#{(ounces / 16).truncate} lbs, #{(ounces % 16).round} oz"
|
527
|
-
when String
|
528
|
-
out = case target_units
|
529
|
-
when /(%[\-+\.\w#]+)\s*(.+)*/ #format string like '%0.2f in'
|
530
|
-
begin
|
531
|
-
if $2 #unit specified, need to convert
|
532
|
-
self.convert_to($2).to_s($1)
|
533
|
-
else
|
534
|
-
"#{$1 % @scalar} #{$2 || self.units}".strip
|
535
|
-
end
|
536
|
-
rescue # parse it like a strftime format string
|
537
|
-
(DateTime.new(0) + self).strftime(target_units)
|
538
|
-
end
|
539
|
-
when /(\S+)/ #unit only 'mm' or '1/mm'
|
540
|
-
self.convert_to($1).to_s
|
469
|
+
cached = ((@@base_unit_cache[self.units] * self.scalar) rescue nil)
|
470
|
+
return cached if cached
|
471
|
+
|
472
|
+
num = []
|
473
|
+
den = []
|
474
|
+
q = 1
|
475
|
+
for unit in @numerator.compact do
|
476
|
+
if @@PREFIX_VALUES[unit]
|
477
|
+
q *= @@PREFIX_VALUES[unit]
|
541
478
|
else
|
542
|
-
|
479
|
+
q *= @@UNIT_VALUES[unit][:scalar] if @@UNIT_VALUES[unit]
|
480
|
+
num << @@UNIT_VALUES[unit][:numerator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:numerator]
|
481
|
+
den << @@UNIT_VALUES[unit][:denominator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:denominator]
|
543
482
|
end
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
483
|
+
end
|
484
|
+
for unit in @denominator.compact do
|
485
|
+
if @@PREFIX_VALUES[unit]
|
486
|
+
q /= @@PREFIX_VALUES[unit]
|
548
487
|
else
|
549
|
-
|
550
|
-
|
488
|
+
q /= @@UNIT_VALUES[unit][:scalar] if @@UNIT_VALUES[unit]
|
489
|
+
den << @@UNIT_VALUES[unit][:numerator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:numerator]
|
490
|
+
num << @@UNIT_VALUES[unit][:denominator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:denominator]
|
491
|
+
end
|
551
492
|
end
|
552
|
-
|
553
|
-
|
493
|
+
|
494
|
+
num = num.flatten.compact
|
495
|
+
den = den.flatten.compact
|
496
|
+
num = UNITY_ARRAY if num.empty?
|
497
|
+
base = RubyUnits::Unit.new(RubyUnits::Unit.eliminate_terms(q, num, den))
|
498
|
+
@@base_unit_cache[self.units]=base
|
499
|
+
return base * @scalar
|
554
500
|
end
|
555
|
-
end
|
556
501
|
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
502
|
+
alias :base :to_base
|
503
|
+
|
504
|
+
# Generate human readable output.
|
505
|
+
# If the name of a unit is passed, the unit will first be converted to the target unit before output.
|
506
|
+
# some named conversions are available
|
507
|
+
#
|
508
|
+
# @example
|
509
|
+
# unit.to_s(:ft) - outputs in feet and inches (e.g., 6'4")
|
510
|
+
# unit.to_s(:lbs) - outputs in pounds and ounces (e.g, 8 lbs, 8 oz)
|
511
|
+
#
|
512
|
+
# You can also pass a standard format string (i.e., '%0.2f')
|
513
|
+
# or a strftime format string.
|
514
|
+
#
|
515
|
+
# output is cached so subsequent calls for the same format will be fast
|
516
|
+
#
|
517
|
+
# @param [Symbol] target_units
|
518
|
+
# @return [String]
|
519
|
+
def to_s(target_units=nil)
|
520
|
+
out = @output[target_units]
|
521
|
+
if out
|
522
|
+
return out
|
523
|
+
else
|
524
|
+
case target_units
|
525
|
+
when :ft
|
526
|
+
inches = self.convert_to("in").scalar.to_int
|
527
|
+
out = "#{(inches / 12).truncate}\'#{(inches % 12).round}\""
|
528
|
+
when :lbs
|
529
|
+
ounces = self.convert_to("oz").scalar.to_int
|
530
|
+
out = "#{(ounces / 16).truncate} lbs, #{(ounces % 16).round} oz"
|
531
|
+
when String
|
532
|
+
out = case target_units
|
533
|
+
when /(%[\-+\.\w#]+)\s*(.+)*/ #format string like '%0.2f in'
|
534
|
+
begin
|
535
|
+
if $2 #unit specified, need to convert
|
536
|
+
self.convert_to($2).to_s($1)
|
537
|
+
else
|
538
|
+
"#{$1 % @scalar} #{$2 || self.units}".strip
|
539
|
+
end
|
540
|
+
rescue # parse it like a strftime format string
|
541
|
+
(DateTime.new(0) + self).strftime(target_units)
|
542
|
+
end
|
543
|
+
when /(\S+)/ #unit only 'mm' or '1/mm'
|
544
|
+
self.convert_to($1).to_s
|
545
|
+
else
|
546
|
+
raise "unhandled case"
|
547
|
+
end
|
548
|
+
else
|
549
|
+
out = case @scalar
|
550
|
+
when Rational
|
551
|
+
"#{@scalar} #{self.units}"
|
552
|
+
else
|
553
|
+
"#{'%g' % @scalar} #{self.units}"
|
554
|
+
end.strip
|
555
|
+
end
|
556
|
+
@output[target_units] = out
|
557
|
+
return out
|
558
|
+
end
|
559
|
+
end
|
564
560
|
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
561
|
+
# Normally pretty prints the unit, but if you really want to see the guts of it, pass ':dump'
|
562
|
+
# @deprecated
|
563
|
+
# @return [String]
|
564
|
+
def inspect(option=nil)
|
565
|
+
return super() if option == :dump
|
566
|
+
return self.to_s
|
567
|
+
end
|
572
568
|
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
# returns the 'degree' unit associated with a temperature unit
|
581
|
-
# @example '100 tempC'.unit.temperature_scale #=> 'degC'
|
582
|
-
# @return [String] possible values: degC, degF, degR, or degK
|
583
|
-
def temperature_scale
|
584
|
-
return nil unless self.is_temperature?
|
585
|
-
return "deg#{@@UNIT_MAP[self.units][/temp([CFRK])/,1]}"
|
586
|
-
end
|
569
|
+
# true if unit is a 'temperature', false if a 'degree' or anything else
|
570
|
+
# @return [Boolean]
|
571
|
+
# @todo use unit definition to determine if it's a temperature instead of a regex
|
572
|
+
def is_temperature?
|
573
|
+
return self.is_degree? && (!(@@UNIT_MAP[self.units] =~ /temp[CFRK]/).nil?)
|
574
|
+
end
|
587
575
|
|
588
|
-
|
589
|
-
# false, even if the units are "unitless" like 'radians, each, etc'
|
590
|
-
# @return [Boolean]
|
591
|
-
def unitless?
|
592
|
-
return(@numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY)
|
593
|
-
end
|
576
|
+
alias :temperature? :is_temperature?
|
594
577
|
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
# @raise [NoMethodError] when other does not define <=>
|
600
|
-
# @raise [ArgumentError] when units are not compatible
|
601
|
-
def <=>(other)
|
602
|
-
case
|
603
|
-
when !self.base_scalar.respond_to?(:<=>)
|
604
|
-
raise NoMethodError, "undefined method `<=>' for #{self.base_scalar.inspect}"
|
605
|
-
when other.nil?
|
606
|
-
return self.base_scalar <=> nil
|
607
|
-
when !self.is_temperature? && other.zero?
|
608
|
-
return self.base_scalar <=> 0
|
609
|
-
when other.instance_of?(Unit)
|
610
|
-
raise ArgumentError, "Incompatible Units (#{self.units} !~ #{other.units})" unless self =~ other
|
611
|
-
return self.base_scalar <=> other.base_scalar
|
612
|
-
else
|
613
|
-
x,y = coerce(other)
|
614
|
-
return x <=> y
|
578
|
+
# true if a degree unit or equivalent.
|
579
|
+
# @return [Boolean]
|
580
|
+
def is_degree?
|
581
|
+
return self.kind == :temperature
|
615
582
|
end
|
616
|
-
end
|
617
583
|
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
def ==(other)
|
627
|
-
case
|
628
|
-
when other.respond_to?(:zero?) && other.zero?
|
629
|
-
return self.zero?
|
630
|
-
when other.instance_of?(Unit)
|
631
|
-
return false unless self =~ other
|
632
|
-
return self.base_scalar == other.base_scalar
|
633
|
-
else
|
634
|
-
begin
|
635
|
-
x,y = coerce(other)
|
636
|
-
return x == y
|
637
|
-
rescue ArgumentError # return false when object cannot be coerced
|
638
|
-
return false
|
639
|
-
end
|
584
|
+
alias :degree? :is_degree?
|
585
|
+
|
586
|
+
# returns the 'degree' unit associated with a temperature unit
|
587
|
+
# @example '100 tempC'.unit.temperature_scale #=> 'degC'
|
588
|
+
# @return [String] possible values: degC, degF, degR, or degK
|
589
|
+
def temperature_scale
|
590
|
+
return nil unless self.is_temperature?
|
591
|
+
return "deg#{@@UNIT_MAP[self.units][/temp([CFRK])/, 1]}"
|
640
592
|
end
|
641
|
-
end
|
642
593
|
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
# @note if you want to do a regexp comparison of the unit string do this ...
|
649
|
-
# unit.units =~ /regexp/
|
650
|
-
# @param [Object] other
|
651
|
-
# @return [Boolean]
|
652
|
-
def =~(other)
|
653
|
-
case other
|
654
|
-
when Unit
|
655
|
-
self.signature == other.signature
|
656
|
-
else
|
657
|
-
begin
|
658
|
-
x,y = coerce(other)
|
659
|
-
return x =~ y
|
660
|
-
rescue ArgumentError
|
661
|
-
return false
|
662
|
-
end
|
594
|
+
# returns true if no associated units
|
595
|
+
# false, even if the units are "unitless" like 'radians, each, etc'
|
596
|
+
# @return [Boolean]
|
597
|
+
def unitless?
|
598
|
+
return(@numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY)
|
663
599
|
end
|
664
|
-
end
|
665
600
|
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
601
|
+
# Compare two Unit objects. Throws an exception if they are not of compatible types.
|
602
|
+
# Comparisons are done based on the value of the unit in base SI units.
|
603
|
+
# @param [Object] other
|
604
|
+
# @return [-1|0|1|nil]
|
605
|
+
# @raise [NoMethodError] when other does not define <=>
|
606
|
+
# @raise [ArgumentError] when units are not compatible
|
607
|
+
def <=>(other)
|
608
|
+
case
|
609
|
+
when !self.base_scalar.respond_to?(:<=>)
|
610
|
+
raise NoMethodError, "undefined method `<=>' for #{self.base_scalar.inspect}"
|
611
|
+
when other.nil?
|
612
|
+
return self.base_scalar <=> nil
|
613
|
+
when !self.is_temperature? && other.zero?
|
614
|
+
return self.base_scalar <=> 0
|
615
|
+
when other.instance_of?(Unit)
|
616
|
+
raise ArgumentError, "Incompatible Units (#{self.units} !~ #{other.units})" unless self =~ other
|
617
|
+
return self.base_scalar <=> other.base_scalar
|
618
|
+
else
|
619
|
+
x, y = coerce(other)
|
620
|
+
return x <=> y
|
685
621
|
end
|
686
622
|
end
|
687
|
-
end
|
688
623
|
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
# @raise [ArgumentError] when units are not compatible
|
699
|
-
# @raise [ArgumentError] when adding a fixed time or date to a time span
|
700
|
-
def +(other)
|
701
|
-
case other
|
702
|
-
when Unit
|
624
|
+
# Compare Units for equality
|
625
|
+
# this is necessary mostly for Complex units. Complex units do not have a <=> operator
|
626
|
+
# so we define this one here so that we can properly check complex units for equality.
|
627
|
+
# Units of incompatible types are not equal, except when they are both zero and neither is a temperature
|
628
|
+
# Equality checks can be tricky since round off errors may make essentially equivalent units
|
629
|
+
# appear to be different.
|
630
|
+
# @param [Object] other
|
631
|
+
# @return [Boolean]
|
632
|
+
def ==(other)
|
703
633
|
case
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
634
|
+
when other.respond_to?(:zero?) && other.zero?
|
635
|
+
return self.zero?
|
636
|
+
when other.instance_of?(Unit)
|
637
|
+
return false unless self =~ other
|
638
|
+
return self.base_scalar == other.base_scalar
|
639
|
+
else
|
640
|
+
begin
|
641
|
+
x, y = coerce(other)
|
642
|
+
return x == y
|
643
|
+
rescue ArgumentError # return false when object cannot be coerced
|
644
|
+
return false
|
713
645
|
end
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
# check to see if units are compatible, but not the scalar part
|
650
|
+
# this check is done by comparing signatures for performance reasons
|
651
|
+
# if passed a string, it will create a unit object with the string and then do the comparison
|
652
|
+
# @example this permits a syntax like:
|
653
|
+
# unit =~ "mm"
|
654
|
+
# @note if you want to do a regexp comparison of the unit string do this ...
|
655
|
+
# unit.units =~ /regexp/
|
656
|
+
# @param [Object] other
|
657
|
+
# @return [Boolean]
|
658
|
+
def =~(other)
|
659
|
+
case other
|
660
|
+
when Unit
|
661
|
+
self.signature == other.signature
|
714
662
|
else
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
663
|
+
begin
|
664
|
+
x, y = coerce(other)
|
665
|
+
return x =~ y
|
666
|
+
rescue ArgumentError
|
667
|
+
return false
|
668
|
+
end
|
720
669
|
end
|
721
|
-
when Date, Time
|
722
|
-
raise ArgumentError, "Date and Time objects represent fixed points in time and cannot be added to a Unit"
|
723
|
-
else
|
724
|
-
x,y = coerce(other)
|
725
|
-
y + x
|
726
670
|
end
|
727
|
-
end
|
728
671
|
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
672
|
+
alias :compatible? :=~
|
673
|
+
alias :compatible_with? :=~
|
674
|
+
|
675
|
+
# Compare two units. Returns true if quantities and units match
|
676
|
+
# @example
|
677
|
+
# RubyUnits::Unit.new("100 cm") === RubyUnits::Unit.new("100 cm") # => true
|
678
|
+
# RubyUnits::Unit.new("100 cm") === RubyUnits::Unit.new("1 m") # => false
|
679
|
+
# @param [Object] other
|
680
|
+
# @return [Boolean]
|
681
|
+
def ===(other)
|
682
|
+
case other
|
683
|
+
when Unit
|
684
|
+
(self.scalar == other.scalar) && (self.units == other.units)
|
685
|
+
else
|
686
|
+
begin
|
687
|
+
x, y = coerce(other)
|
688
|
+
return x === y
|
689
|
+
rescue ArgumentError
|
690
|
+
return false
|
744
691
|
end
|
745
|
-
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
alias :same? :===
|
696
|
+
alias :same_as? :===
|
697
|
+
|
698
|
+
# Add two units together. Result is same units as receiver and scalar and base_scalar are updated appropriately
|
699
|
+
# throws an exception if the units are not compatible.
|
700
|
+
# It is possible to add Time objects to units of time
|
701
|
+
# @param [Object] other
|
702
|
+
# @return [Unit]
|
703
|
+
# @raise [ArgumentError] when two temperatures are added
|
704
|
+
# @raise [ArgumentError] when units are not compatible
|
705
|
+
# @raise [ArgumentError] when adding a fixed time or date to a time span
|
706
|
+
def +(other)
|
707
|
+
case other
|
708
|
+
when Unit
|
746
709
|
case
|
747
|
-
when
|
748
|
-
|
749
|
-
when self
|
750
|
-
|
751
|
-
|
752
|
-
|
710
|
+
when self.zero?
|
711
|
+
other.dup
|
712
|
+
when self =~ other
|
713
|
+
raise ArgumentError, "Cannot add two temperatures" if ([self, other].all? { |x| x.is_temperature? })
|
714
|
+
if [self, other].any? { |x| x.is_temperature? }
|
715
|
+
if self.is_temperature?
|
716
|
+
RubyUnits::Unit.new(:scalar => (self.scalar + other.convert_to(self.temperature_scale).scalar), :numerator => @numerator, :denominator => @denominator, :signature => @signature)
|
717
|
+
else
|
718
|
+
RubyUnits::Unit.new(:scalar => (other.scalar + self.convert_to(other.temperature_scale).scalar), :numerator => other.numerator, :denominator => other.denominator, :signature => other.signature)
|
719
|
+
end
|
720
|
+
else
|
721
|
+
@q ||= ((@@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar) rescue (self.units.unit.to_base.scalar))
|
722
|
+
RubyUnits::Unit.new(:scalar => (self.base_scalar + other.base_scalar)*@q, :numerator => @numerator, :denominator => @denominator, :signature => @signature)
|
723
|
+
end
|
753
724
|
else
|
754
|
-
|
755
|
-
Unit.new(:scalar=>(self.base_scalar - other.base_scalar)*@q, :numerator=>@numerator, :denominator=>@denominator, :signature=>@signature)
|
725
|
+
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
|
756
726
|
end
|
727
|
+
when Date, Time
|
728
|
+
raise ArgumentError, "Date and Time objects represent fixed points in time and cannot be added to a Unit"
|
757
729
|
else
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
raise ArgumentError, "Date and Time objects represent fixed points in time and cannot be subtracted from to a Unit, which can only represent time spans"
|
762
|
-
else
|
763
|
-
x,y = coerce(other)
|
764
|
-
return y-x
|
730
|
+
x, y = coerce(other)
|
731
|
+
y + x
|
732
|
+
end
|
765
733
|
end
|
766
|
-
end
|
767
734
|
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
735
|
+
# Subtract two units. Result is same units as receiver and scalar and base_scalar are updated appropriately
|
736
|
+
# @param [Numeric] other
|
737
|
+
# @return [Unit]
|
738
|
+
# @raise [ArgumentError] when subtracting a temperature from a degree
|
739
|
+
# @raise [ArgumentError] when units are not compatible
|
740
|
+
# @raise [ArgumentError] when subtracting a fixed time from a time span
|
741
|
+
def -(other)
|
742
|
+
case other
|
743
|
+
when Unit
|
744
|
+
case
|
745
|
+
when self.zero?
|
746
|
+
if other.zero?
|
747
|
+
other.dup * -1 # preserve Units class
|
748
|
+
else
|
749
|
+
-other.dup
|
750
|
+
end
|
751
|
+
when self =~ other
|
752
|
+
case
|
753
|
+
when [self, other].all? { |x| x.is_temperature? }
|
754
|
+
RubyUnits::Unit.new(:scalar => (self.base_scalar - other.base_scalar), :numerator => KELVIN, :denominator => UNITY_ARRAY, :signature => @signature).convert_to(self.temperature_scale)
|
755
|
+
when self.is_temperature?
|
756
|
+
RubyUnits::Unit.new(:scalar => (self.base_scalar - other.base_scalar), :numerator => ['<tempK>'], :denominator => UNITY_ARRAY, :signature => @signature).convert_to(self)
|
757
|
+
when other.is_temperature?
|
758
|
+
raise ArgumentError, "Cannot subtract a temperature from a differential degree unit"
|
759
|
+
else
|
760
|
+
@q ||= ((@@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar) rescue (self.units.unit.scalar/self.units.unit.to_base.scalar))
|
761
|
+
RubyUnits::Unit.new(:scalar => (self.base_scalar - other.base_scalar)*@q, :numerator => @numerator, :denominator => @denominator, :signature => @signature)
|
762
|
+
end
|
763
|
+
else
|
764
|
+
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
|
765
|
+
end
|
766
|
+
when Time
|
767
|
+
raise ArgumentError, "Date and Time objects represent fixed points in time and cannot be subtracted from to a Unit, which can only represent time spans"
|
768
|
+
else
|
769
|
+
x, y = coerce(other)
|
770
|
+
return y-x
|
771
|
+
end
|
784
772
|
end
|
785
|
-
end
|
786
773
|
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
else
|
805
|
-
x,y = coerce(other)
|
806
|
-
return y / x
|
774
|
+
# Multiply two units.
|
775
|
+
# @param [Numeric] other
|
776
|
+
# @return [Unit]
|
777
|
+
# @raise [ArgumentError] when attempting to multiply two temperatures
|
778
|
+
def *(other)
|
779
|
+
case other
|
780
|
+
when Unit
|
781
|
+
raise ArgumentError, "Cannot multiply by temperatures" if [other, self].any? { |x| x.is_temperature? }
|
782
|
+
opts = RubyUnits::Unit.eliminate_terms(@scalar*other.scalar, @numerator + other.numerator, @denominator + other.denominator)
|
783
|
+
opts.merge!(:signature => @signature + other.signature)
|
784
|
+
return RubyUnits::Unit.new(opts)
|
785
|
+
when Numeric
|
786
|
+
return RubyUnits::Unit.new(:scalar => @scalar*other, :numerator => @numerator, :denominator => @denominator, :signature => @signature)
|
787
|
+
else
|
788
|
+
x, y = coerce(other)
|
789
|
+
return x * y
|
790
|
+
end
|
807
791
|
end
|
808
|
-
end
|
809
792
|
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
793
|
+
# Divide two units.
|
794
|
+
# Throws an exception if divisor is 0
|
795
|
+
# @param [Numeric] other
|
796
|
+
# @return [Unit]
|
797
|
+
# @raise [ZeroDivisionError] if divisor is zero
|
798
|
+
# @raise [ArgumentError] if attempting to divide a temperature by another temperature
|
799
|
+
def /(other)
|
800
|
+
case other
|
801
|
+
when Unit
|
802
|
+
raise ZeroDivisionError if other.zero?
|
803
|
+
raise ArgumentError, "Cannot divide with temperatures" if [other, self].any? { |x| x.is_temperature? }
|
804
|
+
opts = RubyUnits::Unit.eliminate_terms(@scalar/other.scalar, @numerator + other.denominator, @denominator + other.numerator)
|
805
|
+
opts.merge!(:signature => @signature - other.signature)
|
806
|
+
return RubyUnits::Unit.new(opts)
|
807
|
+
when Numeric
|
808
|
+
raise ZeroDivisionError if other.zero?
|
809
|
+
return RubyUnits::Unit.new(:scalar => @scalar/other, :numerator => @numerator, :denominator => @denominator, :signature => @signature)
|
810
|
+
else
|
811
|
+
x, y = coerce(other)
|
812
|
+
return y / x
|
813
|
+
end
|
821
814
|
end
|
822
|
-
end
|
823
|
-
|
824
|
-
# perform a modulo on a unit, will raise an exception if the units are not compatible
|
825
|
-
# @param [Object] other
|
826
|
-
# @return [Integer]
|
827
|
-
def %(other)
|
828
|
-
return self.divmod(other).last
|
829
|
-
end
|
830
815
|
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
# @raise [ArgumentError] when attempting to raise to a complex number
|
844
|
-
# @raise [ArgumentError] when an invalid exponent is passed
|
845
|
-
def **(other)
|
846
|
-
raise ArgumentError, "Cannot raise a temperature to a power" if self.is_temperature?
|
847
|
-
if other.kind_of?(Numeric)
|
848
|
-
return self.inverse if other == -1
|
849
|
-
return self if other == 1
|
850
|
-
return 1 if other.zero?
|
851
|
-
end
|
852
|
-
case other
|
853
|
-
when Rational
|
854
|
-
return self.power(other.numerator).root(other.denominator)
|
855
|
-
when Integer
|
856
|
-
return self.power(other)
|
857
|
-
when Float
|
858
|
-
return self**(other.to_i) if other == other.to_i
|
859
|
-
valid = (1..9).map {|x| 1/x}
|
860
|
-
raise ArgumentError, "Not a n-th root (1..9), use 1/n" unless valid.include? other.abs
|
861
|
-
return self.root((1/other).to_int)
|
862
|
-
when Complex
|
863
|
-
raise ArgumentError, "exponentiation of complex numbers is not yet supported."
|
864
|
-
else
|
865
|
-
raise ArgumentError, "Invalid Exponent"
|
816
|
+
# divide two units and return quotient and remainder
|
817
|
+
# when both units are in the same units we just use divmod on the raw scalars
|
818
|
+
# otherwise we use the scalar of the base unit which will be a float
|
819
|
+
# @param [Object] other
|
820
|
+
# @return [Array]
|
821
|
+
def divmod(other)
|
822
|
+
raise ArgumentError, "Incompatible Units" unless self =~ other
|
823
|
+
if self.units == other.units
|
824
|
+
return self.scalar.divmod(other.scalar)
|
825
|
+
else
|
826
|
+
return self.to_base.scalar.divmod(other.to_base.scalar)
|
827
|
+
end
|
866
828
|
end
|
867
|
-
end
|
868
829
|
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
def power(n)
|
875
|
-
raise ArgumentError, "Cannot raise a temperature to a power" if self.is_temperature?
|
876
|
-
raise ArgumentError, "Exponent must an Integer" unless n.kind_of?(Integer)
|
877
|
-
return self.inverse if n == -1
|
878
|
-
return 1 if n.zero?
|
879
|
-
return self if n == 1
|
880
|
-
if n > 0 then
|
881
|
-
return (1..(n-1).to_i).inject(self) {|product, x| product * self}
|
882
|
-
else
|
883
|
-
return (1..-(n-1).to_i).inject(self) {|product, x| product / self}
|
830
|
+
# perform a modulo on a unit, will raise an exception if the units are not compatible
|
831
|
+
# @param [Object] other
|
832
|
+
# @return [Integer]
|
833
|
+
def %(other)
|
834
|
+
return self.divmod(other).last
|
884
835
|
end
|
885
|
-
end
|
886
836
|
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
raise ArgumentError
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
837
|
+
# Exponentiate. Only takes integer powers.
|
838
|
+
# Note that anything raised to the power of 0 results in a Unit object with a scalar of 1, and no units.
|
839
|
+
# Throws an exception if exponent is not an integer.
|
840
|
+
# Ideally this routine should accept a float for the exponent
|
841
|
+
# It should then convert the float to a rational and raise the unit by the numerator and root it by the denominator
|
842
|
+
# but, sadly, floats can't be converted to rationals.
|
843
|
+
#
|
844
|
+
# For now, if a rational is passed in, it will be used, otherwise we are stuck with integers and certain floats < 1
|
845
|
+
# @param [Numeric] other
|
846
|
+
# @return [Unit]
|
847
|
+
# @raise [ArgumentError] when raising a temperature to a power
|
848
|
+
# @raise [ArgumentError] when n not in the set integers from (1..9)
|
849
|
+
# @raise [ArgumentError] when attempting to raise to a complex number
|
850
|
+
# @raise [ArgumentError] when an invalid exponent is passed
|
851
|
+
def **(other)
|
852
|
+
raise ArgumentError, "Cannot raise a temperature to a power" if self.is_temperature?
|
853
|
+
if other.kind_of?(Numeric)
|
854
|
+
return self.inverse if other == -1
|
855
|
+
return self if other == 1
|
856
|
+
return 1 if other.zero?
|
857
|
+
end
|
858
|
+
case other
|
859
|
+
when Rational
|
860
|
+
return self.power(other.numerator).root(other.denominator)
|
861
|
+
when Integer
|
862
|
+
return self.power(other)
|
863
|
+
when Float
|
864
|
+
return self**(other.to_i) if other == other.to_i
|
865
|
+
valid = (1..9).map { |x| 1/x }
|
866
|
+
raise ArgumentError, "Not a n-th root (1..9), use 1/n" unless valid.include? other.abs
|
867
|
+
return self.root((1/other).to_int)
|
868
|
+
when Complex
|
869
|
+
raise ArgumentError, "exponentiation of complex numbers is not yet supported."
|
870
|
+
else
|
871
|
+
raise ArgumentError, "Invalid Exponent"
|
872
|
+
end
|
873
|
+
end
|
921
874
|
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
875
|
+
# returns the unit raised to the n-th power
|
876
|
+
# @param [Integer] n
|
877
|
+
# @return [Unit]
|
878
|
+
# @raise [ArgumentError] when attempting to raise a temperature to a power
|
879
|
+
# @raise [ArgumentError] when n is not an integer
|
880
|
+
def power(n)
|
881
|
+
raise ArgumentError, "Cannot raise a temperature to a power" if self.is_temperature?
|
882
|
+
raise ArgumentError, "Exponent must an Integer" unless n.kind_of?(Integer)
|
883
|
+
return self.inverse if n == -1
|
884
|
+
return 1 if n.zero?
|
885
|
+
return self if n == 1
|
886
|
+
if n > 0 then
|
887
|
+
return (1..(n-1).to_i).inject(self) { |product, x| product * self }
|
888
|
+
else
|
889
|
+
return (1..-(n-1).to_i).inject(self) { |product, x| product / self }
|
890
|
+
end
|
891
|
+
end
|
927
892
|
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
raise ArgumentError, "Receiver is not a temperature unit" unless self.degree?
|
953
|
-
start_unit = self.units
|
954
|
-
target_unit = other.units rescue other
|
955
|
-
unless @base_scalar
|
956
|
-
@base_scalar = case @@UNIT_MAP[start_unit]
|
957
|
-
when '<tempC>'
|
958
|
-
@scalar + 273.15
|
959
|
-
when '<tempK>'
|
960
|
-
@scalar
|
961
|
-
when '<tempF>'
|
962
|
-
(@scalar+459.67)*Rational(5,9)
|
963
|
-
when '<tempR>'
|
964
|
-
@scalar*Rational(5,9)
|
965
|
-
end
|
893
|
+
# Calculates the n-th root of a unit
|
894
|
+
# if n < 0, returns 1/unit^(1/n)
|
895
|
+
# @param [Integer] n
|
896
|
+
# @return [Unit]
|
897
|
+
# @raise [ArgumentError] when attemptint to take the root of a temperature
|
898
|
+
# @raise [ArgumentError] when n is not an integer
|
899
|
+
# @raise [ArgumentError] when n is 0
|
900
|
+
def root(n)
|
901
|
+
raise ArgumentError, "Cannot take the root of a temperature" if self.is_temperature?
|
902
|
+
raise ArgumentError, "Exponent must an Integer" unless n.kind_of?(Integer)
|
903
|
+
raise ArgumentError, "0th root undefined" if n.zero?
|
904
|
+
return self if n == 1
|
905
|
+
return self.root(n.abs).inverse if n < 0
|
906
|
+
|
907
|
+
vec = self.unit_signature_vector
|
908
|
+
vec =vec.map { |x| x % n }
|
909
|
+
raise ArgumentError, "Illegal root" unless vec.max == 0
|
910
|
+
num = @numerator.dup
|
911
|
+
den = @denominator.dup
|
912
|
+
|
913
|
+
for item in @numerator.uniq do
|
914
|
+
x = num.find_all { |i| i==item }.size
|
915
|
+
r = ((x/n)*(n-1)).to_int
|
916
|
+
r.times { |y| num.delete_at(num.index(item)) }
|
966
917
|
end
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
when '<tempF>'
|
973
|
-
@base_scalar * Rational(9,5) - 459.67
|
974
|
-
when '<tempR>'
|
975
|
-
@base_scalar * Rational(9,5)
|
918
|
+
|
919
|
+
for item in @denominator.uniq do
|
920
|
+
x = den.find_all { |i| i==item }.size
|
921
|
+
r = ((x/n)*(n-1)).to_int
|
922
|
+
r.times { |y| den.delete_at(den.index(item)) }
|
976
923
|
end
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
924
|
+
q = @scalar < 0 ? (-1)**Rational(1, n) * (@scalar.abs)**Rational(1, n) : @scalar**Rational(1, n)
|
925
|
+
return RubyUnits::Unit.new(:scalar => q, :numerator => num, :denominator => den)
|
926
|
+
end
|
927
|
+
|
928
|
+
# returns inverse of Unit (1/unit)
|
929
|
+
# @return [Unit]
|
930
|
+
def inverse
|
931
|
+
return RubyUnits::Unit.new("1") / self
|
932
|
+
end
|
933
|
+
|
934
|
+
# convert to a specified unit string or to the same units as another Unit
|
935
|
+
#
|
936
|
+
# unit.convert_to "kg" will covert to kilograms
|
937
|
+
# unit1.convert_to unit2 converts to same units as unit2 object
|
938
|
+
#
|
939
|
+
# To convert a Unit object to match another Unit object, use:
|
940
|
+
# unit1 >>= unit2
|
941
|
+
#
|
942
|
+
# Special handling for temperature conversions is supported. If the Unit object is converted
|
943
|
+
# from one temperature unit to another, the proper temperature offsets will be used.
|
944
|
+
# Supports Kelvin, Celsius, Fahrenheit, and Rankine scales.
|
945
|
+
#
|
946
|
+
# @note If temperature is part of a compound unit, the temperature will be treated as a differential
|
947
|
+
# and the units will be scaled appropriately.
|
948
|
+
# @param [Object] other
|
949
|
+
# @return [Unit]
|
950
|
+
# @raise [ArgumentError] when attempting to convert a degree to a temperature
|
951
|
+
# @raise [ArgumentError] when target unit is unknown
|
952
|
+
# @raise [ArgumentError] when target unit is incompatible
|
953
|
+
def convert_to(other)
|
954
|
+
return self if other.nil?
|
955
|
+
return self if TrueClass === other
|
956
|
+
return self if FalseClass === other
|
957
|
+
if (Unit === other && other.is_temperature?) || (String === other && other =~ /temp[CFRK]/)
|
958
|
+
raise ArgumentError, "Receiver is not a temperature unit" unless self.degree?
|
959
|
+
start_unit = self.units
|
960
|
+
target_unit = other.units rescue other
|
961
|
+
unless @base_scalar
|
962
|
+
@base_scalar = case @@UNIT_MAP[start_unit]
|
963
|
+
when '<tempC>'
|
964
|
+
@scalar + 273.15
|
965
|
+
when '<tempK>'
|
966
|
+
@scalar
|
967
|
+
when '<tempF>'
|
968
|
+
(@scalar+459.67)*Rational(5, 9)
|
969
|
+
when '<tempR>'
|
970
|
+
@scalar*Rational(5, 9)
|
971
|
+
end
|
972
|
+
end
|
973
|
+
q= case @@UNIT_MAP[target_unit]
|
974
|
+
when '<tempC>'
|
975
|
+
@base_scalar - 273.15
|
976
|
+
when '<tempK>'
|
977
|
+
@base_scalar
|
978
|
+
when '<tempF>'
|
979
|
+
@base_scalar * Rational(9, 5) - 459.67
|
980
|
+
when '<tempR>'
|
981
|
+
@base_scalar * Rational(9, 5)
|
982
|
+
end
|
983
|
+
return RubyUnits::Unit.new("#{q} #{target_unit}")
|
985
984
|
else
|
986
|
-
|
985
|
+
case other
|
986
|
+
when Unit
|
987
|
+
return self if other.units == self.units
|
988
|
+
target = other
|
989
|
+
when String
|
990
|
+
target = RubyUnits::Unit.new(other)
|
991
|
+
else
|
992
|
+
raise ArgumentError, "Unknown target units"
|
993
|
+
end
|
994
|
+
raise ArgumentError, "Incompatible Units" unless self =~ target
|
995
|
+
_numerator1 = @numerator.map { |x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x }.map { |i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[i][:scalar] }.compact
|
996
|
+
_denominator1 = @denominator.map { |x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x }.map { |i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[i][:scalar] }.compact
|
997
|
+
_numerator2 = target.numerator.map { |x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x }.map { |x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[x][:scalar] }.compact
|
998
|
+
_denominator2 = target.denominator.map { |x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x }.map { |x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[x][:scalar] }.compact
|
999
|
+
|
1000
|
+
q = @scalar * ((_numerator1 + _denominator2).inject(1) { |product, n| product*n }) /
|
1001
|
+
((_numerator2 + _denominator1).inject(1) { |product, n| product*n })
|
1002
|
+
return RubyUnits::Unit.new(:scalar => q, :numerator => target.numerator, :denominator => target.denominator, :signature => target.signature)
|
987
1003
|
end
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
_denominator2 = target.denominator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[x][:scalar] }.compact
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
alias :>> :convert_to
|
1007
|
+
alias :to :convert_to
|
993
1008
|
|
994
|
-
|
995
|
-
|
996
|
-
|
1009
|
+
# converts the unit back to a float if it is unitless. Otherwise raises an exception
|
1010
|
+
# @return [Float]
|
1011
|
+
# @raise [RuntimeError] when not unitless
|
1012
|
+
def to_f
|
1013
|
+
return @scalar.to_f if self.unitless?
|
1014
|
+
raise RuntimeError, "Cannot convert '#{self.to_s}' to Float unless unitless. Use Unit#scalar"
|
997
1015
|
end
|
998
|
-
end
|
999
|
-
alias :>> :convert_to
|
1000
|
-
alias :to :convert_to
|
1001
|
-
|
1002
|
-
# converts the unit back to a float if it is unitless. Otherwise raises an exception
|
1003
|
-
# @return [Float]
|
1004
|
-
# @raise [RuntimeError] when not unitless
|
1005
|
-
def to_f
|
1006
|
-
return @scalar.to_f if self.unitless?
|
1007
|
-
raise RuntimeError, "Cannot convert '#{self.to_s}' to Float unless unitless. Use Unit#scalar"
|
1008
|
-
end
|
1009
1016
|
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
+
# converts the unit back to a complex if it is unitless. Otherwise raises an exception
|
1018
|
+
# @return [Complex]
|
1019
|
+
# @raise [RuntimeError] when not unitless
|
1020
|
+
def to_c
|
1021
|
+
return Complex(@scalar) if self.unitless?
|
1022
|
+
raise RuntimeError, "Cannot convert '#{self.to_s}' to Complex unless unitless. Use Unit#scalar"
|
1023
|
+
end
|
1017
1024
|
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
alias :to_int :to_i
|
1026
|
-
|
1027
|
-
# if unitless, returns a Rational, otherwise raises an error
|
1028
|
-
# @return [Rational]
|
1029
|
-
# @raise [RuntimeError] when not unitless
|
1030
|
-
def to_r
|
1031
|
-
return @scalar.to_r if self.unitless?
|
1032
|
-
raise RuntimeError, "Cannot convert '#{self.to_s}' to Rational unless unitless. Use Unit#scalar"
|
1033
|
-
end
|
1025
|
+
# if unitless, returns an int, otherwise raises an error
|
1026
|
+
# @return [Integer]
|
1027
|
+
# @raise [RuntimeError] when not unitless
|
1028
|
+
def to_i
|
1029
|
+
return @scalar.to_int if self.unitless?
|
1030
|
+
raise RuntimeError, "Cannot convert '#{self.to_s}' to Integer unless unitless. Use Unit#scalar"
|
1031
|
+
end
|
1034
1032
|
|
1035
|
-
|
1036
|
-
# @return [String]
|
1037
|
-
def as_json(*args)
|
1038
|
-
to_s
|
1039
|
-
end
|
1033
|
+
alias :to_int :to_i
|
1040
1034
|
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
output_denominator = []
|
1048
|
-
num = @numerator.clone.compact
|
1049
|
-
den = @denominator.clone.compact
|
1050
|
-
|
1051
|
-
if @numerator == UNITY_ARRAY
|
1052
|
-
output_numerator << "1"
|
1053
|
-
else
|
1054
|
-
while defn = Unit.definition(num.shift) do
|
1055
|
-
if defn && defn.prefix?
|
1056
|
-
output_numerator << defn.display_name + Unit.definition(num.shift).display_name
|
1057
|
-
else
|
1058
|
-
output_numerator << defn.display_name
|
1059
|
-
end
|
1060
|
-
end
|
1035
|
+
# if unitless, returns a Rational, otherwise raises an error
|
1036
|
+
# @return [Rational]
|
1037
|
+
# @raise [RuntimeError] when not unitless
|
1038
|
+
def to_r
|
1039
|
+
return @scalar.to_r if self.unitless?
|
1040
|
+
raise RuntimeError, "Cannot convert '#{self.to_s}' to Rational unless unitless. Use Unit#scalar"
|
1061
1041
|
end
|
1062
1042
|
|
1063
|
-
|
1043
|
+
# Returns string formatted for json
|
1044
|
+
# @return [String]
|
1045
|
+
def as_json(*args)
|
1046
|
+
to_s
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
# returns the 'unit' part of the Unit object without the scalar
|
1050
|
+
# @return [String]
|
1051
|
+
def units(with_prefix = true)
|
1052
|
+
return "" if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY
|
1053
|
+
output_numerator = []
|
1064
1054
|
output_denominator = []
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1055
|
+
num = @numerator.clone.compact
|
1056
|
+
den = @denominator.clone.compact
|
1057
|
+
|
1058
|
+
if @numerator == UNITY_ARRAY
|
1059
|
+
output_numerator << "1"
|
1060
|
+
else
|
1061
|
+
while defn = RubyUnits::Unit.definition(num.shift) do
|
1062
|
+
if defn && defn.prefix?
|
1063
|
+
if with_prefix
|
1064
|
+
output_numerator << (defn.display_name + RubyUnits::Unit.definition(num.shift).display_name)
|
1065
|
+
end
|
1066
|
+
else
|
1067
|
+
output_numerator << defn.display_name
|
1068
|
+
end
|
1071
1069
|
end
|
1072
1070
|
end
|
1073
|
-
end
|
1074
1071
|
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
def -@
|
1089
|
-
return -@scalar if self.unitless?
|
1090
|
-
return (self.dup * -1)
|
1091
|
-
end
|
1072
|
+
if @denominator == UNITY_ARRAY
|
1073
|
+
output_denominator = []
|
1074
|
+
else
|
1075
|
+
while defn = RubyUnits::Unit.definition(den.shift) do
|
1076
|
+
if defn && defn.prefix?
|
1077
|
+
if with_prefix
|
1078
|
+
output_denominator << (defn.display_name + RubyUnits::Unit.definition(den.shift).display_name)
|
1079
|
+
end
|
1080
|
+
else
|
1081
|
+
output_denominator << defn.display_name
|
1082
|
+
end
|
1083
|
+
end
|
1084
|
+
end
|
1092
1085
|
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1086
|
+
on = output_numerator.uniq.
|
1087
|
+
map { |x| [x, output_numerator.count(x)] }.
|
1088
|
+
map { |element, power| ("#{element}".strip + (power > 1 ? "^#{power}" : '')) }
|
1089
|
+
od = output_denominator.uniq.
|
1090
|
+
map { |x| [x, output_denominator.count(x)] }.
|
1091
|
+
map { |element, power| ("#{element}".strip + (power > 1 ? "^#{power}" : '')) }
|
1092
|
+
out = "#{on.join('*')}#{od.empty? ? '' : '/' + od.join('*')}".strip
|
1093
|
+
return out
|
1094
|
+
end
|
1099
1095
|
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1096
|
+
# negates the scalar of the Unit
|
1097
|
+
# @return [Numeric,Unit]
|
1098
|
+
def -@
|
1099
|
+
return -@scalar if self.unitless?
|
1100
|
+
return (self.dup * -1)
|
1101
|
+
end
|
1106
1102
|
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1103
|
+
# absolute value of a unit
|
1104
|
+
# @return [Numeric,Unit]
|
1105
|
+
def abs
|
1106
|
+
return @scalar.abs if self.unitless?
|
1107
|
+
return RubyUnits::Unit.new(@scalar.abs, @numerator, @denominator)
|
1108
|
+
end
|
1112
1109
|
|
1113
|
-
|
1110
|
+
# ceil of a unit
|
1114
1111
|
# @return [Numeric,Unit]
|
1115
|
-
def
|
1116
|
-
return @scalar.
|
1117
|
-
return Unit.new(@scalar.
|
1112
|
+
def ceil
|
1113
|
+
return @scalar.ceil if self.unitless?
|
1114
|
+
return RubyUnits::Unit.new(@scalar.ceil, @numerator, @denominator)
|
1118
1115
|
end
|
1119
|
-
|
1116
|
+
|
1120
1117
|
# @return [Numeric,Unit]
|
1121
|
-
def
|
1122
|
-
return @scalar.
|
1123
|
-
return Unit.new(@scalar.
|
1118
|
+
def floor
|
1119
|
+
return @scalar.floor if self.unitless?
|
1120
|
+
return RubyUnits::Unit.new(@scalar.floor, @numerator, @denominator)
|
1124
1121
|
end
|
1125
|
-
end
|
1126
1122
|
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1123
|
+
if RUBY_VERSION < '1.9'
|
1124
|
+
# @return [Numeric,Unit]
|
1125
|
+
def round
|
1126
|
+
return @scalar.round if self.unitless?
|
1127
|
+
return RubyUnits::Unit.new(@scalar.round, @numerator, @denominator)
|
1128
|
+
end
|
1129
|
+
else
|
1130
|
+
# @return [Numeric,Unit]
|
1131
|
+
def round(ndigits = 0)
|
1132
|
+
return @scalar.round(ndigits) if self.unitless?
|
1133
|
+
return RubyUnits::Unit.new(@scalar.round(ndigits), @numerator, @denominator)
|
1134
|
+
end
|
1135
|
+
end
|
1132
1136
|
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i
|
1139
|
-
return Unit.new(@scalar.to_i.succ, @numerator, @denominator)
|
1140
|
-
end
|
1141
|
-
alias :next :succ
|
1142
|
-
|
1143
|
-
# returns previous unit in a range. '2 mm'.unit.pred #=> '1 mm'.unit
|
1144
|
-
# only works when the scalar is an integer
|
1145
|
-
# @return [Unit]
|
1146
|
-
# @raise [ArgumentError] when scalar is not equal to an integer
|
1147
|
-
def pred
|
1148
|
-
raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i
|
1149
|
-
return Unit.new(@scalar.to_i.pred, @numerator, @denominator)
|
1150
|
-
end
|
1137
|
+
# @return [Numeric, Unit]
|
1138
|
+
def truncate
|
1139
|
+
return @scalar.truncate if self.unitless?
|
1140
|
+
return RubyUnits::Unit.new(@scalar.truncate, @numerator, @denominator)
|
1141
|
+
end
|
1151
1142
|
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1143
|
+
# returns next unit in a range. '1 mm'.unit.succ #=> '2 mm'.unit
|
1144
|
+
# only works when the scalar is an integer
|
1145
|
+
# @return [Unit]
|
1146
|
+
# @raise [ArgumentError] when scalar is not equal to an integer
|
1147
|
+
def succ
|
1148
|
+
raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i
|
1149
|
+
return RubyUnits::Unit.new(@scalar.to_i.succ, @numerator, @denominator)
|
1150
|
+
end
|
1158
1151
|
|
1159
|
-
|
1160
|
-
# defined by DateTime
|
1161
|
-
# @return [DateTime]
|
1162
|
-
def to_datetime
|
1163
|
-
return DateTime.new!(self.convert_to('d').scalar)
|
1164
|
-
end
|
1152
|
+
alias :next :succ
|
1165
1153
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
return
|
1169
|
-
|
1154
|
+
# returns previous unit in a range. '2 mm'.unit.pred #=> '1 mm'.unit
|
1155
|
+
# only works when the scalar is an integer
|
1156
|
+
# @return [Unit]
|
1157
|
+
# @raise [ArgumentError] when scalar is not equal to an integer
|
1158
|
+
def pred
|
1159
|
+
raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i
|
1160
|
+
return RubyUnits::Unit.new(@scalar.to_i.pred, @numerator, @denominator)
|
1161
|
+
end
|
1170
1162
|
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1163
|
+
# Tries to make a Time object from current unit. Assumes the current unit hold the duration in seconds from the epoch.
|
1164
|
+
# @return [Time]
|
1165
|
+
def to_time
|
1166
|
+
return Time.at(self)
|
1167
|
+
end
|
1176
1168
|
|
1177
|
-
|
1178
|
-
# @return [Unit]
|
1179
|
-
def ago
|
1180
|
-
return self.before
|
1181
|
-
end
|
1169
|
+
alias :time :to_time
|
1182
1170
|
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
return (time_point - self rescue time_point.to_datetime - self)
|
1189
|
-
else
|
1190
|
-
raise ArgumentError, "Must specify a Time, Date, or DateTime"
|
1171
|
+
# convert a duration to a DateTime. This will work so long as the duration is the duration from the zero date
|
1172
|
+
# defined by DateTime
|
1173
|
+
# @return [DateTime]
|
1174
|
+
def to_datetime
|
1175
|
+
return DateTime.new!(self.convert_to('d').scalar)
|
1191
1176
|
end
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
# @param [Time, Date, DateTime] time_point
|
1197
|
-
# @return [Unit]
|
1198
|
-
# @raise [ArgumentError] when time point is not a Time, Date, or DateTime
|
1199
|
-
def since(time_point)
|
1200
|
-
case time_point
|
1201
|
-
when Time
|
1202
|
-
return (Time.now - time_point).unit('s').convert_to(self)
|
1203
|
-
when DateTime, Date
|
1204
|
-
return (DateTime.now - time_point).unit('d').convert_to(self)
|
1205
|
-
else
|
1206
|
-
raise ArgumentError, "Must specify a Time, Date, or DateTime"
|
1177
|
+
|
1178
|
+
# @return [Date]
|
1179
|
+
def to_date
|
1180
|
+
return Date.new0(self.convert_to('d').scalar)
|
1207
1181
|
end
|
1208
|
-
end
|
1209
1182
|
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
case time_point
|
1215
|
-
when Time
|
1216
|
-
return (time_point - Time.now).unit('s').convert_to(self)
|
1217
|
-
when DateTime, Date
|
1218
|
-
return (time_point - DateTime.now).unit('d').convert_to(self)
|
1219
|
-
else
|
1220
|
-
raise ArgumentError, "Must specify a Time, Date, or DateTime"
|
1183
|
+
# true if scalar is zero
|
1184
|
+
# @return [Boolean]
|
1185
|
+
def zero?
|
1186
|
+
return self.base_scalar.zero?
|
1221
1187
|
end
|
1222
|
-
end
|
1223
1188
|
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
def from(time_point)
|
1229
|
-
case time_point
|
1230
|
-
when Time, DateTime, Date
|
1231
|
-
return (time_point + self rescue time_point.to_datetime + self)
|
1232
|
-
else
|
1233
|
-
raise ArgumentError, "Must specify a Time, Date, or DateTime"
|
1189
|
+
# @example '5 min'.unit.ago
|
1190
|
+
# @return [Unit]
|
1191
|
+
def ago
|
1192
|
+
return self.before
|
1234
1193
|
end
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1194
|
+
|
1195
|
+
# @example '5 min'.before(time)
|
1196
|
+
# @return [Unit]
|
1197
|
+
def before(time_point = ::Time.now)
|
1198
|
+
case time_point
|
1199
|
+
when Time, Date, DateTime
|
1200
|
+
return (time_point - self rescue time_point.to_datetime - self)
|
1201
|
+
else
|
1202
|
+
raise ArgumentError, "Must specify a Time, Date, or DateTime"
|
1203
|
+
end
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
alias :before_now :before
|
1207
|
+
|
1208
|
+
# @example 'min'.since(time)
|
1209
|
+
# @param [Time, Date, DateTime] time_point
|
1210
|
+
# @return [Unit]
|
1211
|
+
# @raise [ArgumentError] when time point is not a Time, Date, or DateTime
|
1212
|
+
def since(time_point)
|
1213
|
+
case time_point
|
1214
|
+
when Time
|
1215
|
+
return (Time.now - time_point).unit('s').convert_to(self)
|
1216
|
+
when DateTime, Date
|
1217
|
+
return (DateTime.now - time_point).unit('d').convert_to(self)
|
1218
|
+
else
|
1219
|
+
raise ArgumentError, "Must specify a Time, Date, or DateTime"
|
1220
|
+
end
|
1252
1221
|
end
|
1253
|
-
end
|
1254
1222
|
|
1255
|
-
|
1256
|
-
|
1223
|
+
# @example 'min'.until(time)
|
1224
|
+
# @param [Time, Date, DateTime] time_point
|
1225
|
+
# @return [Unit]
|
1226
|
+
def until(time_point)
|
1227
|
+
case time_point
|
1228
|
+
when Time
|
1229
|
+
return (time_point - Time.now).unit('s').convert_to(self)
|
1230
|
+
when DateTime, Date
|
1231
|
+
return (time_point - DateTime.now).unit('d').convert_to(self)
|
1232
|
+
else
|
1233
|
+
raise ArgumentError, "Must specify a Time, Date, or DateTime"
|
1234
|
+
end
|
1235
|
+
end
|
1257
1236
|
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1237
|
+
# @example '5 min'.from(time)
|
1238
|
+
# @param [Time, Date, DateTime] time_point
|
1239
|
+
# @return [Time, Date, DateTime]
|
1240
|
+
# @raise [ArgumentError] when passed argument is not a Time, Date, or DateTime
|
1241
|
+
def from(time_point)
|
1242
|
+
case time_point
|
1243
|
+
when Time, DateTime, Date
|
1244
|
+
return (time_point + self rescue time_point.to_datetime + self)
|
1245
|
+
else
|
1246
|
+
raise ArgumentError, "Must specify a Time, Date, or DateTime"
|
1247
|
+
end
|
1268
1248
|
end
|
1269
|
-
end
|
1270
1249
|
|
1271
|
-
|
1272
|
-
|
1273
|
-
# @raise [ArgumentError] when exponent associated with a unit is > 20 or < -20
|
1274
|
-
def unit_signature_vector
|
1275
|
-
return self.to_base.unit_signature_vector unless self.is_base?
|
1276
|
-
vector = Array.new(SIGNATURE_VECTOR.size,0)
|
1277
|
-
# it's possible to have a kind that misses the array... kinds like :counting
|
1278
|
-
# are more like prefixes, so don't use them to calculate the vector
|
1279
|
-
@numerator.map {|element| Unit.definition(element)}.each do |definition|
|
1280
|
-
index = SIGNATURE_VECTOR.index(definition.kind)
|
1281
|
-
vector[index] += 1 if index
|
1282
|
-
end
|
1283
|
-
@denominator.map {|element| Unit.definition(element)}.each do |definition|
|
1284
|
-
index = SIGNATURE_VECTOR.index(definition.kind)
|
1285
|
-
vector[index] -= 1 if index
|
1286
|
-
end
|
1287
|
-
raise ArgumentError, "Power out of range (-20 < net power of a unit < 20)" if vector.any? {|x| x.abs >=20}
|
1288
|
-
return vector
|
1289
|
-
end
|
1250
|
+
alias :after :from
|
1251
|
+
alias :from_now :from
|
1290
1252
|
|
1291
|
-
|
1253
|
+
# automatically coerce objects to units when possible
|
1254
|
+
# if an object defines a 'to_unit' method, it will be coerced using that method
|
1255
|
+
# @param [Object, #to_unit]
|
1256
|
+
# @return [Array]
|
1257
|
+
def coerce(other)
|
1258
|
+
if other.respond_to? :to_unit
|
1259
|
+
return [other.to_unit, self]
|
1260
|
+
end
|
1261
|
+
case other
|
1262
|
+
when Unit
|
1263
|
+
return [other, self]
|
1264
|
+
else
|
1265
|
+
return [RubyUnits::Unit.new(other), self]
|
1266
|
+
end
|
1267
|
+
end
|
1292
1268
|
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1269
|
+
# returns a new unit that has been
|
1270
|
+
def best_prefix
|
1271
|
+
_best_prefix = if (self.kind == :information)
|
1272
|
+
@@PREFIX_VALUES.key(2**((Math.log(self.base_scalar,2) / 10.0).floor * 10))
|
1273
|
+
else
|
1274
|
+
@@PREFIX_VALUES.key(10**((Math.log10(self.base_scalar) / 3.0).floor * 3))
|
1275
|
+
end
|
1276
|
+
self.to(RubyUnits::Unit.new(@@PREFIX_MAP.key(_best_prefix)+self.units(false)))
|
1277
|
+
end
|
1300
1278
|
|
1301
|
-
|
1302
|
-
|
1303
|
-
#
|
1304
|
-
# Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering, 21(8), Aug 1995, pp.651-661
|
1305
|
-
# @see http://doi.ieeecomputersociety.org/10.1109/32.403789
|
1306
|
-
# @return [Array]
|
1307
|
-
def unit_signature
|
1308
|
-
return @signature unless @signature.nil?
|
1309
|
-
vector = unit_signature_vector
|
1310
|
-
vector.each_with_index {|item,index| vector[index] = item * 20**index}
|
1311
|
-
@signature=vector.inject(0) {|sum,n| sum+n}
|
1312
|
-
return @signature
|
1313
|
-
end
|
1279
|
+
# Protected and Private Functions that should only be called from this class
|
1280
|
+
protected
|
1314
1281
|
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
den = d.dup
|
1322
|
-
|
1323
|
-
num.delete_if {|v| v == UNITY}
|
1324
|
-
den.delete_if {|v| v == UNITY}
|
1325
|
-
combined = Hash.new(0)
|
1326
|
-
|
1327
|
-
i = 0
|
1328
|
-
loop do
|
1329
|
-
break if i > num.size
|
1330
|
-
if @@PREFIX_VALUES.has_key? num[i]
|
1331
|
-
k = [num[i],num[i+1]]
|
1332
|
-
i += 2
|
1282
|
+
# figure out what the scalar part of the base unit for this unit is
|
1283
|
+
# @return [nil]
|
1284
|
+
def update_base_scalar
|
1285
|
+
if self.is_base?
|
1286
|
+
@base_scalar = @scalar
|
1287
|
+
@signature = unit_signature
|
1333
1288
|
else
|
1334
|
-
|
1335
|
-
|
1289
|
+
base = self.to_base
|
1290
|
+
@base_scalar = base.scalar
|
1291
|
+
@signature = base.signature
|
1336
1292
|
end
|
1337
|
-
combined[k] += 1 unless k.nil? || k == UNITY
|
1338
1293
|
end
|
1339
1294
|
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1295
|
+
# calculates the unit signature vector used by unit_signature
|
1296
|
+
# @return [Array]
|
1297
|
+
# @raise [ArgumentError] when exponent associated with a unit is > 20 or < -20
|
1298
|
+
def unit_signature_vector
|
1299
|
+
return self.to_base.unit_signature_vector unless self.is_base?
|
1300
|
+
vector = Array.new(SIGNATURE_VECTOR.size, 0)
|
1301
|
+
# it's possible to have a kind that misses the array... kinds like :counting
|
1302
|
+
# are more like prefixes, so don't use them to calculate the vector
|
1303
|
+
@numerator.map { |element| RubyUnits::Unit.definition(element) }.each do |definition|
|
1304
|
+
index = SIGNATURE_VECTOR.index(definition.kind)
|
1305
|
+
vector[index] += 1 if index
|
1306
|
+
end
|
1307
|
+
@denominator.map { |element| RubyUnits::Unit.definition(element) }.each do |definition|
|
1308
|
+
index = SIGNATURE_VECTOR.index(definition.kind)
|
1309
|
+
vector[index] -= 1 if index
|
1310
|
+
end
|
1311
|
+
raise ArgumentError, "Power out of range (-20 < net power of a unit < 20)" if vector.any? { |x| x.abs >=20 }
|
1312
|
+
return vector
|
1313
|
+
end
|
1314
|
+
|
1315
|
+
|
1316
|
+
private
|
1317
|
+
|
1318
|
+
# used by #dup to duplicate a Unit
|
1319
|
+
# @param [Unit] other
|
1320
|
+
# @private
|
1321
|
+
def initialize_copy(other)
|
1322
|
+
@numerator = other.numerator.dup
|
1323
|
+
@denominator = other.denominator.dup
|
1324
|
+
end
|
1325
|
+
|
1326
|
+
# calculates the unit signature id for use in comparing compatible units and simplification
|
1327
|
+
# the signature is based on a simple classification of units and is based on the following publication
|
1328
|
+
#
|
1329
|
+
# Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering, 21(8), Aug 1995, pp.651-661
|
1330
|
+
# @see http://doi.ieeecomputersociety.org/10.1109/32.403789
|
1331
|
+
# @return [Array]
|
1332
|
+
def unit_signature
|
1333
|
+
return @signature unless @signature.nil?
|
1334
|
+
vector = unit_signature_vector
|
1335
|
+
vector.each_with_index { |item, index| vector[index] = item * 20**index }
|
1336
|
+
@signature=vector.inject(0) { |sum, n| sum+n }
|
1337
|
+
return @signature
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
# @param [Numeric] q quantity
|
1341
|
+
# @param [Array] n numerator
|
1342
|
+
# @param [Array] d denominator
|
1343
|
+
# @return [Hash]
|
1344
|
+
def self.eliminate_terms(q, n, d)
|
1345
|
+
num = n.dup
|
1346
|
+
den = d.dup
|
1347
|
+
|
1348
|
+
num.delete_if { |v| v == UNITY }
|
1349
|
+
den.delete_if { |v| v == UNITY }
|
1350
|
+
combined = Hash.new(0)
|
1351
|
+
|
1352
|
+
i = 0
|
1353
|
+
loop do
|
1354
|
+
break if i > num.size
|
1355
|
+
if @@PREFIX_VALUES.has_key? num[i]
|
1356
|
+
k = [num[i], num[i+1]]
|
1357
|
+
i += 2
|
1358
|
+
else
|
1359
|
+
k = num[i]
|
1360
|
+
i += 1
|
1361
|
+
end
|
1362
|
+
combined[k] += 1 unless k.nil? || k == UNITY
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
j = 0
|
1366
|
+
loop do
|
1367
|
+
break if j > den.size
|
1343
1368
|
if @@PREFIX_VALUES.has_key? den[j]
|
1344
|
-
k = [den[j],den[j+1]]
|
1369
|
+
k = [den[j], den[j+1]]
|
1345
1370
|
j += 2
|
1346
1371
|
else
|
1347
1372
|
k = den[j]
|
1348
1373
|
j += 1
|
1349
1374
|
end
|
1350
|
-
|
1351
|
-
end
|
1352
|
-
|
1353
|
-
num = []
|
1354
|
-
den = []
|
1355
|
-
for key, value in combined do
|
1356
|
-
case
|
1357
|
-
when value > 0
|
1358
|
-
value.times {num << key}
|
1359
|
-
when value < 0
|
1360
|
-
value.abs.times {den << key}
|
1375
|
+
combined[k] -= 1 unless k.nil? || k == UNITY
|
1361
1376
|
end
|
1362
|
-
end
|
1363
|
-
num = UNITY_ARRAY if num.empty?
|
1364
|
-
den = UNITY_ARRAY if den.empty?
|
1365
|
-
return {:scalar=>q, :numerator=>num.flatten.compact, :denominator=>den.flatten.compact}
|
1366
|
-
end
|
1367
1377
|
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
# 6'4" -- recognized as 6 feet + 4 inches
|
1378
|
-
# 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
|
1379
|
-
# @return [nil | Unit]
|
1380
|
-
# @todo This should either be a separate class or at least a class method
|
1381
|
-
def parse(passed_unit_string="0")
|
1382
|
-
unit_string = passed_unit_string.dup
|
1383
|
-
if unit_string =~ /\$\s*(#{NUMBER_REGEX})/
|
1384
|
-
unit_string = "#{$1} USD"
|
1385
|
-
end
|
1386
|
-
unit_string.gsub!("\u00b0".force_encoding('utf-8'), 'deg') if RUBY_VERSION >= '1.9' && unit_string.encoding == Encoding::UTF_8
|
1387
|
-
|
1388
|
-
unit_string.gsub!(/%/,'percent')
|
1389
|
-
unit_string.gsub!(/'/,'feet')
|
1390
|
-
unit_string.gsub!(/"/,'inch')
|
1391
|
-
unit_string.gsub!(/#/,'pound')
|
1392
|
-
|
1393
|
-
#:nocov:
|
1394
|
-
#:nocov_19:
|
1395
|
-
if defined?(Uncertain) && unit_string =~ /(\+\/-|±)/
|
1396
|
-
value, uncertainty, unit_s = unit_string.scan(UNCERTAIN_REGEX)[0]
|
1397
|
-
result = unit_s.unit * Uncertain.new(value.to_f,uncertainty.to_f)
|
1398
|
-
copy(result)
|
1399
|
-
return
|
1400
|
-
end
|
1401
|
-
#:nocov:
|
1402
|
-
#:nocov_19:
|
1403
|
-
|
1404
|
-
if defined?(Complex) && unit_string =~ COMPLEX_NUMBER
|
1405
|
-
real, imaginary, unit_s = unit_string.scan(COMPLEX_REGEX)[0]
|
1406
|
-
result = Unit(unit_s || '1') * Complex(real.to_f,imaginary.to_f)
|
1407
|
-
copy(result)
|
1408
|
-
return
|
1409
|
-
end
|
1410
|
-
|
1411
|
-
if defined?(Rational) && unit_string =~ RATIONAL_NUMBER
|
1412
|
-
numerator, denominator, unit_s = unit_string.scan(RATIONAL_REGEX)[0]
|
1413
|
-
result = Unit(unit_s || '1') * Rational(numerator.to_i,denominator.to_i)
|
1414
|
-
copy(result)
|
1415
|
-
return
|
1416
|
-
end
|
1417
|
-
|
1418
|
-
unit_string =~ NUMBER_REGEX
|
1419
|
-
unit = @@cached_units[$2]
|
1420
|
-
mult = ($1.empty? ? 1.0 : $1.to_f) rescue 1.0
|
1421
|
-
mult = mult.to_int if (mult.to_int == mult)
|
1422
|
-
if unit
|
1423
|
-
copy(unit)
|
1424
|
-
@scalar *= mult
|
1425
|
-
@base_scalar *= mult
|
1426
|
-
return self
|
1427
|
-
end
|
1428
|
-
unit_string.gsub!(/<(#{@@UNIT_REGEX})><(#{@@UNIT_REGEX})>/, '\1*\2')
|
1429
|
-
unit_string.gsub!(/[<>]/,"")
|
1430
|
-
|
1431
|
-
if unit_string =~ /:/
|
1432
|
-
hours, minutes, seconds, microseconds = unit_string.scan(TIME_REGEX)[0]
|
1433
|
-
raise ArgumentError, "Invalid Duration" if [hours, minutes, seconds, microseconds].all? {|x| x.nil?}
|
1434
|
-
result = "#{hours || 0} h".unit +
|
1435
|
-
"#{minutes || 0} minutes".unit +
|
1436
|
-
"#{seconds || 0} seconds".unit +
|
1437
|
-
"#{microseconds || 0} usec".unit
|
1438
|
-
copy(result)
|
1439
|
-
return
|
1440
|
-
end
|
1441
|
-
|
1442
|
-
# Special processing for unusual unit strings
|
1443
|
-
# feet -- 6'5"
|
1444
|
-
feet, inches = unit_string.scan(FEET_INCH_REGEX)[0]
|
1445
|
-
if (feet && inches)
|
1446
|
-
result = Unit.new("#{feet} ft") + Unit.new("#{inches} inches")
|
1447
|
-
copy(result)
|
1448
|
-
return
|
1449
|
-
end
|
1450
|
-
|
1451
|
-
# weight -- 8 lbs 12 oz
|
1452
|
-
pounds, oz = unit_string.scan(LBS_OZ_REGEX)[0]
|
1453
|
-
if (pounds && oz)
|
1454
|
-
result = Unit.new("#{pounds} lbs") + Unit.new("#{oz} oz")
|
1455
|
-
copy(result)
|
1456
|
-
return
|
1457
|
-
end
|
1458
|
-
|
1459
|
-
# more than one per. I.e., "1 m/s/s"
|
1460
|
-
raise( ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.count('/') > 1
|
1461
|
-
raise( ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.scan(/\s[02-9]/).size > 0
|
1462
|
-
@scalar, top, bottom = unit_string.scan(UNIT_STRING_REGEX)[0] #parse the string into parts
|
1463
|
-
top.scan(TOP_REGEX).each do |item|
|
1464
|
-
n = item[1].to_i
|
1465
|
-
x = "#{item[0]} "
|
1466
|
-
case
|
1467
|
-
when n>=0
|
1468
|
-
top.gsub!(/#{item[0]}(\^|\*\*)#{n}/) {|s| x * n}
|
1469
|
-
when n<0
|
1470
|
-
bottom = "#{bottom} #{x * -n}"; top.gsub!(/#{item[0]}(\^|\*\*)#{n}/,"")
|
1378
|
+
num = []
|
1379
|
+
den = []
|
1380
|
+
for key, value in combined do
|
1381
|
+
case
|
1382
|
+
when value > 0
|
1383
|
+
value.times { num << key }
|
1384
|
+
when value < 0
|
1385
|
+
value.abs.times { den << key }
|
1386
|
+
end
|
1471
1387
|
end
|
1388
|
+
num = UNITY_ARRAY if num.empty?
|
1389
|
+
den = UNITY_ARRAY if den.empty?
|
1390
|
+
return { :scalar => q, :numerator => num.flatten.compact, :denominator => den.flatten.compact }
|
1472
1391
|
end
|
1473
|
-
bottom.gsub!(BOTTOM_REGEX) {|s| "#{$1} " * $2.to_i} if bottom
|
1474
|
-
@scalar = @scalar.to_f unless @scalar.nil? || @scalar.empty?
|
1475
|
-
@scalar = 1 unless @scalar.kind_of? Numeric
|
1476
|
-
@scalar = @scalar.to_int if (@scalar.to_int == @scalar)
|
1477
1392
|
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1393
|
+
# parse a string into a unit object.
|
1394
|
+
# Typical formats like :
|
1395
|
+
# "5.6 kg*m/s^2"
|
1396
|
+
# "5.6 kg*m*s^-2"
|
1397
|
+
# "5.6 kilogram*meter*second^-2"
|
1398
|
+
# "2.2 kPa"
|
1399
|
+
# "37 degC"
|
1400
|
+
# "1" -- creates a unitless constant with value 1
|
1401
|
+
# "GPa" -- creates a unit with scalar 1 with units 'GPa'
|
1402
|
+
# 6'4" -- recognized as 6 feet + 4 inches
|
1403
|
+
# 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
|
1404
|
+
# @return [nil | Unit]
|
1405
|
+
# @todo This should either be a separate class or at least a class method
|
1406
|
+
def parse(passed_unit_string="0")
|
1407
|
+
unit_string = passed_unit_string.dup
|
1408
|
+
if unit_string =~ /\$\s*(#{NUMBER_REGEX})/
|
1409
|
+
unit_string = "#{$1} USD"
|
1410
|
+
end
|
1411
|
+
unit_string.gsub!("\u00b0".force_encoding('utf-8'), 'deg') if RUBY_VERSION >= '1.9' && unit_string.encoding == Encoding::UTF_8
|
1412
|
+
|
1413
|
+
unit_string.gsub!(/%/, 'percent')
|
1414
|
+
unit_string.gsub!(/'/, 'feet')
|
1415
|
+
unit_string.gsub!(/"/, 'inch')
|
1416
|
+
unit_string.gsub!(/#/, 'pound')
|
1417
|
+
|
1418
|
+
#:nocov:
|
1419
|
+
#:nocov_19:
|
1420
|
+
if defined?(Uncertain) && unit_string =~ /(\+\/-|±)/
|
1421
|
+
value, uncertainty, unit_s = unit_string.scan(UNCERTAIN_REGEX)[0]
|
1422
|
+
result = unit_s.unit * Uncertain.new(value.to_f, uncertainty.to_f)
|
1423
|
+
copy(result)
|
1424
|
+
return
|
1425
|
+
end
|
1426
|
+
#:nocov:
|
1427
|
+
#:nocov_19:
|
1482
1428
|
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1429
|
+
if defined?(Complex) && unit_string =~ COMPLEX_NUMBER
|
1430
|
+
real, imaginary, unit_s = unit_string.scan(COMPLEX_REGEX)[0]
|
1431
|
+
result = RubyUnits::Unit.new(unit_s || '1') * Complex(real.to_f, imaginary.to_f)
|
1432
|
+
copy(result)
|
1433
|
+
return
|
1434
|
+
end
|
1487
1435
|
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1436
|
+
if defined?(Rational) && unit_string =~ RATIONAL_NUMBER
|
1437
|
+
numerator, denominator, unit_s = unit_string.scan(RATIONAL_REGEX)[0]
|
1438
|
+
result = RubyUnits::Unit.new(unit_s || '1') * Rational(numerator.to_i, denominator.to_i)
|
1439
|
+
copy(result)
|
1440
|
+
return
|
1441
|
+
end
|
1491
1442
|
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1443
|
+
unit_string =~ NUMBER_REGEX
|
1444
|
+
unit = @@cached_units[$2]
|
1445
|
+
mult = ($1.empty? ? 1.0 : $1.to_f) rescue 1.0
|
1446
|
+
mult = mult.to_int if (mult.to_int == mult)
|
1447
|
+
if unit
|
1448
|
+
copy(unit)
|
1449
|
+
@scalar *= mult
|
1450
|
+
@base_scalar *= mult
|
1451
|
+
return self
|
1452
|
+
end
|
1453
|
+
unit_string.gsub!(/<(#{@@UNIT_REGEX})><(#{@@UNIT_REGEX})>/, '\1*\2')
|
1454
|
+
unit_string.gsub!(/[<>]/, "")
|
1455
|
+
|
1456
|
+
if unit_string =~ /:/
|
1457
|
+
hours, minutes, seconds, microseconds = unit_string.scan(TIME_REGEX)[0]
|
1458
|
+
raise ArgumentError, "Invalid Duration" if [hours, minutes, seconds, microseconds].all? { |x| x.nil? }
|
1459
|
+
result = "#{hours || 0} h".unit +
|
1460
|
+
"#{minutes || 0} minutes".unit +
|
1461
|
+
"#{seconds || 0} seconds".unit +
|
1462
|
+
"#{microseconds || 0} usec".unit
|
1463
|
+
copy(result)
|
1464
|
+
return
|
1465
|
+
end
|
1495
1466
|
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1467
|
+
# Special processing for unusual unit strings
|
1468
|
+
# feet -- 6'5"
|
1469
|
+
feet, inches = unit_string.scan(FEET_INCH_REGEX)[0]
|
1470
|
+
if (feet && inches)
|
1471
|
+
result = RubyUnits::Unit.new("#{feet} ft") + RubyUnits::Unit.new("#{inches} inches")
|
1472
|
+
copy(result)
|
1473
|
+
return
|
1474
|
+
end
|
1500
1475
|
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
1505
|
-
|
1476
|
+
# weight -- 8 lbs 12 oz
|
1477
|
+
pounds, oz = unit_string.scan(LBS_OZ_REGEX)[0]
|
1478
|
+
if (pounds && oz)
|
1479
|
+
result = RubyUnits::Unit.new("#{pounds} lbs") + RubyUnits::Unit.new("#{oz} oz")
|
1480
|
+
copy(result)
|
1481
|
+
return
|
1482
|
+
end
|
1506
1483
|
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
complex = %r{#{sci}{2,2}i}
|
1520
|
-
anynumber = %r{(?:(#{complex}|#{rational}|#{sci})\b)?\s?([\D].*)?}
|
1521
|
-
num, unit = string.scan(anynumber).first
|
1522
|
-
|
1523
|
-
return [case num
|
1524
|
-
when NilClass
|
1525
|
-
1
|
1526
|
-
when complex
|
1527
|
-
if num.respond_to?(:to_c)
|
1528
|
-
num.to_c
|
1529
|
-
else
|
1530
|
-
#:nocov_19:
|
1531
|
-
Complex(*num.scan(/(#{sci})(#{sci})i/).flatten.map {|n| n.to_i})
|
1532
|
-
#:nocov_19:
|
1484
|
+
# more than one per. I.e., "1 m/s/s"
|
1485
|
+
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.count('/') > 1
|
1486
|
+
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.scan(/\s[02-9]/).size > 0
|
1487
|
+
@scalar, top, bottom = unit_string.scan(UNIT_STRING_REGEX)[0] #parse the string into parts
|
1488
|
+
top.scan(TOP_REGEX).each do |item|
|
1489
|
+
n = item[1].to_i
|
1490
|
+
x = "#{item[0]} "
|
1491
|
+
case
|
1492
|
+
when n>=0
|
1493
|
+
top.gsub!(/#{item[0]}(\^|\*\*)#{n}/) { |s| x * n }
|
1494
|
+
when n<0
|
1495
|
+
bottom = "#{bottom} #{x * -n}"; top.gsub!(/#{item[0]}(\^|\*\*)#{n}/, "")
|
1533
1496
|
end
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1497
|
+
end
|
1498
|
+
bottom.gsub!(BOTTOM_REGEX) { |s| "#{$1} " * $2.to_i } if bottom
|
1499
|
+
@scalar = @scalar.to_f unless @scalar.nil? || @scalar.empty?
|
1500
|
+
@scalar = 1 unless @scalar.kind_of? Numeric
|
1501
|
+
@scalar = @scalar.to_int if (@scalar.to_int == @scalar)
|
1502
|
+
|
1503
|
+
@numerator ||= UNITY_ARRAY
|
1504
|
+
@denominator ||= UNITY_ARRAY
|
1505
|
+
@numerator = top.scan(RubyUnits::Unit.unit_match_regex).delete_if { |x| x.empty? }.compact if top
|
1506
|
+
@denominator = bottom.scan(RubyUnits::Unit.unit_match_regex).delete_if { |x| x.empty? }.compact if bottom
|
1507
|
+
|
1508
|
+
# eliminate all known terms from this string. This is a quick check to see if the passed unit
|
1509
|
+
# contains terms that are not defined.
|
1510
|
+
used = "#{top} #{bottom}".to_s.gsub(RubyUnits::Unit.unit_match_regex, '').gsub(/[\d\*, "'_^\/\$]/, '')
|
1511
|
+
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") unless used.empty?
|
1512
|
+
|
1513
|
+
@numerator = @numerator.map do |item|
|
1514
|
+
@@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]]
|
1515
|
+
end.flatten.compact.delete_if { |x| x.empty? }
|
1516
|
+
|
1517
|
+
@denominator = @denominator.map do |item|
|
1518
|
+
@@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]]
|
1519
|
+
end.flatten.compact.delete_if { |x| x.empty? }
|
1520
|
+
|
1521
|
+
@numerator = UNITY_ARRAY if @numerator.empty?
|
1522
|
+
@denominator = UNITY_ARRAY if @denominator.empty?
|
1523
|
+
return self
|
1524
|
+
end
|
1540
1525
|
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
@@UNIT_REGEX ||= @@UNIT_MAP.keys.sort_by {|unit_name| [unit_name.length, unit_name]}.reverse.join('|')
|
1547
|
-
end
|
1526
|
+
# return an array of base units
|
1527
|
+
# @return [Array]
|
1528
|
+
def self.base_units
|
1529
|
+
return @@base_units ||= @@definitions.dup.delete_if { |_, defn| !defn.base? }.keys.map { |u| RubyUnits::Unit.new(u) }
|
1530
|
+
end
|
1548
1531
|
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1532
|
+
# parse a string consisting of a number and a unit string
|
1533
|
+
# @param [String] string
|
1534
|
+
# @return [Array] consisting of [Numeric, "unit"]
|
1535
|
+
# @private
|
1536
|
+
def self.parse_into_numbers_and_units(string)
|
1537
|
+
# scientific notation.... 123.234E22, -123.456e-10
|
1538
|
+
sci = %r{[+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*}
|
1539
|
+
# rational numbers.... -1/3, 1/5, 20/100
|
1540
|
+
rational = %r{[+-]?\d+\/\d+}
|
1541
|
+
# complex numbers... -1.2+3i, +1.2-3.3i
|
1542
|
+
complex = %r{#{sci}{2,2}i}
|
1543
|
+
anynumber = %r{(?:(#{complex}|#{rational}|#{sci})\b)?\s?([^\d\.].*)?}
|
1544
|
+
num, unit = string.scan(anynumber).first
|
1545
|
+
|
1546
|
+
return [case num
|
1547
|
+
when NilClass
|
1548
|
+
1
|
1549
|
+
when complex
|
1550
|
+
if num.respond_to?(:to_c)
|
1551
|
+
num.to_c
|
1552
|
+
else
|
1553
|
+
#:nocov_19:
|
1554
|
+
Complex(*num.scan(/(#{sci})(#{sci})i/).flatten.map { |n| n.to_i })
|
1555
|
+
#:nocov_19:
|
1556
|
+
end
|
1557
|
+
when rational
|
1558
|
+
Rational(*num.split("/").map { |x| x.to_i })
|
1559
|
+
else
|
1560
|
+
num.to_f
|
1561
|
+
end, unit.to_s.strip]
|
1562
|
+
end
|
1555
1563
|
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1564
|
+
# return a fragment of a regex to be used for matching units or reconstruct it if hasn't been used yet.
|
1565
|
+
# Unit names are reverse sorted by length so the regexp matcher will prefer longer and more specific names
|
1566
|
+
# @return [String]
|
1567
|
+
# @private
|
1568
|
+
def self.unit_regex
|
1569
|
+
@@UNIT_REGEX ||= @@UNIT_MAP.keys.sort_by { |unit_name| [unit_name.length, unit_name] }.reverse.join('|')
|
1570
|
+
end
|
1562
1571
|
|
1563
|
-
|
1564
|
-
|
1572
|
+
# return a regex used to match units
|
1573
|
+
# @return [RegExp]
|
1574
|
+
# @private
|
1575
|
+
def self.unit_match_regex
|
1576
|
+
@@UNIT_MATCH_REGEX ||= /(#{RubyUnits::Unit.prefix_regex})*?(#{RubyUnits::Unit.unit_regex})\b/
|
1577
|
+
end
|
1578
|
+
|
1579
|
+
# return a regexp fragment used to match prefixes
|
1580
|
+
# @return [String]
|
1581
|
+
# @private
|
1582
|
+
def self.prefix_regex
|
1583
|
+
return @@PREFIX_REGEX ||= @@PREFIX_MAP.keys.sort_by { |prefix| [prefix.length, prefix] }.reverse.join('|')
|
1584
|
+
end
|
1585
|
+
|
1586
|
+
def self.temp_regex
|
1587
|
+
@@TEMP_REGEX ||= Regexp.new "(?:#{
|
1565
1588
|
temp_units=%w(tempK tempC tempF tempR degK degC degF degR)
|
1566
|
-
aliases=temp_units.map{|unit| d=Unit.definition(unit); d && d.aliases}.flatten.compact
|
1567
|
-
regex_str= aliases.empty? ? '(?!x)x' : aliases.join('|')
|
1589
|
+
aliases =temp_units.map { |unit| d=RubyUnits::Unit.definition(unit); d && d.aliases }.flatten.compact
|
1590
|
+
regex_str = aliases.empty? ? '(?!x)x' : aliases.join('|')
|
1568
1591
|
regex_str
|
1569
|
-
|
1570
|
-
|
1592
|
+
})"
|
1593
|
+
end
|
1571
1594
|
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1595
|
+
# inject a definition into the internal array and set it up for use
|
1596
|
+
# @private
|
1597
|
+
def self.use_definition(definition)
|
1598
|
+
@@UNIT_MATCH_REGEX = nil #invalidate the unit match regex
|
1599
|
+
@@TEMP_REGEX = nil #invalidate the temp regex
|
1600
|
+
if definition.prefix?
|
1601
|
+
@@PREFIX_VALUES[definition.name] = definition.scalar
|
1602
|
+
definition.aliases.each { |_alias| @@PREFIX_MAP[_alias] = definition.name }
|
1603
|
+
@@PREFIX_REGEX = nil #invalidate the prefix regex
|
1604
|
+
else
|
1605
|
+
@@UNIT_VALUES[definition.name] = {}
|
1606
|
+
@@UNIT_VALUES[definition.name][:scalar] = definition.scalar
|
1607
|
+
@@UNIT_VALUES[definition.name][:numerator] = definition.numerator if definition.numerator
|
1608
|
+
@@UNIT_VALUES[definition.name][:denominator] = definition.denominator if definition.denominator
|
1609
|
+
definition.aliases.each { |_alias| @@UNIT_MAP[_alias] = definition.name }
|
1610
|
+
@@UNIT_REGEX = nil #invalidate the unit regex
|
1611
|
+
end
|
1588
1612
|
end
|
1589
1613
|
end
|
1590
|
-
|
1591
1614
|
end
|