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 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. avoid using spaces in unit names
35
+ 3. spaces in units are allowed, but ones like '11/m' will be recognized as '11 1/m'.
38
36
 
39
- ## Unit compatability:
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.3
1
+ 1.4.4
data/lib/ruby-units.rb CHANGED
@@ -1,14 +1,9 @@
1
1
  $LOAD_PATH << File.dirname(__FILE__)
2
- require "ruby_units/version"
3
- require "ruby_units/definition"
4
- require "ruby_units/cache"
5
- require 'ruby_units/array'
6
- require 'ruby_units/date'
7
- require 'ruby_units/time'
8
- require 'ruby_units/math'
9
- require 'ruby_units/numeric'
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
- require "ruby_units/version"
3
- require "ruby_units/definition"
4
- require "ruby_units/cache"
5
- require 'ruby_units/array'
6
- require 'ruby_units/date'
7
- require 'ruby_units/time'
8
- require 'ruby_units/math'
9
- require 'ruby_units/numeric'
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
@@ -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
@@ -1,4 +1,4 @@
1
- class Unit < Numeric
1
+ class RubyUnits::Unit < Numeric
2
2
  @@cached_units = {}
3
3
 
4
4
  class Cache
@@ -17,4 +17,4 @@ class Unit < Numeric
17
17
  end
18
18
 
19
19
  end
20
- end
20
+ end
@@ -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
@@ -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
@@ -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'
@@ -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
@@ -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
@@ -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)
@@ -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
- class Unit < Numeric
36
- VERSION = Unit::Version::STRING
37
- @@definitions = {}
38
- @@PREFIX_VALUES = {}
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
- @@PREFIX_REGEX = nil
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
- @@definitions.each do |name, definition|
142
- self.use_definition(definition)
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
- # determine if a unit is already defined
151
- # @param [String] unit
152
- # @return [Boolean]
153
- def self.defined?(unit)
154
- self.definitions.values.any? {|d| d.aliases.include?(unit)}
155
- end
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
- # return the unit definition for a unit
158
- # @param [String] unit
159
- # @return [Unit::Definition, nil]
160
- def self.definition(_unit)
161
- unit = (_unit =~ /^<.+>$/) ? _unit : "<#{_unit}>"
162
- return @@definitions[unit]
163
- end
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
- # return a list of all defined units
166
- # @return [Array]
167
- def self.definitions
168
- return @@definitions
169
- end
166
+ # return a list of all defined units
167
+ # @return [Array]
168
+ def self.definitions
169
+ return @@definitions
170
+ end
170
171
 
171
- # @param [Unit::Definition|String] unit_definition
172
- # @param [Block] block
173
- # @return [Unit::Definition]
174
- # @raise [ArgumentError] when passed a non-string if using the block form
175
- # Unpack a unit definition and add it to the array of defined units
176
- #
177
- # @example Block form
178
- # Unit.define('foobar') do |foobar|
179
- # foobar.definition = Unit("1 baz")
180
- # end
181
- #
182
- # @example Unit::Definition form
183
- # unit_definition = Unit::Definition.new("foobar") {|foobar| foobar.definition = Unit("1 baz")}
184
- # Unit.define(unit_definition)
185
- def self.define(unit_definition, &block)
186
- if block_given?
187
- raise ArgumentError, "When using the block form of Unit.define, pass the name of the unit" unless unit_definition.instance_of?(String)
188
- unit_definition = Unit::Definition.new(unit_definition, &block)
189
- end
190
- Unit.definitions[unit_definition.name] = unit_definition
191
- Unit.use_definition(unit_definition)
192
- return unit_definition
193
- end
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
- # @param [String] name Name of unit to redefine
196
- # @param [Block] block
197
- # @raise [ArgumentError] if a block is not given
198
- # @yield [Unit::Definition]
199
- # @return (see Unit.define)
200
- # Get the definition for a unit and allow it to be redefined
201
- def self.redefine!(name, &block)
202
- raise ArgumentError, "A block is required to redefine a unit" unless block_given?
203
- unit_definition = self.definition(name)
204
- yield unit_definition
205
- self.define(unit_definition)
206
- end
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
- # @param [String] name of unit to undefine
209
- # @return (see Unit.setup)
210
- # Undefine a unit. Will not raise an exception for unknown units.
211
- def self.undefine!(unit)
212
- @@definitions.delete("<#{unit}>")
213
- Unit.setup
214
- end
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
- include Comparable
217
+ include Comparable
217
218
 
218
- # @return [Numeric]
219
- attr_accessor :scalar
219
+ # @return [Numeric]
220
+ attr_accessor :scalar
220
221
 
221
- # @return [Array]
222
- attr_accessor :numerator
222
+ # @return [Array]
223
+ attr_accessor :numerator
223
224
 
224
- # @return [Array]
225
- attr_accessor :denominator
225
+ # @return [Array]
226
+ attr_accessor :denominator
226
227
 
227
- # @return [Integer]
228
- attr_accessor :signature
228
+ # @return [Integer]
229
+ attr_accessor :signature
229
230
 
230
- # @return [Numeric]
231
- attr_accessor :base_scalar
231
+ # @return [Numeric]
232
+ attr_accessor :base_scalar
232
233
 
233
- # @return [Array]
234
- attr_accessor :base_numerator
234
+ # @return [Array]
235
+ attr_accessor :base_numerator
235
236
 
236
- # @return [Array]
237
- attr_accessor :base_denominator
237
+ # @return [Array]
238
+ attr_accessor :base_denominator
238
239
 
239
- # @return [String]
240
- attr_accessor :output
240
+ # @return [String]
241
+ attr_accessor :output
241
242
 
242
- # @return [String]
243
- attr_accessor :unit_name
243
+ # @return [String]
244
+ attr_accessor :unit_name
244
245
 
245
- # needed to make complex units play nice -- otherwise not detected as a complex_generic
246
- # @param [Class]
247
- # @return [Boolean]
248
- def kind_of?(klass)
249
- self.scalar.kind_of?(klass)
250
- end
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
- # Used to copy one unit to another
253
- # @param [Unit] from Unit to copy definition from
254
- # @return [Unit]
255
- def copy(from)
256
- @scalar = from.scalar
257
- @numerator = from.numerator
258
- @denominator = from.denominator
259
- @is_base = from.is_base?
260
- @signature = from.signature
261
- @base_scalar = from.base_scalar
262
- @unit_name = from.unit_name rescue nil
263
- return self
264
- end
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
- if RUBY_VERSION < "1.9"
267
- # :nocov_19:
267
+ if RUBY_VERSION < "1.9"
268
+ # :nocov_19:
268
269
 
269
- # a list of properties to emit to yaml
270
- # @return [Array]
271
- def to_yaml_properties
272
- %w{@scalar @numerator @denominator @signature @base_scalar}
273
- end
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
- # basically a copy of the basic to_yaml. Needed because otherwise it ends up coercing the object to a string
276
- # before YAML'izing it.
277
- # @param [Hash] opts
278
- # @return [String]
279
- def to_yaml( opts = {} )
280
- YAML::quick_emit( object_id, opts ) do |out|
281
- out.map( taguri, to_yaml_style ) do |map|
282
- for m in to_yaml_properties do
283
- map.add( m[1..-1], instance_variable_get( m ) )
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
- # Create a new Unit object. Can be initialized using a String, a Hash, an Array, Time, DateTime
292
- #
293
- # @example Valid options include:
294
- # "5.6 kg*m/s^2"
295
- # "5.6 kg*m*s^-2"
296
- # "5.6 kilogram*meter*second^-2"
297
- # "2.2 kPa"
298
- # "37 degC"
299
- # "1" -- creates a unitless constant with value 1
300
- # "GPa" -- creates a unit with scalar 1 with units 'GPa'
301
- # "6'4\""" -- recognized as 6 feet + 4 inches
302
- # "8 lbs 8 oz" -- recognized as 8 lbs + 8 ounces
303
- # [1, 'kg']
304
- # {:scalar => 1, :numerator=>'kg'}
305
- #
306
- # @param [Unit,String,Hash,Array,Date,Time,DateTime] options
307
- # @return [Unit]
308
- # @raise [ArgumentError] if absolute value of a temperature is less than absolute zero
309
- # @raise [ArgumentError] if no unit is specified
310
- # @raise [ArgumentError] if an invalid unit is specified
311
- def initialize(*options)
312
- @scalar = nil
313
- @base_scalar = nil
314
- @unit_name = nil
315
- @signature = nil
316
- @output = { }
317
- raise ArgumentError, "Invalid Unit Format" if options[0].nil?
318
- if options.size == 2
319
- # options[0] is the scalar
320
- # options[1] is a unit string
321
- begin
322
- cached = @@cached_units[options[1]] * options[0]
323
- copy(cached)
324
- rescue
325
- initialize("#{options[0]} #{(options[1].units rescue options[1])}")
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
- return
328
- end
329
- if options.size == 3
330
- options[1] = options[1].join if options[1].kind_of?(Array)
331
- options[2] = options[2].join if options[2].kind_of?(Array)
332
- begin
333
- cached = @@cached_units["#{options[1]}/#{options[2]}"] * options[0]
334
- copy(cached)
335
- rescue
336
- initialize("#{options[0]} #{options[1]}/#{options[2]}")
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
- case options[0]
342
- when Unit
343
- copy(options[0])
344
- return
345
- when Hash
346
- @scalar = options[0][:scalar] || 1
347
- @numerator = options[0][:numerator] || UNITY_ARRAY
348
- @denominator = options[0][:denominator] || UNITY_ARRAY
349
- @signature = options[0][:signature]
350
- when Array
351
- initialize(*options[0])
352
- return
353
- when Numeric
354
- @scalar = options[0]
355
- @numerator = @denominator = UNITY_ARRAY
356
- when Time
357
- @scalar = options[0].to_f
358
- @numerator = ['<second>']
359
- @denominator = UNITY_ARRAY
360
- when DateTime, Date
361
- @scalar = options[0].ajd
362
- @numerator = ['<day>']
363
- @denominator = UNITY_ARRAY
364
- when /^\s*$/
365
- raise ArgumentError, "No Unit Specified"
366
- when String
367
- parse(options[0])
368
- else
369
- raise ArgumentError, "Invalid Unit Format"
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?(.+)?|&plusmn;|\+\/-/)
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?(.+)?|&plusmn;|\+\/-/)
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
- # @todo: figure out how to handle :counting units. This method should probably return :counting instead of :unitless for 'each'
388
- # return the kind of the unit (:mass, :length, etc...)
389
- # @return [Symbol]
390
- def kind
391
- return @@KINDS[self.signature]
392
- end
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
- # @private
395
- # @return [Hash]
396
- def self.cached
397
- return @@cached_units
398
- end
395
+ # @private
396
+ # @return [Hash]
397
+ def self.cached
398
+ return @@cached_units
399
+ end
399
400
 
400
- # @private
401
- # @return [true]
402
- def self.clear_cache
403
- @@cached_units = {}
404
- @@base_unit_cache = {}
405
- Unit.new(1)
406
- return true
407
- end
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
- # @private
410
- # @return [Hash]
411
- def self.base_unit_cache
412
- return @@base_unit_cache
413
- end
410
+ # @private
411
+ # @return [Hash]
412
+ def self.base_unit_cache
413
+ return @@base_unit_cache
414
+ end
414
415
 
415
- # @example parse strings
416
- # "1 minute in seconds"
417
- # @param [String] input
418
- # @return [Unit]
419
- def self.parse(input)
420
- first, second = input.scan(/(.+)\s(?:in|to|as)\s(.+)/i).first
421
- return second.nil? ? first.unit : first.unit.convert_to(second)
422
- end
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
- # @return [Unit]
425
- def to_unit
426
- self
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
- cached = ((@@base_unit_cache[self.units] * self.scalar) rescue nil)
467
- return cached if cached
430
+ alias :unit :to_unit
468
431
 
469
- num = []
470
- den = []
471
- q = 1
472
- for unit in @numerator.compact do
473
- if @@PREFIX_VALUES[unit]
474
- q *= @@PREFIX_VALUES[unit]
475
- else
476
- q *= @@UNIT_VALUES[unit][:scalar] if @@UNIT_VALUES[unit]
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
- for unit in @denominator.compact do
482
- if @@PREFIX_VALUES[unit]
483
- q /= @@PREFIX_VALUES[unit]
484
- else
485
- q /= @@UNIT_VALUES[unit][:scalar] if @@UNIT_VALUES[unit]
486
- den << @@UNIT_VALUES[unit][:numerator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:numerator]
487
- num << @@UNIT_VALUES[unit][:denominator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:denominator]
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
- num = num.flatten.compact
492
- den = den.flatten.compact
493
- num = UNITY_ARRAY if num.empty?
494
- base= Unit.new(Unit.eliminate_terms(q,num,den))
495
- @@base_unit_cache[self.units]=base
496
- return base * @scalar
497
- end
498
- alias :base :to_base
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
- raise "unhandled case"
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
- else
545
- out = case @scalar
546
- when Rational
547
- "#{@scalar} #{self.units}"
483
+ end
484
+ for unit in @denominator.compact do
485
+ if @@PREFIX_VALUES[unit]
486
+ q /= @@PREFIX_VALUES[unit]
548
487
  else
549
- "#{'%g' % @scalar} #{self.units}"
550
- end.strip
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
- @output[target_units] = out
553
- return out
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
- # Normally pretty prints the unit, but if you really want to see the guts of it, pass ':dump'
558
- # @deprecated
559
- # @return [String]
560
- def inspect(option=nil)
561
- return super() if option == :dump
562
- return self.to_s
563
- end
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
- # true if unit is a 'temperature', false if a 'degree' or anything else
566
- # @return [Boolean]
567
- # @todo use unit definition to determine if it's a temperature instead of a regex
568
- def is_temperature?
569
- return self.is_degree? && (!(@@UNIT_MAP[self.units] =~ /temp[CFRK]/).nil?)
570
- end
571
- alias :temperature? :is_temperature?
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
- # true if a degree unit or equivalent.
574
- # @return [Boolean]
575
- def is_degree?
576
- return self.kind == :temperature
577
- end
578
- alias :degree? :is_degree?
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
- # returns true if no associated units
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
- # Compare two Unit objects. Throws an exception if they are not of compatible types.
596
- # Comparisons are done based on the value of the unit in base SI units.
597
- # @param [Object] other
598
- # @return [-1|0|1|nil]
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
- # Compare Units for equality
619
- # this is necessary mostly for Complex units. Complex units do not have a <=> operator
620
- # so we define this one here so that we can properly check complex units for equality.
621
- # Units of incompatible types are not equal, except when they are both zero and neither is a temperature
622
- # Equality checks can be tricky since round off errors may make essentially equivalent units
623
- # appear to be different.
624
- # @param [Object] other
625
- # @return [Boolean]
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
- # check to see if units are compatible, but not the scalar part
644
- # this check is done by comparing signatures for performance reasons
645
- # if passed a string, it will create a unit object with the string and then do the comparison
646
- # @example this permits a syntax like:
647
- # unit =~ "mm"
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
- alias :compatible? :=~
667
- alias :compatible_with? :=~
668
-
669
- # Compare two units. Returns true if quantities and units match
670
- # @example
671
- # Unit("100 cm") === Unit("100 cm") # => true
672
- # Unit("100 cm") === Unit("1 m") # => false
673
- # @param [Object] other
674
- # @return [Boolean]
675
- def ===(other)
676
- case other
677
- when Unit
678
- (self.scalar == other.scalar) && (self.units == other.units)
679
- else
680
- begin
681
- x,y = coerce(other)
682
- return x === y
683
- rescue ArgumentError
684
- return false
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
- alias :same? :===
690
- alias :same_as? :===
691
-
692
- # Add two units together. Result is same units as receiver and scalar and base_scalar are updated appropriately
693
- # throws an exception if the units are not compatible.
694
- # It is possible to add Time objects to units of time
695
- # @param [Object] other
696
- # @return [Unit]
697
- # @raise [ArgumentError] when two temperatures are added
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
- when self.zero?
705
- other.dup
706
- when self =~ other
707
- raise ArgumentError, "Cannot add two temperatures" if ([self, other].all? {|x| x.is_temperature?})
708
- if [self, other].any? {|x| x.is_temperature?}
709
- if self.is_temperature?
710
- Unit.new(:scalar => (self.scalar + other.convert_to(self.temperature_scale).scalar), :numerator => @numerator, :denominator=>@denominator, :signature => @signature)
711
- else
712
- Unit.new(:scalar => (other.scalar + self.convert_to(other.temperature_scale).scalar), :numerator => other.numerator, :denominator=>other.denominator, :signature => other.signature)
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
- @q ||= ((@@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar) rescue (self.units.unit.to_base.scalar))
716
- Unit.new(:scalar=>(self.base_scalar + other.base_scalar)*@q, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
717
- end
718
- else
719
- raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
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
- # Subtract two units. Result is same units as receiver and scalar and base_scalar are updated appropriately
730
- # @param [Numeric] other
731
- # @return [Unit]
732
- # @raise [ArgumentError] when subtracting a temperature from a degree
733
- # @raise [ArgumentError] when units are not compatible
734
- # @raise [ArgumentError] when subtracting a fixed time from a time span
735
- def -(other)
736
- case other
737
- when Unit
738
- case
739
- when self.zero?
740
- if other.zero?
741
- other.dup * -1 # preserve Units class
742
- else
743
- -other.dup
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
- when self =~ other
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 [self, other].all? {|x| x.is_temperature?}
748
- Unit.new(:scalar => (self.base_scalar - other.base_scalar), :numerator => KELVIN, :denominator => UNITY_ARRAY, :signature => @signature).convert_to(self.temperature_scale)
749
- when self.is_temperature?
750
- Unit.new(:scalar => (self.base_scalar - other.base_scalar), :numerator => ['<tempK>'], :denominator => UNITY_ARRAY, :signature => @signature).convert_to(self)
751
- when other.is_temperature?
752
- raise ArgumentError, "Cannot subtract a temperature from a differential degree unit"
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
- @q ||= ((@@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar) rescue (self.units.unit.scalar/self.units.unit.to_base.scalar))
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
- raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
759
- end
760
- when Time
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
- # Multiply two units.
769
- # @param [Numeric] other
770
- # @return [Unit]
771
- # @raise [ArgumentError] when attempting to multiply two temperatures
772
- def *(other)
773
- case other
774
- when Unit
775
- raise ArgumentError, "Cannot multiply by temperatures" if [other,self].any? {|x| x.is_temperature?}
776
- opts = Unit.eliminate_terms(@scalar*other.scalar, @numerator + other.numerator ,@denominator + other.denominator)
777
- opts.merge!(:signature => @signature + other.signature)
778
- return Unit.new(opts)
779
- when Numeric
780
- return Unit.new(:scalar=>@scalar*other, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
781
- else
782
- x,y = coerce(other)
783
- return x * y
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
- # Divide two units.
788
- # Throws an exception if divisor is 0
789
- # @param [Numeric] other
790
- # @return [Unit]
791
- # @raise [ZeroDivisionError] if divisor is zero
792
- # @raise [ArgumentError] if attempting to divide a temperature by another temperature
793
- def /(other)
794
- case other
795
- when Unit
796
- raise ZeroDivisionError if other.zero?
797
- raise ArgumentError, "Cannot divide with temperatures" if [other,self].any? {|x| x.is_temperature?}
798
- opts = Unit.eliminate_terms(@scalar/other.scalar, @numerator + other.denominator ,@denominator + other.numerator)
799
- opts.merge!(:signature=> @signature - other.signature)
800
- return Unit.new(opts)
801
- when Numeric
802
- raise ZeroDivisionError if other.zero?
803
- return Unit.new(:scalar=>@scalar/other, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
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
- # divide two units and return quotient and remainder
811
- # when both units are in the same units we just use divmod on the raw scalars
812
- # otherwise we use the scalar of the base unit which will be a float
813
- # @param [Object] other
814
- # @return [Array]
815
- def divmod(other)
816
- raise ArgumentError, "Incompatible Units" unless self =~ other
817
- if self.units == other.units
818
- return self.scalar.divmod(other.scalar)
819
- else
820
- return self.to_base.scalar.divmod(other.to_base.scalar)
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
- # Exponentiate. Only takes integer powers.
832
- # Note that anything raised to the power of 0 results in a Unit object with a scalar of 1, and no units.
833
- # Throws an exception if exponent is not an integer.
834
- # Ideally this routine should accept a float for the exponent
835
- # It should then convert the float to a rational and raise the unit by the numerator and root it by the denominator
836
- # but, sadly, floats can't be converted to rationals.
837
- #
838
- # For now, if a rational is passed in, it will be used, otherwise we are stuck with integers and certain floats < 1
839
- # @param [Numeric] other
840
- # @return [Unit]
841
- # @raise [ArgumentError] when raising a temperature to a power
842
- # @raise [ArgumentError] when n not in the set integers from (1..9)
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
- # returns the unit raised to the n-th power
870
- # @param [Integer] n
871
- # @return [Unit]
872
- # @raise [ArgumentError] when attempting to raise a temperature to a power
873
- # @raise [ArgumentError] when n is not an integer
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
- # Calculates the n-th root of a unit
888
- # if n < 0, returns 1/unit^(1/n)
889
- # @param [Integer] n
890
- # @return [Unit]
891
- # @raise [ArgumentError] when attemptint to take the root of a temperature
892
- # @raise [ArgumentError] when n is not an integer
893
- # @raise [ArgumentError] when n is 0
894
- def root(n)
895
- raise ArgumentError, "Cannot take the root of a temperature" if self.is_temperature?
896
- raise ArgumentError, "Exponent must an Integer" unless n.kind_of?(Integer)
897
- raise ArgumentError, "0th root undefined" if n.zero?
898
- return self if n == 1
899
- return self.root(n.abs).inverse if n < 0
900
-
901
- vec = self.unit_signature_vector
902
- vec=vec.map {|x| x % n}
903
- raise ArgumentError, "Illegal root" unless vec.max == 0
904
- num = @numerator.dup
905
- den = @denominator.dup
906
-
907
- for item in @numerator.uniq do
908
- x = num.find_all {|i| i==item}.size
909
- r = ((x/n)*(n-1)).to_int
910
- r.times {|y| num.delete_at(num.index(item))}
911
- end
912
-
913
- for item in @denominator.uniq do
914
- x = den.find_all {|i| i==item}.size
915
- r = ((x/n)*(n-1)).to_int
916
- r.times {|y| den.delete_at(den.index(item))}
917
- end
918
- q = @scalar < 0 ? (-1)**Rational(1,n) * (@scalar.abs)**Rational(1,n) : @scalar**Rational(1,n)
919
- return Unit.new(:scalar=>q,:numerator=>num,:denominator=>den)
920
- end
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
- # returns inverse of Unit (1/unit)
923
- # @return [Unit]
924
- def inverse
925
- return Unit("1") / self
926
- end
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
- # convert to a specified unit string or to the same units as another Unit
929
- #
930
- # unit.convert_to "kg" will covert to kilograms
931
- # unit1.convert_to unit2 converts to same units as unit2 object
932
- #
933
- # To convert a Unit object to match another Unit object, use:
934
- # unit1 >>= unit2
935
- #
936
- # Special handling for temperature conversions is supported. If the Unit object is converted
937
- # from one temperature unit to another, the proper temperature offsets will be used.
938
- # Supports Kelvin, Celsius, Fahrenheit, and Rankine scales.
939
- #
940
- # @note If temperature is part of a compound unit, the temperature will be treated as a differential
941
- # and the units will be scaled appropriately.
942
- # @param [Object] other
943
- # @return [Unit]
944
- # @raise [ArgumentError] when attempting to convert a degree to a temperature
945
- # @raise [ArgumentError] when target unit is unknown
946
- # @raise [ArgumentError] when target unit is incompatible
947
- def convert_to(other)
948
- return self if other.nil?
949
- return self if TrueClass === other
950
- return self if FalseClass === other
951
- if (Unit === other && other.is_temperature?) || (String === other && other =~ /temp[CFRK]/)
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
- q= case @@UNIT_MAP[target_unit]
968
- when '<tempC>'
969
- @base_scalar - 273.15
970
- when '<tempK>'
971
- @base_scalar
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
- return Unit.new("#{q} #{target_unit}")
978
- else
979
- case other
980
- when Unit
981
- return self if other.units == self.units
982
- target = other
983
- when String
984
- target = Unit.new(other)
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
- raise ArgumentError, "Unknown target units"
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
- raise ArgumentError, "Incompatible Units" unless self =~ target
989
- _numerator1 = @numerator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[i][:scalar] }.compact
990
- _denominator1 = @denominator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[i][:scalar] }.compact
991
- _numerator2 = target.numerator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[x][:scalar] }.compact
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
- q = @scalar * ( (_numerator1 + _denominator2).inject(1) {|product,n| product*n} ) /
995
- ( (_numerator2 + _denominator1).inject(1) {|product,n| product*n} )
996
- return Unit.new(:scalar=>q, :numerator=>target.numerator, :denominator=>target.denominator, :signature => target.signature)
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
- # converts the unit back to a complex if it is unitless. Otherwise raises an exception
1011
- # @return [Complex]
1012
- # @raise [RuntimeError] when not unitless
1013
- def to_c
1014
- return Complex(@scalar) if self.unitless?
1015
- raise RuntimeError, "Cannot convert '#{self.to_s}' to Complex unless unitless. Use Unit#scalar"
1016
- end
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
- # if unitless, returns an int, otherwise raises an error
1019
- # @return [Integer]
1020
- # @raise [RuntimeError] when not unitless
1021
- def to_i
1022
- return @scalar.to_int if self.unitless?
1023
- raise RuntimeError, "Cannot convert '#{self.to_s}' to Integer unless unitless. Use Unit#scalar"
1024
- end
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
- # Returns string formatted for json
1036
- # @return [String]
1037
- def as_json(*args)
1038
- to_s
1039
- end
1033
+ alias :to_int :to_i
1040
1034
 
1041
- # returns the 'unit' part of the Unit object without the scalar
1042
- # @return [String]
1043
- def units
1044
- return "" if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY
1045
- return @unit_name unless @unit_name.nil?
1046
- output_numerator = []
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
- if @denominator == UNITY_ARRAY
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
- else
1066
- while defn = Unit.definition(den.shift) do
1067
- if defn && defn.prefix?
1068
- output_denominator << defn.display_name + Unit.definition(den.shift).display_name
1069
- else
1070
- output_denominator << defn.display_name
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
- on = output_numerator.uniq.
1076
- map {|x| [x, output_numerator.count(x)]}.
1077
- map {|element, power| ("#{element}".strip + (power > 1 ? "^#{power}" : ''))}
1078
- od = output_denominator.uniq.
1079
- map {|x| [x, output_denominator.count(x)]}.
1080
- map {|element, power| ("#{element}".strip + (power > 1 ? "^#{power}" : ''))}
1081
- out = "#{on.join('*')}#{od.empty? ? '': '/' + od.join('*')}".strip
1082
- @unit_name = out unless self.kind == :temperature
1083
- return out
1084
- end
1085
-
1086
- # negates the scalar of the Unit
1087
- # @return [Numeric,Unit]
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
- # absolute value of a unit
1094
- # @return [Numeric,Unit]
1095
- def abs
1096
- return @scalar.abs if self.unitless?
1097
- return Unit.new(@scalar.abs, @numerator, @denominator)
1098
- end
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
- # ceil of a unit
1101
- # @return [Numeric,Unit]
1102
- def ceil
1103
- return @scalar.ceil if self.unitless?
1104
- return Unit.new(@scalar.ceil, @numerator, @denominator)
1105
- end
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
- # @return [Numeric,Unit]
1108
- def floor
1109
- return @scalar.floor if self.unitless?
1110
- return Unit.new(@scalar.floor, @numerator, @denominator)
1111
- end
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
- if RUBY_VERSION < '1.9'
1110
+ # ceil of a unit
1114
1111
  # @return [Numeric,Unit]
1115
- def round
1116
- return @scalar.round if self.unitless?
1117
- return Unit.new(@scalar.round, @numerator, @denominator)
1112
+ def ceil
1113
+ return @scalar.ceil if self.unitless?
1114
+ return RubyUnits::Unit.new(@scalar.ceil, @numerator, @denominator)
1118
1115
  end
1119
- else
1116
+
1120
1117
  # @return [Numeric,Unit]
1121
- def round(ndigits = 0)
1122
- return @scalar.round(ndigits) if self.unitless?
1123
- return Unit.new(@scalar.round(ndigits), @numerator, @denominator)
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
- # @return [Numeric, Unit]
1128
- def truncate
1129
- return @scalar.truncate if self.unitless?
1130
- return Unit.new(@scalar.truncate, @numerator, @denominator)
1131
- end
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
- # returns next unit in a range. '1 mm'.unit.succ #=> '2 mm'.unit
1134
- # only works when the scalar is an integer
1135
- # @return [Unit]
1136
- # @raise [ArgumentError] when scalar is not equal to an integer
1137
- def succ
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
- # Tries to make a Time object from current unit. Assumes the current unit hold the duration in seconds from the epoch.
1153
- # @return [Time]
1154
- def to_time
1155
- return Time.at(self)
1156
- end
1157
- alias :time :to_time
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
- # convert a duration to a DateTime. This will work so long as the duration is the duration from the zero date
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
- # @return [Date]
1167
- def to_date
1168
- return Date.new0(self.convert_to('d').scalar)
1169
- end
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
- # true if scalar is zero
1172
- # @return [Boolean]
1173
- def zero?
1174
- return self.base_scalar.zero?
1175
- end
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
- # @example '5 min'.unit.ago
1178
- # @return [Unit]
1179
- def ago
1180
- return self.before
1181
- end
1169
+ alias :time :to_time
1182
1170
 
1183
- # @example '5 min'.before(time)
1184
- # @return [Unit]
1185
- def before(time_point = ::Time.now)
1186
- case time_point
1187
- when Time, Date, DateTime
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
- end
1193
- alias :before_now :before
1194
-
1195
- # @example 'min'.since(time)
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
- # @example 'min'.until(time)
1211
- # @param [Time, Date, DateTime] time_point
1212
- # @return [Unit]
1213
- def until(time_point)
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
- # @example '5 min'.from(time)
1225
- # @param [Time, Date, DateTime] time_point
1226
- # @return [Time, Date, DateTime]
1227
- # @raise [ArgumentError] when passed argument is not a Time, Date, or DateTime
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
- end
1236
- alias :after :from
1237
- alias :from_now :from
1238
-
1239
- # automatically coerce objects to units when possible
1240
- # if an object defines a 'to_unit' method, it will be coerced using that method
1241
- # @param [Object, #to_unit]
1242
- # @return [Array]
1243
- def coerce(other)
1244
- if other.respond_to? :to_unit
1245
- return [other.to_unit, self]
1246
- end
1247
- case other
1248
- when Unit
1249
- return [other, self]
1250
- else
1251
- return [Unit.new(other), self]
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
- # Protected and Private Functions that should only be called from this class
1256
- protected
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
- # figure out what the scalar part of the base unit for this unit is
1259
- # @return [nil]
1260
- def update_base_scalar
1261
- if self.is_base?
1262
- @base_scalar = @scalar
1263
- @signature = unit_signature
1264
- else
1265
- base = self.to_base
1266
- @base_scalar = base.scalar
1267
- @signature = base.signature
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
- # calculates the unit signature vector used by unit_signature
1272
- # @return [Array]
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
- private
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
- # used by #dup to duplicate a Unit
1294
- # @param [Unit] other
1295
- # @private
1296
- def initialize_copy(other)
1297
- @numerator = other.numerator.dup
1298
- @denominator = other.denominator.dup
1299
- end
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
- # calculates the unit signature id for use in comparing compatible units and simplification
1302
- # the signature is based on a simple classification of units and is based on the following publication
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
- # @param [Numeric] q quantity
1316
- # @param [Array] n numerator
1317
- # @param [Array] d denominator
1318
- # @return [Hash]
1319
- def self.eliminate_terms(q, n, d)
1320
- num = n.dup
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
- k = num[i]
1335
- i += 1
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
- j = 0
1341
- loop do
1342
- break if j > den.size
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
- combined[k] -= 1 unless k.nil? || k == UNITY
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
- # parse a string into a unit object.
1369
- # Typical formats like :
1370
- # "5.6 kg*m/s^2"
1371
- # "5.6 kg*m*s^-2"
1372
- # "5.6 kilogram*meter*second^-2"
1373
- # "2.2 kPa"
1374
- # "37 degC"
1375
- # "1" -- creates a unitless constant with value 1
1376
- # "GPa" -- creates a unit with scalar 1 with units 'GPa'
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 =~ /(\+\/-|&plusmn;)/
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
- @numerator ||= UNITY_ARRAY
1479
- @denominator ||= UNITY_ARRAY
1480
- @numerator = top.scan(Unit.unit_match_regex).delete_if {|x| x.empty?}.compact if top
1481
- @denominator = bottom.scan(Unit.unit_match_regex).delete_if {|x| x.empty?}.compact if bottom
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 =~ /(\+\/-|&plusmn;)/
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
- # eliminate all known terms from this string. This is a quick check to see if the passed unit
1484
- # contains terms that are not defined.
1485
- used = "#{top} #{bottom}".to_s.gsub(Unit.unit_match_regex,'').gsub(/[\d\*, "'_^\/\$]/,'')
1486
- raise( ArgumentError, "'#{passed_unit_string}' Unit not recognized") unless used.empty?
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
- @numerator = @numerator.map do |item|
1489
- @@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]]
1490
- end.flatten.compact.delete_if {|x| x.empty?}
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
- @denominator = @denominator.map do |item|
1493
- @@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]]
1494
- end.flatten.compact.delete_if {|x| x.empty?}
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
- @numerator = UNITY_ARRAY if @numerator.empty?
1497
- @denominator = UNITY_ARRAY if @denominator.empty?
1498
- return self
1499
- end
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
- # return an array of base units
1502
- # @return [Array]
1503
- def self.base_units
1504
- return @@base_units ||= @@definitions.dup.delete_if {|_, defn| !defn.base?}.keys.map {|u| Unit.new(u)}
1505
- end
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
- private
1508
-
1509
- # parse a string consisting of a number and a unit string
1510
- # @param [String] string
1511
- # @return [Array] consisting of [Numeric, "unit"]
1512
- # @private
1513
- def self.parse_into_numbers_and_units(string)
1514
- # scientific notation.... 123.234E22, -123.456e-10
1515
- sci = %r{[+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*}
1516
- # rational numbers.... -1/3, 1/5, 20/100
1517
- rational = %r{[+-]?\d+\/\d+}
1518
- # complex numbers... -1.2+3i, +1.2-3.3i
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
- when rational
1535
- Rational(*num.split("/").map {|x| x.to_i})
1536
- else
1537
- num.to_f
1538
- end, unit.to_s.strip]
1539
- end
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
- # return a fragment of a regex to be used for matching units or reconstruct it if hasn't been used yet.
1542
- # Unit names are reverse sorted by length so the regexp matcher will prefer longer and more specific names
1543
- # @return [String]
1544
- # @private
1545
- def self.unit_regex
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
- # return a regex used to match units
1550
- # @return [RegExp]
1551
- # @private
1552
- def self.unit_match_regex
1553
- @@UNIT_MATCH_REGEX ||= /(#{Unit.prefix_regex})*?(#{Unit.unit_regex})\b/
1554
- end
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
- # return a regexp fragment used to match prefixes
1557
- # @return [String]
1558
- # @private
1559
- def self.prefix_regex
1560
- return @@PREFIX_REGEX ||= @@PREFIX_MAP.keys.sort_by {|prefix| [prefix.length, prefix]}.reverse.join('|')
1561
- end
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
- def self.temp_regex
1564
- @@TEMP_REGEX ||= Regexp.new "(?:#{
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
- end
1592
+ })"
1593
+ end
1571
1594
 
1572
- # inject a definition into the internal array and set it up for use
1573
- # @private
1574
- def self.use_definition(definition)
1575
- @@UNIT_MATCH_REGEX = nil #invalidate the unit match regex
1576
- @@TEMP_REGEX = nil #invalidate the temp regex
1577
- if definition.prefix?
1578
- @@PREFIX_VALUES[definition.name] = definition.scalar
1579
- definition.aliases.each {|_alias| @@PREFIX_MAP[_alias] = definition.name }
1580
- @@PREFIX_REGEX = nil #invalidate the prefix regex
1581
- else
1582
- @@UNIT_VALUES[definition.name] = {}
1583
- @@UNIT_VALUES[definition.name][:scalar] = definition.scalar
1584
- @@UNIT_VALUES[definition.name][:numerator] = definition.numerator if definition.numerator
1585
- @@UNIT_VALUES[definition.name][:denominator] = definition.denominator if definition.denominator
1586
- definition.aliases.each {|_alias| @@UNIT_MAP[_alias] = definition.name}
1587
- @@UNIT_REGEX = nil #invalidate the unit regex
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