ruby-units 2.3.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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|