ruby-units 2.3.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,27 +1,27 @@
1
1
  require 'date'
2
- # Copyright 2006-2015
3
- # @author Kevin C. Olbrich, Ph.D.
4
- # @see https://github.com/olbrich/ruby-units
5
- #
6
- # @note The accuracy of unit conversions depends on the precision of the conversion factor.
7
- # If you have more accurate estimates for particular conversion factors, please send them
8
- # to me and I will incorporate them into the next release. It is also incumbent on the end-user
9
- # to ensure that the accuracy of any conversions is sufficient for their intended application.
10
- #
11
- # While there are a large number of unit specified in the base package,
12
- # there are also a large number of units that are not included.
13
- # This package covers nearly all SI, Imperial, and units commonly used
14
- # in the United States. If your favorite units are not listed here, file an issue on github.
15
- #
16
- # To add or override a unit definition, add a code block like this..
17
- # @example Define a new unit
18
- # RubyUnits::Unit.define("foobar") do |unit|
19
- # unit.aliases = %w{foo fb foo-bar}
20
- # unit.definition = RubyUnits::Unit.new("1 baz")
21
- # end
22
- #
23
2
  module RubyUnits
24
- class Unit < Numeric
3
+ # Copyright 2006-2022
4
+ # @author Kevin C. Olbrich, Ph.D.
5
+ # @see https://github.com/olbrich/ruby-units
6
+ #
7
+ # @note The accuracy of unit conversions depends on the precision of the conversion factor.
8
+ # If you have more accurate estimates for particular conversion factors, please send them
9
+ # to me and I will incorporate them into the next release. It is also incumbent on the end-user
10
+ # to ensure that the accuracy of any conversions is sufficient for their intended application.
11
+ #
12
+ # While there are a large number of unit specified in the base package,
13
+ # there are also a large number of units that are not included.
14
+ # This package covers nearly all SI, Imperial, and units commonly used
15
+ # in the United States. If your favorite units are not listed here, file an issue on GitHub.
16
+ #
17
+ # To add or override a unit definition, add a code block like this..
18
+ # @example Define a new unit
19
+ # RubyUnits::Unit.define("foobar") do |unit|
20
+ # unit.aliases = %w{foo fb foo-bar}
21
+ # unit.definition = RubyUnits::Unit.new("1 baz")
22
+ # end
23
+ #
24
+ class Unit < ::Numeric
25
25
  @@definitions = {}
26
26
  @@prefix_values = {}
27
27
  @@prefix_map = {}
@@ -31,30 +31,41 @@ module RubyUnits
31
31
  @@unit_match_regex = nil
32
32
  UNITY = '<1>'.freeze
33
33
  UNITY_ARRAY = [UNITY].freeze
34
- # ideally we would like to generate this regex from the alias for a 'feet' and 'inches', but they aren't
35
- # defined at the point in the code where we need this regex.
36
- FEET_INCH_UNITS_REGEX = /(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inch(?:es)?)/
37
- FEET_INCH_REGEX = /(\d+)\s*#{FEET_INCH_UNITS_REGEX}/
38
- # ideally we would like to generate this regex from the alias for a 'pound' and 'ounce', but they aren't
39
- # defined at the point in the code where we need this regex.
40
- LBS_OZ_UNIT_REGEX = /(?:#|lbs?|pounds?|pound-mass)+[\s,]*(\d+)\s*(?:ozs?|ounces?)/
41
- LBS_OZ_REGEX = /(\d+)\s*#{LBS_OZ_UNIT_REGEX}/
42
- # ideally we would like to generate this regex from the alias for a 'stone' and 'pound', but they aren't
43
- # defined at the point in the code where we need this regex.
44
- # also note that the plural of 'stone' is still 'stone', but we accept 'stones' anyway.
45
- STONE_LB_UNIT_REGEX = /(?:sts?|stones?)+[\s,]*(\d+)\s*(?:#|lbs?|pounds?|pound-mass)*/
46
- STONE_LB_REGEX = /(\d+)\s*#{STONE_LB_UNIT_REGEX}/
47
- TIME_REGEX = /(\d+)*:(\d+)*:*(\d+)*[:,]*(\d+)*/
48
- SCI_NUMBER = /([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)/
49
- RATIONAL_NUMBER = %r{\(?([+-])?(\d+[ -])?(\d+)\/(\d+)\)?}
50
- COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/
51
- NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/
52
- UNIT_STRING_REGEX = %r{#{SCI_NUMBER}*\s*([^\/]*)\/*(.+)*}
53
- TOP_REGEX = /([^ \*]+)(?:\^|\*\*)([\d-]+)/
54
- BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/
55
- NUMBER_UNIT_REGEX = /#{SCI_NUMBER}?(.*)/
56
- COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/
57
- RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(.+)?/
34
+ # ideally we would like to generate this regex from the alias for a 'feet'
35
+ # and 'inches', but they aren't defined at the point in the code where we
36
+ # need this regex.
37
+ FEET_INCH_UNITS_REGEX = /(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inch(?:es)?)/.freeze
38
+ FEET_INCH_REGEX = /(\d+)\s*#{FEET_INCH_UNITS_REGEX}/.freeze
39
+ # ideally we would like to generate this regex from the alias for a 'pound'
40
+ # and 'ounce', but they aren't defined at the point in the code where we
41
+ # need this regex.
42
+ LBS_OZ_UNIT_REGEX = /(?:#|lbs?|pounds?|pound-mass)+[\s,]*(\d+)\s*(?:ozs?|ounces?)/.freeze
43
+ LBS_OZ_REGEX = /(\d+)\s*#{LBS_OZ_UNIT_REGEX}/.freeze
44
+ # ideally we would like to generate this regex from the alias for a 'stone'
45
+ # and 'pound', but they aren't defined at the point in the code where we
46
+ # need this regex. also note that the plural of 'stone' is still 'stone',
47
+ # but we accept 'stones' anyway.
48
+ STONE_LB_UNIT_REGEX = /(?:sts?|stones?)+[\s,]*(\d+)\s*(?:#|lbs?|pounds?|pound-mass)*/.freeze
49
+ STONE_LB_REGEX = /(\d+)\s*#{STONE_LB_UNIT_REGEX}/.freeze
50
+ # Time formats: 12:34:56,78, (hh:mm:ss,msec) etc.
51
+ TIME_REGEX = /(?<hour>\d+):(?<min>\d+):(?:(?<sec>\d+))?(?:,(?<msec>\d+))?/.freeze
52
+ # Scientific notation: 1, -1, +1, 1.2, +1.2, -1.2, 123.4E5, +123.4e5,
53
+ # -123.4E+5, -123.4e-5, etc.
54
+ SCI_NUMBER = /([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)/.freeze
55
+ # Rational number, including improper fractions: 1 2/3, -1 2/3, 5/3, etc.
56
+ RATIONAL_NUMBER = %r{\(?([+-])?(\d+[ -])?(\d+)/(\d+)\)?}.freeze
57
+ # Complex numbers: 1+2i, 1.0+2.0i, -1-1i, etc.
58
+ COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/.freeze
59
+ # Any Complex, Rational, or scientific number
60
+ ANY_NUMBER = /(#{COMPLEX_NUMBER}|#{RATIONAL_NUMBER}|#{SCI_NUMBER})/.freeze
61
+ ANY_NUMBER_REGEX = /(?:#{ANY_NUMBER})?\s?([^-\d.].*)?/.freeze
62
+ NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/.freeze
63
+ UNIT_STRING_REGEX = %r{#{SCI_NUMBER}*\s*([^/]*)/*(.+)*}.freeze
64
+ TOP_REGEX = /([^ *]+)(?:\^|\*\*)([\d-]+)/.freeze
65
+ BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/.freeze
66
+ NUMBER_UNIT_REGEX = /#{SCI_NUMBER}?(.*)/.freeze
67
+ COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/.freeze
68
+ RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(.+)?/.freeze
58
69
  KELVIN = ['<kelvin>'].freeze
59
70
  FAHRENHEIT = ['<fahrenheit>'].freeze
60
71
  RANKINE = ['<rankine>'].freeze
@@ -73,62 +84,60 @@ module RubyUnits
73
84
  angle
74
85
  ].freeze
75
86
  @@kinds = {
76
- -312_078 => :elastance,
77
- -312_058 => :resistance,
78
- -312_038 => :inductance,
79
- -152_040 => :magnetism,
80
- -152_038 => :magnetism,
81
- -152_058 => :potential,
82
- -7997 => :specific_volume,
83
- -79 => :snap,
84
- -59 => :jolt,
85
- -39 => :acceleration,
86
- -38 => :radiation,
87
- -20 => :frequency,
88
- -19 => :speed,
89
- -18 => :viscosity,
90
- -17 => :volumetric_flow,
91
- -1 => :wavenumber,
92
- 0 => :unitless,
93
- 1 => :length,
94
- 2 => :area,
95
- 3 => :volume,
96
- 20 => :time,
97
- 400 => :temperature,
98
- 7941 => :yank,
99
- 7942 => :power,
100
- 7959 => :pressure,
101
- 7962 => :energy,
102
- 7979 => :viscosity,
103
- 7961 => :force,
104
- 7981 => :momentum,
105
- 7982 => :angular_momentum,
106
- 7997 => :density,
107
- 7998 => :area_density,
108
- 8000 => :mass,
109
- 152_020 => :radiation_exposure,
110
- 159_999 => :magnetism,
111
- 160_000 => :current,
112
- 160_020 => :charge,
113
- 312_058 => :conductance,
114
- 312_078 => :capacitance,
115
- 3_199_980 => :activity,
116
- 3_199_997 => :molar_concentration,
117
- 3_200_000 => :substance,
118
- 63_999_998 => :illuminance,
119
- 64_000_000 => :luminous_power,
87
+ -312_078 => :elastance,
88
+ -312_058 => :resistance,
89
+ -312_038 => :inductance,
90
+ -152_040 => :magnetism,
91
+ -152_038 => :magnetism,
92
+ -152_058 => :potential,
93
+ -7997 => :specific_volume,
94
+ -79 => :snap,
95
+ -59 => :jolt,
96
+ -39 => :acceleration,
97
+ -38 => :radiation,
98
+ -20 => :frequency,
99
+ -19 => :speed,
100
+ -18 => :viscosity,
101
+ -17 => :volumetric_flow,
102
+ -1 => :wavenumber,
103
+ 0 => :unitless,
104
+ 1 => :length,
105
+ 2 => :area,
106
+ 3 => :volume,
107
+ 20 => :time,
108
+ 400 => :temperature,
109
+ 7941 => :yank,
110
+ 7942 => :power,
111
+ 7959 => :pressure,
112
+ 7962 => :energy,
113
+ 7979 => :viscosity,
114
+ 7961 => :force,
115
+ 7981 => :momentum,
116
+ 7982 => :angular_momentum,
117
+ 7997 => :density,
118
+ 7998 => :area_density,
119
+ 8000 => :mass,
120
+ 152_020 => :radiation_exposure,
121
+ 159_999 => :magnetism,
122
+ 160_000 => :current,
123
+ 160_020 => :charge,
124
+ 312_058 => :conductance,
125
+ 312_078 => :capacitance,
126
+ 3_199_980 => :activity,
127
+ 3_199_997 => :molar_concentration,
128
+ 3_200_000 => :substance,
129
+ 63_999_998 => :illuminance,
130
+ 64_000_000 => :luminous_power,
120
131
  1_280_000_000 => :currency,
121
- 25_600_000_000 => :information,
132
+ 25_600_000_000 => :information,
122
133
  511_999_999_980 => :angular_velocity,
123
134
  512_000_000_000 => :angle
124
135
  }.freeze
125
- @@cached_units = {}
126
- @@base_unit_cache = {}
127
136
 
128
137
  # Class Methods
129
138
 
130
139
  # setup internal arrays and hashes
131
- # @return [true]
140
+ # @return [Boolean]
132
141
  def self.setup
133
142
  clear_cache
134
143
  @@prefix_values = {}
@@ -139,11 +148,11 @@ module RubyUnits
139
148
  @@unit_match_regex = nil
140
149
  @@prefix_regex = nil
141
150
 
142
- @@definitions.each do |_name, definition|
151
+ @@definitions.each_value do |definition|
143
152
  use_definition(definition)
144
153
  end
145
154
 
146
- RubyUnits::Unit.new(1)
155
+ new(1)
147
156
  true
148
157
  end
149
158
 
@@ -155,7 +164,7 @@ module RubyUnits
155
164
  end
156
165
 
157
166
  # return the unit definition for a unit
158
- # @param [String] unit
167
+ # @param unit_name [String]
159
168
  # @return [RubyUnits::Unit::Definition, nil]
160
169
  def self.definition(unit_name)
161
170
  unit = unit_name =~ /^<.+>$/ ? unit_name : "<#{unit_name}>"
@@ -163,13 +172,13 @@ module RubyUnits
163
172
  end
164
173
 
165
174
  # return a list of all defined units
166
- # @return [Array]
175
+ # @return [Array<RubyUnits::Units::Definition>]
167
176
  def self.definitions
168
177
  @@definitions
169
178
  end
170
179
 
171
- # @param [RubyUnits::Unit::Definition|String] unit_definition
172
- # @param [Block] block
180
+ # @param [RubyUnits::Unit::Definition, String] unit_definition
181
+ # @param [Proc] block
173
182
  # @return [RubyUnits::Unit::Definition]
174
183
  # @raise [ArgumentError] when passed a non-string if using the block form
175
184
  # Unpack a unit definition and add it to the array of defined units
@@ -184,54 +193,62 @@ module RubyUnits
184
193
  # RubyUnits::Unit.define(unit_definition)
185
194
  def self.define(unit_definition, &block)
186
195
  if block_given?
187
- raise ArgumentError, 'When using the block form of RubyUnits::Unit.define, pass the name of the unit' unless unit_definition.instance_of?(String)
196
+ raise ArgumentError, 'When using the block form of RubyUnits::Unit.define, pass the name of the unit' unless unit_definition.is_a?(String)
197
+
188
198
  unit_definition = RubyUnits::Unit::Definition.new(unit_definition, &block)
189
199
  end
190
- RubyUnits::Unit.definitions[unit_definition.name] = unit_definition
191
- RubyUnits::Unit.use_definition(unit_definition)
200
+ definitions[unit_definition.name] = unit_definition
201
+ use_definition(unit_definition)
192
202
  unit_definition
193
203
  end
194
204
 
205
+ # Get the definition for a unit and allow it to be redefined
206
+ #
195
207
  # @param [String] name Name of unit to redefine
196
- # @param [Block] block
208
+ # @param [Proc] _block
197
209
  # @raise [ArgumentError] if a block is not given
198
- # @yield [RubyUnits::Unit::Definition]
210
+ # @yieldparam [RubyUnits::Unit::Definition] the definition of the unit being
211
+ # redefined
199
212
  # @return (see RubyUnits::Unit.define)
200
- # Get the definition for a unit and allow it to be redefined
201
- def self.redefine!(name)
213
+ def self.redefine!(name, &_block)
202
214
  raise ArgumentError, 'A block is required to redefine a unit' unless block_given?
215
+
203
216
  unit_definition = definition(name)
204
217
  raise(ArgumentError, "'#{name}' Unit not recognized") unless unit_definition
218
+
205
219
  yield unit_definition
206
220
  @@definitions.delete("<#{name}>")
207
221
  define(unit_definition)
208
- RubyUnits::Unit.setup
222
+ setup
209
223
  end
210
224
 
211
- # @param [String] name of unit to undefine
212
- # @return (see RubyUnits::Unit.setup)
213
225
  # Undefine a unit. Will not raise an exception for unknown units.
226
+ #
227
+ # @param unit [String] name of unit to undefine
228
+ # @return (see RubyUnits::Unit.setup)
214
229
  def self.undefine!(unit)
215
230
  @@definitions.delete("<#{unit}>")
216
- RubyUnits::Unit.setup
231
+ setup
217
232
  end
218
233
 
219
- # @return [Hash]
234
+ # Unit cache
235
+ #
236
+ # @return [RubyUnits::Cache]
220
237
  def self.cached
221
- @@cached_units
238
+ @cached ||= RubyUnits::Cache.new
222
239
  end
223
240
 
224
- # @return [true]
241
+ # @return [Boolean]
225
242
  def self.clear_cache
226
- @@cached_units = {}
227
- @@base_unit_cache = {}
228
- RubyUnits::Unit.new(1)
243
+ cached.clear
244
+ base_unit_cache.clear
245
+ new(1)
229
246
  true
230
247
  end
231
248
 
232
- # @return [Hash]
249
+ # @return [RubyUnits::Cache]
233
250
  def self.base_unit_cache
234
- @@base_unit_cache
251
+ @base_unit_cache ||= RubyUnits::Cache.new
235
252
  end
236
253
 
237
254
  # @example parse strings
@@ -240,104 +257,75 @@ module RubyUnits
240
257
  # @return [Unit]
241
258
  def self.parse(input)
242
259
  first, second = input.scan(/(.+)\s(?:in|to|as)\s(.+)/i).first
243
- second.nil? ? RubyUnits::Unit.new(first) : RubyUnits::Unit.new(first).convert_to(second)
260
+ second.nil? ? new(first) : new(first).convert_to(second)
244
261
  end
245
262
 
246
- # @param [Numeric] q quantity
247
- # @param [Array] n numerator
248
- # @param [Array] d denominator
263
+ # @param q [Numeric] quantity
264
+ # @param n [Array] numerator
265
+ # @param d [Array] denominator
249
266
  # @return [Hash]
250
267
  def self.eliminate_terms(q, n, d)
251
268
  num = n.dup
252
269
  den = d.dup
270
+ num.delete(UNITY)
271
+ den.delete(UNITY)
253
272
 
254
- num.delete_if { |v| v == UNITY }
255
- den.delete_if { |v| v == UNITY }
256
- combined = Hash.new(0)
257
-
258
- i = 0
259
- loop do
260
- break if i > num.size
261
- if @@prefix_values.key? num[i]
262
- k = [num[i], num[i + 1]]
263
- i += 2
264
- else
265
- k = num[i]
266
- i += 1
267
- end
268
- combined[k] += 1 unless k.nil? || k == UNITY
269
- end
273
+ combined = ::Hash.new(0)
270
274
 
271
- j = 0
272
- loop do
273
- break if j > den.size
274
- if @@prefix_values.key? den[j]
275
- k = [den[j], den[j + 1]]
276
- j += 2
277
- else
278
- k = den[j]
279
- j += 1
280
- end
281
- combined[k] -= 1 unless k.nil? || k == UNITY
275
+ [[num, 1], [den, -1]].each do |array, increment|
276
+ array.chunk_while { |elt_before, _| definition(elt_before).prefix? }
277
+ .to_a
278
+ .each { |unit| combined[unit] += increment }
282
279
  end
283
280
 
284
281
  num = []
285
282
  den = []
286
283
  combined.each do |key, value|
287
- if value >= 0
284
+ if value.positive?
288
285
  value.times { num << key }
289
- elsif value < 0
286
+ elsif value.negative?
290
287
  value.abs.times { den << key }
291
288
  end
292
289
  end
293
290
  num = UNITY_ARRAY if num.empty?
294
291
  den = UNITY_ARRAY if den.empty?
295
- { scalar: q, numerator: num.flatten.compact, denominator: den.flatten.compact }
292
+ { scalar: q, numerator: num.flatten, denominator: den.flatten }
293
+ end
294
+
295
+ # Creates a new unit from the current one with all common terms eliminated.
296
+ #
297
+ # @return [RubyUnits::Unit]
298
+ def eliminate_terms
299
+ self.class.new(self.class.eliminate_terms(@scalar, @numerator, @denominator))
296
300
  end
297
301
 
298
302
  # return an array of base units
299
303
  # @return [Array]
300
304
  def self.base_units
301
- @@base_units ||= @@definitions.dup.delete_if { |_, defn| !defn.base? }.keys.map { |u| RubyUnits::Unit.new(u) }
305
+ @@base_units ||= @@definitions.dup.select { |_, definition| definition.base? }.keys.map { |u| new(u) }
302
306
  end
303
307
 
304
- # parse a string consisting of a number and a unit string
308
+ # Parse a string consisting of a number and a unit string
305
309
  # NOTE: This does not properly handle units formatted like '12mg/6ml'
310
+ #
306
311
  # @param [String] string
307
- # @return [Array] consisting of [Numeric, "unit"]
312
+ # @return [Array(Numeric, String)] consisting of [number, "unit"]
308
313
  def self.parse_into_numbers_and_units(string)
309
- # scientific notation.... 123.234E22, -123.456e-10
310
- sci = /[+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*/
311
- # rational numbers.... -1/3, 1/5, 20/100, -6 1/2, -6-1/2
312
- rational = %r{\(?[+-]?(?:\d+[ -])?\d+\/\d+\)?}
313
- # complex numbers... -1.2+3i, +1.2-3.3i
314
- complex = /#{sci}{2,2}i/
315
- anynumber = /(?:(#{complex}|#{rational}|#{sci}))?\s?([^-\d\.].*)?/
316
-
317
- num, unit = string.scan(anynumber).first
314
+ num, unit = string.scan(ANY_NUMBER_REGEX).first
318
315
 
319
316
  [
320
317
  case num
321
- when NilClass
318
+ when nil # This happens when no number is passed and we are parsing a pure unit string
322
319
  1
323
- when complex
324
- if num.respond_to?(:to_c)
325
- num.to_c
326
- else
327
- #:nocov_19:
328
- Complex(*num.scan(/(#{sci})(#{sci})i/).flatten.map(&:to_i))
329
- #:nocov_19:
330
- end
331
- when rational
332
- # if it has whitespace, it will be of the form '6 1/2'
333
- if num =~ RATIONAL_NUMBER
334
- sign = Regexp.last_match(1) == '-' ? -1 : 1
335
- n = Regexp.last_match(2).to_i
336
- f = Rational(Regexp.last_match(3).to_i, Regexp.last_match(4).to_i)
337
- sign * (n + f)
338
- else
339
- Rational(*num.split('/').map(&:to_i))
340
- end
320
+ when COMPLEX_NUMBER
321
+ num.to_c
322
+ when RATIONAL_NUMBER
323
+ # We use this method instead of relying on `to_r` because it does not
324
+ # handle improper fractions correctly.
325
+ sign = Regexp.last_match(1) == '-' ? -1 : 1
326
+ n = Regexp.last_match(2).to_i
327
+ f = Rational(Regexp.last_match(3).to_i, Regexp.last_match(4).to_i)
328
+ sign * (n + f)
341
329
  else
342
330
  num.to_f
343
331
  end,
@@ -353,9 +341,9 @@ module RubyUnits
353
341
  end
354
342
 
355
343
  # return a regex used to match units
356
- # @return [RegExp]
344
+ # @return [Regexp]
357
345
  def self.unit_match_regex
358
- @@unit_match_regex ||= /(#{RubyUnits::Unit.prefix_regex})??(#{RubyUnits::Unit.unit_regex})\b/
346
+ @@unit_match_regex ||= /(#{prefix_regex})??(#{unit_regex})\b/
359
347
  end
360
348
 
361
349
  # return a regexp fragment used to match prefixes
@@ -365,11 +353,14 @@ module RubyUnits
365
353
  @@prefix_regex ||= @@prefix_map.keys.sort_by { |prefix| [prefix.length, prefix] }.reverse.join('|')
366
354
  end
367
355
 
356
+ # Generates (and memoizes) a regexp matching any of the temperature units or their aliases.
357
+ #
358
+ # @return [Regexp]
368
359
  def self.temp_regex
369
360
  @@temp_regex ||= begin
370
361
  temp_units = %w[tempK tempC tempF tempR degK degC degF degR]
371
362
  aliases = temp_units.map do |unit|
372
- d = RubyUnits::Unit.definition(unit)
363
+ d = definition(unit)
373
364
  d && d.aliases
374
365
  end.flatten.compact
375
366
  regex_str = aliases.empty? ? '(?!x)x' : aliases.join('|')
@@ -378,6 +369,8 @@ module RubyUnits
378
369
  end
379
370
 
380
371
  # inject a definition into the internal array and set it up for use
372
+ #
373
+ # @param definition [RubyUnits::Unit::Definition]
381
374
  def self.use_definition(definition)
382
375
  @@unit_match_regex = nil # invalidate the unit match regex
383
376
  @@temp_regex = nil # invalidate the temp regex
@@ -425,20 +418,16 @@ module RubyUnits
425
418
  attr_accessor :unit_name
426
419
 
427
420
  # Used to copy one unit to another
428
- # @param [Unit] from Unit to copy definition from
429
- # @return [Unit]
421
+ # @param from [RubyUnits::Unit] Unit to copy definition from
422
+ # @return [RubyUnits::Unit]
430
423
  def copy(from)
431
- @scalar = from.scalar
432
- @numerator = from.numerator
424
+ @scalar = from.scalar
425
+ @numerator = from.numerator
433
426
  @denominator = from.denominator
434
427
  @base = from.base?
435
- @signature = from.signature
428
+ @signature = from.signature
436
429
  @base_scalar = from.base_scalar
437
- @unit_name = begin
438
- from.unit_name
439
- rescue
440
- nil
441
- end
430
+ @unit_name = from.unit_name
442
431
  self
443
432
  end
444
433
 
@@ -469,29 +458,26 @@ module RubyUnits
469
458
  @signature = nil
470
459
  @output = {}
471
460
  raise ArgumentError, 'Invalid Unit Format' if options[0].nil?
461
+
472
462
  if options.size == 2
473
463
  # options[0] is the scalar
474
464
  # options[1] is a unit string
475
- begin
476
- cached = @@cached_units[options[1]] * options[0]
477
- copy(cached)
478
- rescue
479
- initialize("#{options[0]} #{(begin
480
- options[1].units
481
- rescue
482
- options[1]
483
- end)}")
465
+ cached = self.class.cached.get(options[1])
466
+ if cached.nil?
467
+ initialize("#{options[0]} #{options[1]}")
468
+ else
469
+ copy(cached * options[0])
484
470
  end
485
471
  return
486
472
  end
487
473
  if options.size == 3
488
474
  options[1] = options[1].join if options[1].is_a?(Array)
489
475
  options[2] = options[2].join if options[2].is_a?(Array)
490
- begin
491
- cached = @@cached_units["#{options[1]}/#{options[2]}"] * options[0]
492
- copy(cached)
493
- rescue
476
+ cached = self.class.cached.get("#{options[1]}/#{options[2]}")
477
+ if cached.nil?
494
478
  initialize("#{options[0]} #{options[1]}/#{options[2]}")
479
+ else
480
+ copy(cached) * options[0]
495
481
  end
496
482
  return
497
483
  end
@@ -528,20 +514,21 @@ module RubyUnits
528
514
  end
529
515
  update_base_scalar
530
516
  raise ArgumentError, 'Temperatures must not be less than absolute zero' if temperature? && base_scalar < 0
517
+
531
518
  unary_unit = units || ''
532
519
  if options.first.instance_of?(String)
533
- _opt_scalar, opt_units = RubyUnits::Unit.parse_into_numbers_and_units(options[0])
534
- unless @@cached_units.keys.include?(opt_units) ||
535
- (opt_units =~ %r{\D/[\d+\.]+}) ||
536
- (opt_units =~ %r{(#{RubyUnits::Unit.temp_regex})|(#{STONE_LB_UNIT_REGEX})|(#{LBS_OZ_UNIT_REGEX})|(#{FEET_INCH_UNITS_REGEX})|%|(#{TIME_REGEX})|i\s?(.+)?|&plusmn;|\+\/-})
537
- @@cached_units[opt_units] = (scalar == 1 ? self : opt_units.to_unit) if opt_units && !opt_units.empty?
520
+ _opt_scalar, opt_units = self.class.parse_into_numbers_and_units(options[0])
521
+ if !(self.class.cached.keys.include?(opt_units) ||
522
+ (opt_units =~ %r{\D/[\d+.]+}) ||
523
+ (opt_units =~ %r{(#{self.class.temp_regex})|(#{STONE_LB_UNIT_REGEX})|(#{LBS_OZ_UNIT_REGEX})|(#{FEET_INCH_UNITS_REGEX})|%|(#{TIME_REGEX})|i\s?(.+)?|&plusmn;|\+/-})) && (opt_units && !opt_units.empty?)
524
+ self.class.cached.set(opt_units, scalar == 1 ? self : opt_units.to_unit)
538
525
  end
539
526
  end
540
- unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /#{RubyUnits::Unit.temp_regex}/)
541
- @@cached_units[unary_unit] = (scalar == 1 ? self : unary_unit.to_unit)
527
+ unless self.class.cached.keys.include?(unary_unit) || (unary_unit =~ self.class.temp_regex)
528
+ self.class.cached.set(unary_unit, scalar == 1 ? self : unary_unit.to_unit)
542
529
  end
543
530
  [@scalar, @numerator, @denominator, @base_scalar, @signature, @base].each(&:freeze)
544
- self
531
+ super()
545
532
  end
546
533
 
547
534
  # @todo: figure out how to handle :counting units. This method should probably return :counting instead of :unitless for 'each'
@@ -551,9 +538,14 @@ module RubyUnits
551
538
  @@kinds[signature]
552
539
  end
553
540
 
554
- # @return [Unit]
555
- def to_unit
556
- self
541
+ # Convert the unit to a Unit, possibly performing a conversion.
542
+ # > The ability to pass a Unit to convert to was added in v3.0.0 for
543
+ # > consistency with other uses of #to_unit.
544
+ #
545
+ # @param other [RubyUnits::Unit, String] unit to convert to
546
+ # @return [RubyUnits::Unit]
547
+ def to_unit(other = nil)
548
+ other ? convert_to(other) : self
557
549
  end
558
550
 
559
551
  alias unit to_unit
@@ -562,10 +554,11 @@ module RubyUnits
562
554
  # @return [Boolean]
563
555
  def base?
564
556
  return @base if defined? @base
557
+
565
558
  @base = (@numerator + @denominator)
566
559
  .compact
567
560
  .uniq
568
- .map { |unit| RubyUnits::Unit.definition(unit) }
561
+ .map { |unit| self.class.definition(unit) }
569
562
  .all? { |element| element.unity? || element.base? }
570
563
  @base
571
564
  end
@@ -578,6 +571,7 @@ module RubyUnits
578
571
  # @todo this is brittle as it depends on the display_name of a unit, which can be changed
579
572
  def to_base
580
573
  return self if base?
574
+
581
575
  if @@unit_map[units] =~ /\A<(?:temp|deg)[CRF]>\Z/
582
576
  @signature = @@kinds.key(:temperature)
583
577
  base = if temperature?
@@ -588,12 +582,8 @@ module RubyUnits
588
582
  return base
589
583
  end
590
584
 
591
- cached = (begin
592
- (@@base_unit_cache[units] * scalar)
593
- rescue
594
- nil
595
- end)
596
- return cached if cached
585
+ cached_unit = self.class.base_unit_cache.get(units)
586
+ return cached_unit * scalar unless cached_unit.nil?
597
587
 
598
588
  num = []
599
589
  den = []
@@ -620,8 +610,8 @@ module RubyUnits
620
610
  num = num.flatten.compact
621
611
  den = den.flatten.compact
622
612
  num = UNITY_ARRAY if num.empty?
623
- base = RubyUnits::Unit.new(RubyUnits::Unit.eliminate_terms(q, num, den))
624
- @@base_unit_cache[units] = base
613
+ base = self.class.new(self.class.eliminate_terms(q, num, den))
614
+ self.class.base_unit_cache.set(units, base)
625
615
  base * @scalar
626
616
  end
627
617
 
@@ -646,6 +636,7 @@ module RubyUnits
646
636
  def to_s(target_units = nil)
647
637
  out = @output[target_units]
648
638
  return out if out
639
+
649
640
  separator = RubyUnits.configuration.separator
650
641
  case target_units
651
642
  when :ft
@@ -661,14 +652,14 @@ module RubyUnits
661
652
  out = case target_units.strip
662
653
  when /\A\s*\Z/ # whitespace only
663
654
  ''
664
- when /(%[\-+\.\w#]+)\s*(.+)*/ # format string like '%0.2f in'
655
+ when /(%[\-+.\w#]+)\s*(.+)*/ # format string like '%0.2f in'
665
656
  begin
666
657
  if Regexp.last_match(2) # unit specified, need to convert
667
658
  convert_to(Regexp.last_match(2)).to_s(Regexp.last_match(1))
668
659
  else
669
660
  "#{Regexp.last_match(1) % @scalar}#{separator}#{Regexp.last_match(2) || units}".strip
670
661
  end
671
- rescue # parse it like a strftime format string
662
+ rescue StandardError # parse it like a strftime format string
672
663
  (DateTime.new(0) + self).strftime(target_units)
673
664
  end
674
665
  when /(\S+)/ # unit only 'mm' or '1/mm'
@@ -695,6 +686,7 @@ module RubyUnits
695
686
  # @return [String]
696
687
  def inspect(dump = nil)
697
688
  return super() if dump
689
+
698
690
  to_s
699
691
  end
700
692
 
@@ -702,7 +694,7 @@ module RubyUnits
702
694
  # @return [Boolean]
703
695
  # @todo use unit definition to determine if it's a temperature instead of a regex
704
696
  def temperature?
705
- degree? && !(@@unit_map[units] =~ /temp[CFRK]/).nil?
697
+ degree? && units.match?(self.class.temp_regex)
706
698
  end
707
699
 
708
700
  alias is_temperature? temperature?
@@ -720,6 +712,7 @@ module RubyUnits
720
712
  # @return [String] possible values: degC, degF, degR, or degK
721
713
  def temperature_scale
722
714
  return nil unless temperature?
715
+
723
716
  "deg#{@@unit_map[units][/temp([CFRK])/, 1]}"
724
717
  end
725
718
 
@@ -733,17 +726,19 @@ module RubyUnits
733
726
  # Compare two Unit objects. Throws an exception if they are not of compatible types.
734
727
  # Comparisons are done based on the value of the unit in base SI units.
735
728
  # @param [Object] other
736
- # @return [-1|0|1|nil]
729
+ # @return [Integer,nil]
737
730
  # @raise [NoMethodError] when other does not define <=>
738
731
  # @raise [ArgumentError] when units are not compatible
739
732
  def <=>(other)
740
733
  raise NoMethodError, "undefined method `<=>' for #{base_scalar.inspect}" unless base_scalar.respond_to?(:<=>)
734
+
741
735
  if other.nil?
742
736
  base_scalar <=> nil
743
737
  elsif !temperature? && other.respond_to?(:zero?) && other.zero?
744
738
  base_scalar <=> 0
745
739
  elsif other.instance_of?(Unit)
746
740
  raise ArgumentError, "Incompatible Units ('#{units}' not compatible with '#{other.units}')" unless self =~ other
741
+
747
742
  base_scalar <=> other.base_scalar
748
743
  else
749
744
  x, y = coerce(other)
@@ -764,6 +759,7 @@ module RubyUnits
764
759
  zero?
765
760
  elsif other.instance_of?(Unit)
766
761
  return false unless self =~ other
762
+
767
763
  base_scalar == other.base_scalar
768
764
  else
769
765
  begin
@@ -791,9 +787,9 @@ module RubyUnits
791
787
  else
792
788
  begin
793
789
  x, y = coerce(other)
794
- return x =~ y
790
+ x =~ y
795
791
  rescue ArgumentError
796
- return false
792
+ false
797
793
  end
798
794
  end
799
795
  end
@@ -814,9 +810,9 @@ module RubyUnits
814
810
  else
815
811
  begin
816
812
  x, y = coerce(other)
817
- return x === y
813
+ x === y
818
814
  rescue ArgumentError
819
- return false
815
+ false
820
816
  end
821
817
  end
822
818
  end
@@ -839,14 +835,15 @@ module RubyUnits
839
835
  other.dup
840
836
  elsif self =~ other
841
837
  raise ArgumentError, 'Cannot add two temperatures' if [self, other].all?(&:temperature?)
838
+
842
839
  if [self, other].any?(&:temperature?)
843
840
  if temperature?
844
- RubyUnits::Unit.new(scalar: (scalar + other.convert_to(temperature_scale).scalar), numerator: @numerator, denominator: @denominator, signature: @signature)
841
+ self.class.new(scalar: (scalar + other.convert_to(temperature_scale).scalar), numerator: @numerator, denominator: @denominator, signature: @signature)
845
842
  else
846
- RubyUnits::Unit.new(scalar: (other.scalar + convert_to(other.temperature_scale).scalar), numerator: other.numerator, denominator: other.denominator, signature: other.signature)
843
+ self.class.new(scalar: (other.scalar + convert_to(other.temperature_scale).scalar), numerator: other.numerator, denominator: other.denominator, signature: other.signature)
847
844
  end
848
845
  else
849
- RubyUnits::Unit.new(scalar: (base_scalar + other.base_scalar), numerator: base.numerator, denominator: base.denominator, signature: @signature).convert_to(self)
846
+ self.class.new(scalar: (base_scalar + other.base_scalar), numerator: base.numerator, denominator: base.denominator, signature: @signature).convert_to(self)
850
847
  end
851
848
  else
852
849
  raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
@@ -876,19 +873,19 @@ module RubyUnits
876
873
  end
877
874
  elsif self =~ other
878
875
  if [self, other].all?(&:temperature?)
879
- RubyUnits::Unit.new(scalar: (base_scalar - other.base_scalar), numerator: KELVIN, denominator: UNITY_ARRAY, signature: @signature).convert_to(temperature_scale)
876
+ self.class.new(scalar: (base_scalar - other.base_scalar), numerator: KELVIN, denominator: UNITY_ARRAY, signature: @signature).convert_to(temperature_scale)
880
877
  elsif temperature?
881
- RubyUnits::Unit.new(scalar: (base_scalar - other.base_scalar), numerator: ['<tempK>'], denominator: UNITY_ARRAY, signature: @signature).convert_to(self)
878
+ self.class.new(scalar: (base_scalar - other.base_scalar), numerator: ['<tempK>'], denominator: UNITY_ARRAY, signature: @signature).convert_to(self)
882
879
  elsif other.temperature?
883
880
  raise ArgumentError, 'Cannot subtract a temperature from a differential degree unit'
884
881
  else
885
- RubyUnits::Unit.new(scalar: (base_scalar - other.base_scalar), numerator: base.numerator, denominator: base.denominator, signature: @signature).convert_to(self)
882
+ self.class.new(scalar: (base_scalar - other.base_scalar), numerator: base.numerator, denominator: base.denominator, signature: @signature).convert_to(self)
886
883
  end
887
884
  else
888
885
  raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
889
886
  end
890
887
  when Time
891
- 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'
888
+ raise ArgumentError, 'Date and Time objects represent fixed points in time and cannot be subtracted from a Unit'
892
889
  else
893
890
  x, y = coerce(other)
894
891
  y - x
@@ -903,11 +900,12 @@ module RubyUnits
903
900
  case other
904
901
  when Unit
905
902
  raise ArgumentError, 'Cannot multiply by temperatures' if [other, self].any?(&:temperature?)
906
- opts = RubyUnits::Unit.eliminate_terms(@scalar * other.scalar, @numerator + other.numerator, @denominator + other.denominator)
903
+
904
+ opts = self.class.eliminate_terms(@scalar * other.scalar, @numerator + other.numerator, @denominator + other.denominator)
907
905
  opts[:signature] = @signature + other.signature
908
- RubyUnits::Unit.new(opts)
906
+ self.class.new(opts)
909
907
  when Numeric
910
- RubyUnits::Unit.new(scalar: @scalar * other, numerator: @numerator, denominator: @denominator, signature: @signature)
908
+ self.class.new(scalar: @scalar * other, numerator: @numerator, denominator: @denominator, signature: @signature)
911
909
  else
912
910
  x, y = coerce(other)
913
911
  x * y
@@ -925,16 +923,18 @@ module RubyUnits
925
923
  when Unit
926
924
  raise ZeroDivisionError if other.zero?
927
925
  raise ArgumentError, 'Cannot divide with temperatures' if [other, self].any?(&:temperature?)
926
+
928
927
  sc = Rational(@scalar, other.scalar)
929
928
  sc = sc.numerator if sc.denominator == 1
930
- opts = RubyUnits::Unit.eliminate_terms(sc, @numerator + other.denominator, @denominator + other.numerator)
929
+ opts = self.class.eliminate_terms(sc, @numerator + other.denominator, @denominator + other.numerator)
931
930
  opts[:signature] = @signature - other.signature
932
- RubyUnits::Unit.new(opts)
931
+ self.class.new(opts)
933
932
  when Numeric
934
933
  raise ZeroDivisionError if other.zero?
934
+
935
935
  sc = Rational(@scalar, other)
936
936
  sc = sc.numerator if sc.denominator == 1
937
- RubyUnits::Unit.new(scalar: sc, numerator: @numerator, denominator: @denominator, signature: @signature)
937
+ self.class.new(scalar: sc, numerator: @numerator, denominator: @denominator, signature: @signature)
938
938
  else
939
939
  x, y = coerce(other)
940
940
  y / x
@@ -949,6 +949,7 @@ module RubyUnits
949
949
  def divmod(other)
950
950
  raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ other
951
951
  return scalar.divmod(other.scalar) if units == other.units
952
+
952
953
  to_base.scalar.divmod(other.to_base.scalar)
953
954
  end
954
955
 
@@ -959,7 +960,7 @@ module RubyUnits
959
960
  divmod(other).last
960
961
  end
961
962
 
962
- # Exponentiate. Only takes integer powers.
963
+ # Exponentiation. Only takes integer powers.
963
964
  # Note that anything raised to the power of 0 results in a Unit object with a scalar of 1, and no units.
964
965
  # Throws an exception if exponent is not an integer.
965
966
  # Ideally this routine should accept a float for the exponent
@@ -975,6 +976,7 @@ module RubyUnits
975
976
  # @raise [ArgumentError] when an invalid exponent is passed
976
977
  def **(other)
977
978
  raise ArgumentError, 'Cannot raise a temperature to a power' if temperature?
979
+
978
980
  if other.is_a?(Numeric)
979
981
  return inverse if other == -1
980
982
  return self if other == 1
@@ -982,14 +984,16 @@ module RubyUnits
982
984
  end
983
985
  case other
984
986
  when Rational
985
- return power(other.numerator).root(other.denominator)
987
+ power(other.numerator).root(other.denominator)
986
988
  when Integer
987
- return power(other)
989
+ power(other)
988
990
  when Float
989
991
  return self**other.to_i if other == other.to_i
992
+
990
993
  valid = (1..9).map { |n| Rational(1, n) }
991
994
  raise ArgumentError, 'Not a n-th root (1..9), use 1/n' unless valid.include? other.abs
992
- return root(Rational(1, other).to_int)
995
+
996
+ root(Rational(1, other).to_int)
993
997
  when Complex
994
998
  raise ArgumentError, 'exponentiation of complex numbers is not supported.'
995
999
  else
@@ -1009,6 +1013,7 @@ module RubyUnits
1009
1013
  return 1 if n.zero?
1010
1014
  return self if n == 1
1011
1015
  return (1..(n - 1).to_i).inject(self) { |acc, _elem| acc * self } if n >= 0
1016
+
1012
1017
  (1..-(n - 1).to_i).inject(self) { |acc, _elem| acc / self }
1013
1018
  end
1014
1019
 
@@ -1016,7 +1021,7 @@ module RubyUnits
1016
1021
  # if n < 0, returns 1/unit^(1/n)
1017
1022
  # @param [Integer] n
1018
1023
  # @return [Unit]
1019
- # @raise [ArgumentError] when attemptint to take the root of a temperature
1024
+ # @raise [ArgumentError] when attempting to take the root of a temperature
1020
1025
  # @raise [ArgumentError] when n is not an integer
1021
1026
  # @raise [ArgumentError] when n is 0
1022
1027
  def root(n)
@@ -1029,6 +1034,7 @@ module RubyUnits
1029
1034
  vec = unit_signature_vector
1030
1035
  vec = vec.map { |x| x % n }
1031
1036
  raise ArgumentError, 'Illegal root' unless vec.max.zero?
1037
+
1032
1038
  num = @numerator.dup
1033
1039
  den = @denominator.dup
1034
1040
 
@@ -1043,13 +1049,13 @@ module RubyUnits
1043
1049
  r = ((x / n) * (n - 1)).to_int
1044
1050
  r.times { den.delete_at(den.index(item)) }
1045
1051
  end
1046
- RubyUnits::Unit.new(scalar: @scalar**Rational(1, n), numerator: num, denominator: den)
1052
+ self.class.new(scalar: @scalar**Rational(1, n), numerator: num, denominator: den)
1047
1053
  end
1048
1054
 
1049
1055
  # returns inverse of Unit (1/unit)
1050
1056
  # @return [Unit]
1051
1057
  def inverse
1052
- RubyUnits::Unit.new('1') / self
1058
+ self.class.new('1') / self
1053
1059
  end
1054
1060
 
1055
1061
  # convert to a specified unit string or to the same units as another Unit
@@ -1060,13 +1066,17 @@ module RubyUnits
1060
1066
  # To convert a Unit object to match another Unit object, use:
1061
1067
  # unit1 >>= unit2
1062
1068
  #
1063
- # Special handling for temperature conversions is supported. If the Unit object is converted
1064
- # from one temperature unit to another, the proper temperature offsets will be used.
1065
- # Supports Kelvin, Celsius, Fahrenheit, and Rankine scales.
1069
+ # Special handling for temperature conversions is supported. If the Unit
1070
+ # object is converted from one temperature unit to another, the proper
1071
+ # temperature offsets will be used. Supports Kelvin, Celsius, Fahrenheit,
1072
+ # and Rankine scales.
1066
1073
  #
1067
- # @note If temperature is part of a compound unit, the temperature will be treated as a differential
1068
- # and the units will be scaled appropriately.
1069
- # @param [Object] other
1074
+ # @note If temperature is part of a compound unit, the temperature will be
1075
+ # treated as a differential and the units will be scaled appropriately.
1076
+ # @note When converting units with Integer scalars, the scalar will be
1077
+ # converted to a Rational to avoid unexpected behavior caused by Integer
1078
+ # division.
1079
+ # @param other [Unit, String]
1070
1080
  # @return [Unit]
1071
1081
  # @raise [ArgumentError] when attempting to convert a degree to a temperature
1072
1082
  # @raise [ArgumentError] when target unit is unknown
@@ -1075,14 +1085,23 @@ module RubyUnits
1075
1085
  return self if other.nil?
1076
1086
  return self if TrueClass === other
1077
1087
  return self if FalseClass === other
1078
- if (Unit === other && other.temperature?) || (String === other && other =~ /temp[CFRK]/)
1088
+
1089
+ if (other.is_a?(Unit) && other.temperature?) || (other.is_a?(String) && other =~ self.class.temp_regex)
1079
1090
  raise ArgumentError, 'Receiver is not a temperature unit' unless degree?
1091
+
1080
1092
  start_unit = units
1081
- target_unit = begin
1093
+ # @type [String]
1094
+ target_unit = case other
1095
+ when Unit
1082
1096
  other.units
1083
- rescue
1097
+ when String
1084
1098
  other
1099
+ else
1100
+ raise ArgumentError, 'Unknown target units'
1085
1101
  end
1102
+ return self if target_unit == start_unit
1103
+
1104
+ # @type [Numeric]
1086
1105
  @base_scalar ||= case @@unit_map[start_unit]
1087
1106
  when '<tempC>'
1088
1107
  @scalar + 273.15
@@ -1093,36 +1112,47 @@ module RubyUnits
1093
1112
  when '<tempR>'
1094
1113
  @scalar.to_r * Rational(5, 9)
1095
1114
  end
1115
+ # @type [Numeric]
1096
1116
  q = case @@unit_map[target_unit]
1097
1117
  when '<tempC>'
1098
- @base_scalar - 273.15r
1118
+ @base_scalar - 273.15
1099
1119
  when '<tempK>'
1100
1120
  @base_scalar
1101
1121
  when '<tempF>'
1102
- @base_scalar.to_r * Rational(9, 5) - 459.67r
1122
+ (@base_scalar.to_r * Rational(9, 5)) - 459.67r
1103
1123
  when '<tempR>'
1104
1124
  @base_scalar.to_r * Rational(9, 5)
1105
1125
  end
1106
- return RubyUnits::Unit.new("#{q} #{target_unit}")
1126
+ self.class.new("#{q} #{target_unit}")
1107
1127
  else
1108
- case other
1109
- when Unit
1110
- return self if other.units == units
1111
- target = other
1112
- when String
1113
- target = RubyUnits::Unit.new(other)
1114
- else
1115
- raise ArgumentError, 'Unknown target units'
1116
- end
1128
+ # @type [Unit]
1129
+ target = case other
1130
+ when Unit
1131
+ other
1132
+ when String
1133
+ self.class.new(other)
1134
+ else
1135
+ raise ArgumentError, 'Unknown target units'
1136
+ end
1137
+ return self if target.units == units
1138
+
1117
1139
  raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ target
1118
- numerator1 = @numerator.map { |x| @@prefix_values[x] ? @@prefix_values[x] : x }.map { |i| i.is_a?(Numeric) ? i : @@unit_values[i][:scalar] }.compact
1119
- denominator1 = @denominator.map { |x| @@prefix_values[x] ? @@prefix_values[x] : x }.map { |i| i.is_a?(Numeric) ? i : @@unit_values[i][:scalar] }.compact
1120
- numerator2 = target.numerator.map { |x| @@prefix_values[x] ? @@prefix_values[x] : x }.map { |x| x.is_a?(Numeric) ? x : @@unit_values[x][:scalar] }.compact
1121
- denominator2 = target.denominator.map { |x| @@prefix_values[x] ? @@prefix_values[x] : x }.map { |x| x.is_a?(Numeric) ? x : @@unit_values[x][:scalar] }.compact
1122
-
1123
- q = @scalar * ((numerator1 + denominator2).inject(1) { |acc, elem| acc * elem }) /
1124
- ((numerator2 + denominator1).inject(1) { |acc, elem| acc * elem })
1125
- return RubyUnits::Unit.new(scalar: q, numerator: target.numerator, denominator: target.denominator, signature: target.signature)
1140
+
1141
+ numerator1 = @numerator.map { |x| @@prefix_values[x] || x }.map { |i| i.is_a?(Numeric) ? i : @@unit_values[i][:scalar] }.compact
1142
+ denominator1 = @denominator.map { |x| @@prefix_values[x] || x }.map { |i| i.is_a?(Numeric) ? i : @@unit_values[i][:scalar] }.compact
1143
+ numerator2 = target.numerator.map { |x| @@prefix_values[x] || x }.map { |x| x.is_a?(Numeric) ? x : @@unit_values[x][:scalar] }.compact
1144
+ denominator2 = target.denominator.map { |x| @@prefix_values[x] || x }.map { |x| x.is_a?(Numeric) ? x : @@unit_values[x][:scalar] }.compact
1145
+
1146
+ # If the scalar is an Integer, convert it to a Rational number so that
1147
+ # if the value is scaled during conversion, resolution is not lost due
1148
+ # to integer math
1149
+ # @type [Rational, Numeric]
1150
+ conversion_scalar = @scalar.is_a?(Integer) ? @scalar.to_r : @scalar
1151
+ q = conversion_scalar * (numerator1 + denominator2).reduce(1, :*) / (numerator2 + denominator1).reduce(1, :*)
1152
+ # Convert the scalar to an Integer if the result is equivalent to an
1153
+ # integer
1154
+ q = q.to_i if @scalar.is_a?(Integer) && q.to_i == q
1155
+ self.class.new(scalar: q, numerator: target.numerator, denominator: target.denominator, signature: target.signature)
1126
1156
  end
1127
1157
  end
1128
1158
 
@@ -1134,6 +1164,7 @@ module RubyUnits
1134
1164
  # @raise [RuntimeError] when not unitless
1135
1165
  def to_f
1136
1166
  return @scalar.to_f if unitless?
1167
+
1137
1168
  raise "Cannot convert '#{self}' to Float unless unitless. Use Unit#scalar"
1138
1169
  end
1139
1170
 
@@ -1142,6 +1173,7 @@ module RubyUnits
1142
1173
  # @raise [RuntimeError] when not unitless
1143
1174
  def to_c
1144
1175
  return Complex(@scalar) if unitless?
1176
+
1145
1177
  raise "Cannot convert '#{self}' to Complex unless unitless. Use Unit#scalar"
1146
1178
  end
1147
1179
 
@@ -1150,6 +1182,7 @@ module RubyUnits
1150
1182
  # @raise [RuntimeError] when not unitless
1151
1183
  def to_i
1152
1184
  return @scalar.to_int if unitless?
1185
+
1153
1186
  raise "Cannot convert '#{self}' to Integer unless unitless. Use Unit#scalar"
1154
1187
  end
1155
1188
 
@@ -1160,6 +1193,7 @@ module RubyUnits
1160
1193
  # @raise [RuntimeError] when not unitless
1161
1194
  def to_r
1162
1195
  return @scalar.to_r if unitless?
1196
+
1163
1197
  raise "Cannot convert '#{self}' to Rational unless unitless. Use Unit#scalar"
1164
1198
  end
1165
1199
 
@@ -1169,52 +1203,29 @@ module RubyUnits
1169
1203
  to_s
1170
1204
  end
1171
1205
 
1172
- # returns the 'unit' part of the Unit object without the scalar
1206
+ # Returns the 'unit' part of the Unit object without the scalar
1207
+ #
1208
+ # @param with_prefix [Boolean] include prefixes in output
1173
1209
  # @return [String]
1174
1210
  def units(with_prefix: true)
1175
1211
  return '' if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY
1212
+
1176
1213
  output_numerator = ['1']
1177
1214
  output_denominator = []
1178
1215
  num = @numerator.clone.compact
1179
1216
  den = @denominator.clone.compact
1180
1217
 
1181
1218
  unless num == UNITY_ARRAY
1182
- definitions = num.map { |element| RubyUnits::Unit.definition(element) }
1219
+ definitions = num.map { |element| self.class.definition(element) }
1183
1220
  definitions.reject!(&:prefix?) unless with_prefix
1184
- # there is a bug in jruby 9.1.6.0's implementation of chunk_while
1185
- # see https://github.com/jruby/jruby/issues/4410
1186
- # TODO: fix this after jruby fixes their bug.
1187
- definitions = if definitions.respond_to?(:chunk_while) && RUBY_ENGINE != 'jruby'
1188
- definitions.chunk_while { |defn, _| defn.prefix? }.to_a
1189
- else # chunk_while is new to ruby 2.3+, so fallback to less efficient methods for older ruby
1190
- result = []
1191
- enumerator = definitions.to_enum
1192
- loop do
1193
- first = enumerator.next
1194
- result << (first.prefix? ? [first, enumerator.next] : [first])
1195
- end
1196
- result
1197
- end
1221
+ definitions = definitions.chunk_while { |definition, _| definition.prefix? }.to_a
1198
1222
  output_numerator = definitions.map { |element| element.map(&:display_name).join }
1199
1223
  end
1200
1224
 
1201
1225
  unless den == UNITY_ARRAY
1202
- definitions = den.map { |element| RubyUnits::Unit.definition(element) }
1226
+ definitions = den.map { |element| self.class.definition(element) }
1203
1227
  definitions.reject!(&:prefix?) unless with_prefix
1204
- # there is a bug in jruby 9.1.6.0's implementation of chunk_while
1205
- # see https://github.com/jruby/jruby/issues/4410
1206
- # TODO: fix this after jruby fixes their bug.
1207
- definitions = if definitions.respond_to?(:chunk_while) && RUBY_ENGINE != 'jruby'
1208
- definitions.chunk_while { |defn, _| defn.prefix? }.to_a
1209
- else # chunk_while is new to ruby 2.3+, so fallback to less efficient methods for older ruby
1210
- result = []
1211
- enumerator = definitions.to_enum
1212
- loop do
1213
- first = enumerator.next
1214
- result << (first.prefix? ? [first, enumerator.next] : [first])
1215
- end
1216
- result
1217
- end
1228
+ definitions = definitions.chunk_while { |definition, _| definition.prefix? }.to_a
1218
1229
  output_denominator = definitions.map { |element| element.map(&:display_name).join }
1219
1230
  end
1220
1231
 
@@ -1226,13 +1237,14 @@ module RubyUnits
1226
1237
  .uniq
1227
1238
  .map { |x| [x, output_denominator.count(x)] }
1228
1239
  .map { |element, power| (element.to_s.strip + (power > 1 ? "^#{power}" : '')) }
1229
- "#{on.join('*')}#{od.empty? ? '' : '/' + od.join('*')}".strip
1240
+ "#{on.join('*')}#{od.empty? ? '' : "/#{od.join('*')}"}".strip
1230
1241
  end
1231
1242
 
1232
1243
  # negates the scalar of the Unit
1233
1244
  # @return [Numeric,Unit]
1234
1245
  def -@
1235
1246
  return -@scalar if unitless?
1247
+
1236
1248
  dup * -1
1237
1249
  end
1238
1250
 
@@ -1240,32 +1252,46 @@ module RubyUnits
1240
1252
  # @return [Numeric,Unit]
1241
1253
  def abs
1242
1254
  return @scalar.abs if unitless?
1243
- RubyUnits::Unit.new(@scalar.abs, @numerator, @denominator)
1255
+
1256
+ self.class.new(@scalar.abs, @numerator, @denominator)
1244
1257
  end
1245
1258
 
1246
1259
  # ceil of a unit
1247
1260
  # @return [Numeric,Unit]
1248
- def ceil
1249
- return @scalar.ceil if unitless?
1250
- RubyUnits::Unit.new(@scalar.ceil, @numerator, @denominator)
1261
+ def ceil(*args)
1262
+ return @scalar.ceil(*args) if unitless?
1263
+
1264
+ self.class.new(@scalar.ceil(*args), @numerator, @denominator)
1251
1265
  end
1252
1266
 
1253
1267
  # @return [Numeric,Unit]
1254
- def floor
1255
- return @scalar.floor if unitless?
1256
- RubyUnits::Unit.new(@scalar.floor, @numerator, @denominator)
1268
+ def floor(*args)
1269
+ return @scalar.floor(*args) if unitless?
1270
+
1271
+ self.class.new(@scalar.floor(*args), @numerator, @denominator)
1257
1272
  end
1258
1273
 
1274
+ # Round the unit according to the rules of the scalar's class. Call this
1275
+ # with the arguments appropriate for the scalar's class (e.g., Integer,
1276
+ # Rational, etc..). Because unit conversions can often result in Rational
1277
+ # scalars (to preserve precision), it may be advisable to use +to_s+ to
1278
+ # format output instead of using +round+.
1279
+ # @example
1280
+ # RubyUnits::Unit.new('21870 mm/min').convert_to('m/min').round(1) #=> 2187/100 m/min
1281
+ # RubyUnits::Unit.new('21870 mm/min').convert_to('m/min').to_s('%0.1f') #=> 21.9 m/min
1282
+ #
1259
1283
  # @return [Numeric,Unit]
1260
- def round(ndigits = 0)
1261
- return @scalar.round(ndigits) if unitless?
1262
- RubyUnits::Unit.new(@scalar.round(ndigits), @numerator, @denominator)
1284
+ def round(*args, **kwargs)
1285
+ return @scalar.round(*args, **kwargs) if unitless?
1286
+
1287
+ self.class.new(@scalar.round(*args, **kwargs), @numerator, @denominator)
1263
1288
  end
1264
1289
 
1265
1290
  # @return [Numeric, Unit]
1266
- def truncate
1267
- return @scalar.truncate if unitless?
1268
- RubyUnits::Unit.new(@scalar.truncate, @numerator, @denominator)
1291
+ def truncate(*args)
1292
+ return @scalar.truncate(*args) if unitless?
1293
+
1294
+ self.class.new(@scalar.truncate(*args), @numerator, @denominator)
1269
1295
  end
1270
1296
 
1271
1297
  # returns next unit in a range. '1 mm'.to_unit.succ #=> '2 mm'.to_unit
@@ -1274,7 +1300,8 @@ module RubyUnits
1274
1300
  # @raise [ArgumentError] when scalar is not equal to an integer
1275
1301
  def succ
1276
1302
  raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
1277
- RubyUnits::Unit.new(@scalar.to_i.succ, @numerator, @denominator)
1303
+
1304
+ self.class.new(@scalar.to_i.succ, @numerator, @denominator)
1278
1305
  end
1279
1306
 
1280
1307
  alias next succ
@@ -1285,7 +1312,8 @@ module RubyUnits
1285
1312
  # @raise [ArgumentError] when scalar is not equal to an integer
1286
1313
  def pred
1287
1314
  raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
1288
- RubyUnits::Unit.new(@scalar.to_i.pred, @numerator, @denominator)
1315
+
1316
+ self.class.new(@scalar.to_i.pred, @numerator, @denominator)
1289
1317
  end
1290
1318
 
1291
1319
  # Tries to make a Time object from current unit. Assumes the current unit hold the duration in seconds from the epoch.
@@ -1298,7 +1326,7 @@ module RubyUnits
1298
1326
 
1299
1327
  # convert a duration to a DateTime. This will work so long as the duration is the duration from the zero date
1300
1328
  # defined by DateTime
1301
- # @return [DateTime]
1329
+ # @return [::DateTime]
1302
1330
  def to_datetime
1303
1331
  DateTime.new!(convert_to('d').scalar)
1304
1332
  end
@@ -1325,11 +1353,11 @@ module RubyUnits
1325
1353
  def before(time_point = ::Time.now)
1326
1354
  case time_point
1327
1355
  when Time, Date, DateTime
1328
- return (begin
1329
- time_point - self
1330
- rescue
1331
- time_point.to_datetime - self
1332
- end)
1356
+ (begin
1357
+ time_point - self
1358
+ rescue StandardError
1359
+ time_point.to_datetime - self
1360
+ end)
1333
1361
  else
1334
1362
  raise ArgumentError, 'Must specify a Time, Date, or DateTime'
1335
1363
  end
@@ -1344,9 +1372,9 @@ module RubyUnits
1344
1372
  def since(time_point)
1345
1373
  case time_point
1346
1374
  when Time
1347
- (Time.now - time_point).to_unit('s').convert_to(self)
1375
+ self.class.new(::Time.now - time_point, 'second').convert_to(self)
1348
1376
  when DateTime, Date
1349
- (DateTime.now - time_point).to_unit('d').convert_to(self)
1377
+ self.class.new(::DateTime.now - time_point, 'day').convert_to(self)
1350
1378
  else
1351
1379
  raise ArgumentError, 'Must specify a Time, Date, or DateTime'
1352
1380
  end
@@ -1358,9 +1386,9 @@ module RubyUnits
1358
1386
  def until(time_point)
1359
1387
  case time_point
1360
1388
  when Time
1361
- (time_point - Time.now).to_unit('s').convert_to(self)
1389
+ self.class.new(time_point - ::Time.now, 'second').convert_to(self)
1362
1390
  when DateTime, Date
1363
- (time_point - DateTime.now).to_unit('d').convert_to(self)
1391
+ self.class.new(time_point - ::DateTime.now, 'day').convert_to(self)
1364
1392
  else
1365
1393
  raise ArgumentError, 'Must specify a Time, Date, or DateTime'
1366
1394
  end
@@ -1374,10 +1402,10 @@ module RubyUnits
1374
1402
  case time_point
1375
1403
  when Time, DateTime, Date
1376
1404
  (begin
1377
- time_point + self
1378
- rescue
1379
- time_point.to_datetime + self
1380
- end)
1405
+ time_point + self
1406
+ rescue StandardError
1407
+ time_point.to_datetime + self
1408
+ end)
1381
1409
  else
1382
1410
  raise ArgumentError, 'Must specify a Time, Date, or DateTime'
1383
1411
  end
@@ -1388,27 +1416,29 @@ module RubyUnits
1388
1416
 
1389
1417
  # automatically coerce objects to units when possible
1390
1418
  # if an object defines a 'to_unit' method, it will be coerced using that method
1391
- # @param [Object, #to_unit]
1419
+ # @param other [Object, #to_unit]
1392
1420
  # @return [Array]
1393
1421
  def coerce(other)
1394
1422
  return [other.to_unit, self] if other.respond_to? :to_unit
1423
+
1395
1424
  case other
1396
1425
  when Unit
1397
1426
  [other, self]
1398
1427
  else
1399
- [RubyUnits::Unit.new(other), self]
1428
+ [self.class.new(other), self]
1400
1429
  end
1401
1430
  end
1402
1431
 
1403
1432
  # returns a new unit that has been scaled to be more in line with typical usage.
1404
1433
  def best_prefix
1405
1434
  return to_base if scalar.zero?
1435
+
1406
1436
  best_prefix = if kind == :information
1407
- @@prefix_values.key(2**((Math.log(base_scalar, 2) / 10.0).floor * 10))
1437
+ @@prefix_values.key(2**((::Math.log(base_scalar, 2) / 10.0).floor * 10))
1408
1438
  else
1409
- @@prefix_values.key(10**((Math.log10(base_scalar) / 3.0).floor * 3))
1439
+ @@prefix_values.key(10**((::Math.log10(base_scalar) / 3.0).floor * 3))
1410
1440
  end
1411
- to(RubyUnits::Unit.new(@@prefix_map.key(best_prefix) + units(with_prefix: false)))
1441
+ to(self.class.new(@@prefix_map.key(best_prefix) + units(with_prefix: false)))
1412
1442
  end
1413
1443
 
1414
1444
  # override hash method so objects with same values are considered equal
@@ -1445,18 +1475,19 @@ module RubyUnits
1445
1475
  # @raise [ArgumentError] when exponent associated with a unit is > 20 or < -20
1446
1476
  def unit_signature_vector
1447
1477
  return to_base.unit_signature_vector unless base?
1448
- vector = Array.new(SIGNATURE_VECTOR.size, 0)
1478
+ vector = ::Array.new(SIGNATURE_VECTOR.size, 0)
1449
1479
  # it's possible to have a kind that misses the array... kinds like :counting
1450
1480
  # are more like prefixes, so don't use them to calculate the vector
1451
- @numerator.map { |element| RubyUnits::Unit.definition(element) }.each do |definition|
1481
+ @numerator.map { |element| self.class.definition(element) }.each do |definition|
1452
1482
  index = SIGNATURE_VECTOR.index(definition.kind)
1453
1483
  vector[index] += 1 if index
1454
1484
  end
1455
- @denominator.map { |element| RubyUnits::Unit.definition(element) }.each do |definition|
1485
+ @denominator.map { |element| self.class.definition(element) }.each do |definition|
1456
1486
  index = SIGNATURE_VECTOR.index(definition.kind)
1457
1487
  vector[index] -= 1 if index
1458
1488
  end
1459
1489
  raise ArgumentError, 'Power out of range (-20 < net power of a unit < 20)' if vector.any? { |x| x.abs >= 20 }
1490
+
1460
1491
  vector
1461
1492
  end
1462
1493
 
@@ -1478,8 +1509,9 @@ module RubyUnits
1478
1509
  # @return [Array]
1479
1510
  def unit_signature
1480
1511
  return @signature unless @signature.nil?
1512
+
1481
1513
  vector = unit_signature_vector
1482
- vector.each_with_index { |item, index| vector[index] = item * 20**index }
1514
+ vector.each_with_index { |item, index| vector[index] = item * (20**index) }
1483
1515
  @signature = vector.inject(0) { |acc, elem| acc + elem }
1484
1516
  @signature
1485
1517
  end
@@ -1495,7 +1527,7 @@ module RubyUnits
1495
1527
  # "GPa" -- creates a unit with scalar 1 with units 'GPa'
1496
1528
  # 6'4" -- recognized as 6 feet + 4 inches
1497
1529
  # 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
1498
- # @return [nil | Unit]
1530
+ # @return [nil,RubyUnits::Unit]
1499
1531
  # @todo This should either be a separate class or at least a class method
1500
1532
  def parse(passed_unit_string = '0')
1501
1533
  unit_string = passed_unit_string.dup
@@ -1506,7 +1538,7 @@ module RubyUnits
1506
1538
 
1507
1539
  if defined?(Complex) && unit_string =~ COMPLEX_NUMBER
1508
1540
  real, imaginary, unit_s = unit_string.scan(COMPLEX_REGEX)[0]
1509
- result = RubyUnits::Unit.new(unit_s || '1') * Complex(real.to_f, imaginary.to_f)
1541
+ result = self.class.new(unit_s || '1') * Complex(real.to_f, imaginary.to_f)
1510
1542
  copy(result)
1511
1543
  return
1512
1544
  end
@@ -1515,19 +1547,16 @@ module RubyUnits
1515
1547
  sign, proper, numerator, denominator, unit_s = unit_string.scan(RATIONAL_REGEX)[0]
1516
1548
  sign = sign == '-' ? -1 : 1
1517
1549
  rational = sign * (proper.to_i + Rational(numerator.to_i, denominator.to_i))
1518
- result = RubyUnits::Unit.new(unit_s || '1') * rational
1550
+ result = self.class.new(unit_s || '1') * rational
1519
1551
  copy(result)
1520
1552
  return
1521
1553
  end
1522
1554
 
1523
1555
  unit_string =~ NUMBER_REGEX
1524
- unit = @@cached_units[Regexp.last_match(2)]
1525
- mult = begin
1526
- (Regexp.last_match(1).empty? ? 1.0 : Regexp.last_match(1).to_f)
1527
- rescue
1528
- 1.0
1529
- end
1556
+ unit = self.class.cached.get(Regexp.last_match(2))
1557
+ mult = Regexp.last_match(1).nil? ? 1.0 : Regexp.last_match(1).to_f
1530
1558
  mult = mult.to_int if mult.to_int == mult
1559
+
1531
1560
  if unit
1532
1561
  copy(unit)
1533
1562
  @scalar *= mult
@@ -1541,13 +1570,14 @@ module RubyUnits
1541
1570
  # ... and then strip the remaining brackets for x*y*z
1542
1571
  unit_string.gsub!(/[<>]/, '')
1543
1572
 
1544
- if unit_string =~ /:/
1573
+ if unit_string =~ TIME_REGEX
1545
1574
  hours, minutes, seconds, microseconds = unit_string.scan(TIME_REGEX)[0]
1546
1575
  raise ArgumentError, 'Invalid Duration' if [hours, minutes, seconds, microseconds].all?(&:nil?)
1547
- result = RubyUnits::Unit.new("#{hours || 0} h") +
1548
- RubyUnits::Unit.new("#{minutes || 0} minutes") +
1549
- RubyUnits::Unit.new("#{seconds || 0} seconds") +
1550
- RubyUnits::Unit.new("#{microseconds || 0} usec")
1576
+
1577
+ result = self.class.new("#{hours || 0} h") +
1578
+ self.class.new("#{minutes || 0} minutes") +
1579
+ self.class.new("#{seconds || 0} seconds") +
1580
+ self.class.new("#{microseconds || 0} usec")
1551
1581
  copy(result)
1552
1582
  return
1553
1583
  end
@@ -1556,7 +1586,7 @@ module RubyUnits
1556
1586
  # feet -- 6'5"
1557
1587
  feet, inches = unit_string.scan(FEET_INCH_REGEX)[0]
1558
1588
  if feet && inches
1559
- result = RubyUnits::Unit.new("#{feet} ft") + RubyUnits::Unit.new("#{inches} inches")
1589
+ result = self.class.new("#{feet} ft") + self.class.new("#{inches} inches")
1560
1590
  copy(result)
1561
1591
  return
1562
1592
  end
@@ -1564,7 +1594,7 @@ module RubyUnits
1564
1594
  # weight -- 8 lbs 12 oz
1565
1595
  pounds, oz = unit_string.scan(LBS_OZ_REGEX)[0]
1566
1596
  if pounds && oz
1567
- result = RubyUnits::Unit.new("#{pounds} lbs") + RubyUnits::Unit.new("#{oz} oz")
1597
+ result = self.class.new("#{pounds} lbs") + self.class.new("#{oz} oz")
1568
1598
  copy(result)
1569
1599
  return
1570
1600
  end
@@ -1572,7 +1602,7 @@ module RubyUnits
1572
1602
  # stone -- 3 stone 5, 2 stone, 14 stone 3 pounds, etc.
1573
1603
  stone, pounds = unit_string.scan(STONE_LB_REGEX)[0]
1574
1604
  if stone && pounds
1575
- result = RubyUnits::Unit.new("#{stone} stone") + RubyUnits::Unit.new("#{pounds} lbs")
1605
+ result = self.class.new("#{stone} stone") + self.class.new("#{pounds} lbs")
1576
1606
  copy(result)
1577
1607
  return
1578
1608
  end
@@ -1580,6 +1610,7 @@ module RubyUnits
1580
1610
  # more than one per. I.e., "1 m/s/s"
1581
1611
  raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.count('/') > 1
1582
1612
  raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string =~ /\s[02-9]/
1613
+
1583
1614
  @scalar, top, bottom = unit_string.scan(UNIT_STRING_REGEX)[0] # parse the string into parts
1584
1615
  top.scan(TOP_REGEX).each do |item|
1585
1616
  n = item[1].to_i
@@ -1612,12 +1643,12 @@ module RubyUnits
1612
1643
 
1613
1644
  @numerator ||= UNITY_ARRAY
1614
1645
  @denominator ||= UNITY_ARRAY
1615
- @numerator = top.scan(RubyUnits::Unit.unit_match_regex).delete_if(&:empty?).compact if top
1616
- @denominator = bottom.scan(RubyUnits::Unit.unit_match_regex).delete_if(&:empty?).compact if bottom
1646
+ @numerator = top.scan(self.class.unit_match_regex).delete_if(&:empty?).compact if top
1647
+ @denominator = bottom.scan(self.class.unit_match_regex).delete_if(&:empty?).compact if bottom
1617
1648
 
1618
1649
  # eliminate all known terms from this string. This is a quick check to see if the passed unit
1619
1650
  # contains terms that are not defined.
1620
- used = "#{top} #{bottom}".to_s.gsub(RubyUnits::Unit.unit_match_regex, '').gsub(%r{[\d\*, "'_^\/\$]}, '')
1651
+ used = "#{top} #{bottom}".to_s.gsub(self.class.unit_match_regex, '').gsub(%r{[\d*, "'_^/$]}, '')
1621
1652
  raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") unless used.empty?
1622
1653
 
1623
1654
  @numerator = @numerator.map do |item|