ruby-units 1.4.3 → 1.4.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://secure.travis-ci.org/olbrich/ruby-units.png)](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
|