ruby-units 2.4.1 → 4.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,77 +1,106 @@
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
25
- @@definitions = {}
26
- @@prefix_values = {}
27
- @@prefix_map = {}
28
- @@unit_map = {}
29
- @@unit_values = {}
30
- @@unit_regex = nil
31
- @@unit_match_regex = nil
3
+ # Copyright 2006-2023
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
+ class << self
26
+ # return a list of all defined units
27
+ # @return [Hash{Symbol=>RubyUnits::Units::Definition}]
28
+ attr_accessor :definitions
29
+
30
+ # @return [Hash{Symbol => String}] the list of units and their prefixes
31
+ attr_accessor :prefix_values
32
+
33
+ # @return [Hash{Symbol => String}]
34
+ attr_accessor :prefix_map
35
+
36
+ # @return [Hash{Symbol => String}]
37
+ attr_accessor :unit_map
38
+
39
+ # @return [Hash{Symbol => String}]
40
+ attr_accessor :unit_values
41
+
42
+ # @return [Hash{Integer => Symbol}]
43
+ attr_reader :kinds
44
+ end
45
+ self.definitions = {}
46
+ self.prefix_values = {}
47
+ self.prefix_map = {}
48
+ self.unit_map = {}
49
+ self.unit_values = {}
50
+ @unit_regex = nil
51
+ @unit_match_regex = nil
32
52
  UNITY = '<1>'.freeze
33
53
  UNITY_ARRAY = [UNITY].freeze
54
+
55
+ SIGN_REGEX = /(?:[+-])?/.freeze # +, -, or nothing
56
+
57
+ # regex for matching an integer number but not a fraction
58
+ INTEGER_DIGITS_REGEX = %r{(?<!/)\d+(?!/)}.freeze # 1, 2, 3, but not 1/2 or -1
59
+ INTEGER_REGEX = /(#{SIGN_REGEX}#{INTEGER_DIGITS_REGEX})/.freeze # -1, 1, +1, but not 1/2
60
+ UNSIGNED_INTEGER_REGEX = /((?<!-)#{INTEGER_DIGITS_REGEX})/.freeze # 1, 2, 3, but not -1
61
+ DIGITS_REGEX = /\d+/.freeze # 0, 1, 2, 3
62
+ DECIMAL_REGEX = /\d*[.]?#{DIGITS_REGEX}/.freeze # 1, 0.1, .1
63
+ # Rational number, including improper fractions: 1 2/3, -1 2/3, 5/3, etc.
64
+ RATIONAL_NUMBER = %r{\(?(?:(?<proper>#{SIGN_REGEX}#{DECIMAL_REGEX})[ -])?(?<numerator>#{SIGN_REGEX}#{DECIMAL_REGEX})/(?<denominator>#{SIGN_REGEX}#{DECIMAL_REGEX})\)?} # 1 2/3, -1 2/3, 5/3, 1-2/3, (1/2) etc.
65
+ # Scientific notation: 1, -1, +1, 1.2, +1.2, -1.2, 123.4E5, +123.4e5,
66
+ # -123.4E+5, -123.4e-5, etc.
67
+ SCI_NUMBER = /([+-]?\d*[.]?\d+(?:[Ee][+-]?\d+(?![.]))?)/
34
68
  # ideally we would like to generate this regex from the alias for a 'feet'
35
69
  # and 'inches', but they aren't defined at the point in the code where we
36
70
  # 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
71
+ FEET_INCH_UNITS_REGEX = /(?:'|ft|feet)\s*(?<inches>#{RATIONAL_NUMBER}|#{SCI_NUMBER})\s*(?:"|in|inch(?:es)?)/.freeze
72
+ FEET_INCH_REGEX = /(?<feet>#{INTEGER_REGEX})\s*#{FEET_INCH_UNITS_REGEX}/.freeze
39
73
  # ideally we would like to generate this regex from the alias for a 'pound'
40
74
  # and 'ounce', but they aren't defined at the point in the code where we
41
75
  # 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
76
+ LBS_OZ_UNIT_REGEX = /(?:#|lbs?|pounds?|pound-mass)+[\s,]*(?<oz>#{RATIONAL_NUMBER}|#{UNSIGNED_INTEGER_REGEX})\s*(?:ozs?|ounces?)/.freeze
77
+ LBS_OZ_REGEX = /(?<pounds>#{INTEGER_REGEX})\s*#{LBS_OZ_UNIT_REGEX}/.freeze
44
78
  # ideally we would like to generate this regex from the alias for a 'stone'
45
79
  # and 'pound', but they aren't defined at the point in the code where we
46
80
  # need this regex. also note that the plural of 'stone' is still 'stone',
47
81
  # 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
82
+ STONE_LB_UNIT_REGEX = /(?:sts?|stones?)+[\s,]*(?<pounds>#{RATIONAL_NUMBER}|#{UNSIGNED_INTEGER_REGEX})\s*(?:#|lbs?|pounds?|pound-mass)*/.freeze
83
+ STONE_LB_REGEX = /(?<stone>#{INTEGER_REGEX})\s*#{STONE_LB_UNIT_REGEX}/.freeze
50
84
  # 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
85
+ TIME_REGEX = /(?<hour>\d+):(?<min>\d+):?(?:(?<sec>\d+))?(?:[.](?<msec>\d+))?/.freeze
57
86
  # Complex numbers: 1+2i, 1.0+2.0i, -1-1i, etc.
58
- COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/.freeze
87
+ COMPLEX_NUMBER = /(?<real>#{SCI_NUMBER})?(?<imaginary>#{SCI_NUMBER})i\b/.freeze
59
88
  # Any Complex, Rational, or scientific number
60
89
  ANY_NUMBER = /(#{COMPLEX_NUMBER}|#{RATIONAL_NUMBER}|#{SCI_NUMBER})/.freeze
61
90
  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
91
+ NUMBER_REGEX = /(?<scalar>#{SCI_NUMBER}*)\s*(?<unit>.+)?/.freeze # a number followed by a unit
92
+ UNIT_STRING_REGEX = %r{#{SCI_NUMBER}*\s*([^/]*)/*(.+)*}.freeze
93
+ TOP_REGEX = /([^ *]+)(?:\^|\*\*)([\d-]+)/.freeze
65
94
  BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/.freeze
66
95
  NUMBER_UNIT_REGEX = /#{SCI_NUMBER}?(.*)/.freeze
67
- COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/.freeze
68
- RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(.+)?/.freeze
96
+ COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(?<unit>.+)?/.freeze
97
+ RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(?<unit>.+)?/.freeze
69
98
  KELVIN = ['<kelvin>'].freeze
70
99
  FAHRENHEIT = ['<fahrenheit>'].freeze
71
100
  RANKINE = ['<rankine>'].freeze
72
101
  CELSIUS = ['<celsius>'].freeze
73
- @@temp_regex = nil
74
- SIGNATURE_VECTOR = %i[
102
+ @temp_regex = nil
103
+ SIGNATURE_VECTOR = %i[
75
104
  length
76
105
  time
77
106
  temperature
@@ -83,74 +112,72 @@ module RubyUnits
83
112
  information
84
113
  angle
85
114
  ].freeze
86
- @@kinds = {
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,
115
+ @kinds = {
116
+ -312_078 => :elastance,
117
+ -312_058 => :resistance,
118
+ -312_038 => :inductance,
119
+ -152_040 => :magnetism,
120
+ -152_038 => :magnetism,
121
+ -152_058 => :potential,
122
+ -7997 => :specific_volume,
123
+ -79 => :snap,
124
+ -59 => :jolt,
125
+ -39 => :acceleration,
126
+ -38 => :radiation,
127
+ -20 => :frequency,
128
+ -19 => :speed,
129
+ -18 => :viscosity,
130
+ -17 => :volumetric_flow,
131
+ -1 => :wavenumber,
132
+ 0 => :unitless,
133
+ 1 => :length,
134
+ 2 => :area,
135
+ 3 => :volume,
136
+ 20 => :time,
137
+ 400 => :temperature,
138
+ 7941 => :yank,
139
+ 7942 => :power,
140
+ 7959 => :pressure,
141
+ 7962 => :energy,
142
+ 7979 => :viscosity,
143
+ 7961 => :force,
144
+ 7981 => :momentum,
145
+ 7982 => :angular_momentum,
146
+ 7997 => :density,
147
+ 7998 => :area_density,
148
+ 8000 => :mass,
149
+ 152_020 => :radiation_exposure,
150
+ 159_999 => :magnetism,
151
+ 160_000 => :current,
152
+ 160_020 => :charge,
153
+ 312_058 => :conductance,
154
+ 312_078 => :capacitance,
155
+ 3_199_980 => :activity,
156
+ 3_199_997 => :molar_concentration,
157
+ 3_200_000 => :substance,
158
+ 63_999_998 => :illuminance,
159
+ 64_000_000 => :luminous_power,
131
160
  1_280_000_000 => :currency,
132
- 25_600_000_000 => :information,
161
+ 25_600_000_000 => :information,
133
162
  511_999_999_980 => :angular_velocity,
134
163
  512_000_000_000 => :angle
135
164
  }.freeze
136
- @@cached_units = {}
137
- @@base_unit_cache = {}
138
165
 
139
166
  # Class Methods
140
167
 
141
168
  # setup internal arrays and hashes
142
- # @return [true]
169
+ # @return [Boolean]
143
170
  def self.setup
144
171
  clear_cache
145
- @@prefix_values = {}
146
- @@prefix_map = {}
147
- @@unit_values = {}
148
- @@unit_map = {}
149
- @@unit_regex = nil
150
- @@unit_match_regex = nil
151
- @@prefix_regex = nil
152
-
153
- @@definitions.each_value do |definition|
172
+ self.prefix_values = {}
173
+ self.prefix_map = {}
174
+ self.unit_map = {}
175
+ self.unit_values = {}
176
+ @unit_regex = nil
177
+ @unit_match_regex = nil
178
+ @prefix_regex = nil
179
+
180
+ definitions.each_value do |definition|
154
181
  use_definition(definition)
155
182
  end
156
183
 
@@ -162,7 +189,7 @@ module RubyUnits
162
189
  # @param [String] unit
163
190
  # @return [Boolean]
164
191
  def self.defined?(unit)
165
- definitions.values.any? { |d| d.aliases.include?(unit) }
192
+ definitions.values.any? { _1.aliases.include?(unit) }
166
193
  end
167
194
 
168
195
  # return the unit definition for a unit
@@ -170,17 +197,11 @@ module RubyUnits
170
197
  # @return [RubyUnits::Unit::Definition, nil]
171
198
  def self.definition(unit_name)
172
199
  unit = unit_name =~ /^<.+>$/ ? unit_name : "<#{unit_name}>"
173
- @@definitions[unit]
174
- end
175
-
176
- # return a list of all defined units
177
- # @return [Array<RubyUnits::Units::Definition>]
178
- def self.definitions
179
- @@definitions
200
+ definitions[unit]
180
201
  end
181
202
 
182
203
  # @param [RubyUnits::Unit::Definition, String] unit_definition
183
- # @param [Block] block
204
+ # @param [Proc] block
184
205
  # @return [RubyUnits::Unit::Definition]
185
206
  # @raise [ArgumentError] when passed a non-string if using the block form
186
207
  # Unpack a unit definition and add it to the array of defined units
@@ -195,7 +216,8 @@ module RubyUnits
195
216
  # RubyUnits::Unit.define(unit_definition)
196
217
  def self.define(unit_definition, &block)
197
218
  if block_given?
198
- raise ArgumentError, 'When using the block form of RubyUnits::Unit.define, pass the name of the unit' unless unit_definition.instance_of?(String)
219
+ raise ArgumentError, 'When using the block form of RubyUnits::Unit.define, pass the name of the unit' unless unit_definition.is_a?(String)
220
+
199
221
  unit_definition = RubyUnits::Unit::Definition.new(unit_definition, &block)
200
222
  end
201
223
  definitions[unit_definition.name] = unit_definition
@@ -206,7 +228,7 @@ module RubyUnits
206
228
  # Get the definition for a unit and allow it to be redefined
207
229
  #
208
230
  # @param [String] name Name of unit to redefine
209
- # @param [Block] _block
231
+ # @param [Proc] _block
210
232
  # @raise [ArgumentError] if a block is not given
211
233
  # @yieldparam [RubyUnits::Unit::Definition] the definition of the unit being
212
234
  # redefined
@@ -218,7 +240,7 @@ module RubyUnits
218
240
  raise(ArgumentError, "'#{name}' Unit not recognized") unless unit_definition
219
241
 
220
242
  yield unit_definition
221
- @@definitions.delete("<#{name}>")
243
+ definitions.delete("<#{name}>")
222
244
  define(unit_definition)
223
245
  setup
224
246
  end
@@ -228,26 +250,28 @@ module RubyUnits
228
250
  # @param unit [String] name of unit to undefine
229
251
  # @return (see RubyUnits::Unit.setup)
230
252
  def self.undefine!(unit)
231
- @@definitions.delete("<#{unit}>")
253
+ definitions.delete("<#{unit}>")
232
254
  setup
233
255
  end
234
256
 
235
- # @return [Hash]
257
+ # Unit cache
258
+ #
259
+ # @return [RubyUnits::Cache]
236
260
  def self.cached
237
- @@cached_units
261
+ @cached ||= RubyUnits::Cache.new
238
262
  end
239
263
 
240
- # @return [true]
264
+ # @return [Boolean]
241
265
  def self.clear_cache
242
- @@cached_units = {}
243
- @@base_unit_cache = {}
266
+ cached.clear
267
+ base_unit_cache.clear
244
268
  new(1)
245
269
  true
246
270
  end
247
271
 
248
- # @return [Hash]
272
+ # @return [RubyUnits::Cache]
249
273
  def self.base_unit_cache
250
- @@base_unit_cache
274
+ @base_unit_cache ||= RubyUnits::Cache.new
251
275
  end
252
276
 
253
277
  # @example parse strings
@@ -269,12 +293,12 @@ module RubyUnits
269
293
  num.delete(UNITY)
270
294
  den.delete(UNITY)
271
295
 
272
- combined = Hash.new(0)
296
+ combined = ::Hash.new(0)
273
297
 
274
298
  [[num, 1], [den, -1]].each do |array, increment|
275
299
  array.chunk_while { |elt_before, _| definition(elt_before).prefix? }
276
300
  .to_a
277
- .each { |unit| combined[unit] += increment }
301
+ .each { combined[_1] += increment }
278
302
  end
279
303
 
280
304
  num = []
@@ -301,11 +325,12 @@ module RubyUnits
301
325
  # return an array of base units
302
326
  # @return [Array]
303
327
  def self.base_units
304
- @@base_units ||= @@definitions.dup.delete_if { |_, defn| !defn.base? }.keys.map { |u| new(u) }
328
+ @base_units ||= definitions.dup.select { |_, definition| definition.base? }.keys.map { new(_1) }
305
329
  end
306
330
 
307
- # parse a string consisting of a number and a unit string
331
+ # Parse a string consisting of a number and a unit string
308
332
  # NOTE: This does not properly handle units formatted like '12mg/6ml'
333
+ #
309
334
  # @param [String] string
310
335
  # @return [Array(Numeric, String)] consisting of [number, "unit"]
311
336
  def self.parse_into_numbers_and_units(string)
@@ -335,27 +360,27 @@ module RubyUnits
335
360
  # Unit names are reverse sorted by length so the regexp matcher will prefer longer and more specific names
336
361
  # @return [String]
337
362
  def self.unit_regex
338
- @@unit_regex ||= @@unit_map.keys.sort_by { |unit_name| [unit_name.length, unit_name] }.reverse.join('|')
363
+ @unit_regex ||= unit_map.keys.sort_by { [_1.length, _1] }.reverse.join('|')
339
364
  end
340
365
 
341
366
  # return a regex used to match units
342
- # @return [RegExp]
367
+ # @return [Regexp]
343
368
  def self.unit_match_regex
344
- @@unit_match_regex ||= /(#{prefix_regex})??(#{unit_regex})\b/
369
+ @unit_match_regex ||= /(#{prefix_regex})??(#{unit_regex})\b/
345
370
  end
346
371
 
347
372
  # return a regexp fragment used to match prefixes
348
373
  # @return [String]
349
374
  # @private
350
375
  def self.prefix_regex
351
- @@prefix_regex ||= @@prefix_map.keys.sort_by { |prefix| [prefix.length, prefix] }.reverse.join('|')
376
+ @prefix_regex ||= prefix_map.keys.sort_by { [_1.length, _1] }.reverse.join('|')
352
377
  end
353
378
 
354
379
  # Generates (and memoizes) a regexp matching any of the temperature units or their aliases.
355
380
  #
356
- # @return [RegExp]
381
+ # @return [Regexp]
357
382
  def self.temp_regex
358
- @@temp_regex ||= begin
383
+ @temp_regex ||= begin
359
384
  temp_units = %w[tempK tempC tempF tempR degK degC degF degR]
360
385
  aliases = temp_units.map do |unit|
361
386
  d = definition(unit)
@@ -370,19 +395,19 @@ module RubyUnits
370
395
  #
371
396
  # @param definition [RubyUnits::Unit::Definition]
372
397
  def self.use_definition(definition)
373
- @@unit_match_regex = nil # invalidate the unit match regex
374
- @@temp_regex = nil # invalidate the temp regex
398
+ @unit_match_regex = nil # invalidate the unit match regex
399
+ @temp_regex = nil # invalidate the temp regex
375
400
  if definition.prefix?
376
- @@prefix_values[definition.name] = definition.scalar
377
- definition.aliases.each { |alias_name| @@prefix_map[alias_name] = definition.name }
378
- @@prefix_regex = nil # invalidate the prefix regex
401
+ prefix_values[definition.name] = definition.scalar
402
+ definition.aliases.each { prefix_map[_1] = definition.name }
403
+ @prefix_regex = nil # invalidate the prefix regex
379
404
  else
380
- @@unit_values[definition.name] = {}
381
- @@unit_values[definition.name][:scalar] = definition.scalar
382
- @@unit_values[definition.name][:numerator] = definition.numerator if definition.numerator
383
- @@unit_values[definition.name][:denominator] = definition.denominator if definition.denominator
384
- definition.aliases.each { |alias_name| @@unit_map[alias_name] = definition.name }
385
- @@unit_regex = nil # invalidate the unit regex
405
+ unit_values[definition.name] = {}
406
+ unit_values[definition.name][:scalar] = definition.scalar
407
+ unit_values[definition.name][:numerator] = definition.numerator if definition.numerator
408
+ unit_values[definition.name][:denominator] = definition.denominator if definition.denominator
409
+ definition.aliases.each { unit_map[_1] = definition.name }
410
+ @unit_regex = nil # invalidate the unit regex
386
411
  end
387
412
  end
388
413
 
@@ -416,20 +441,16 @@ module RubyUnits
416
441
  attr_accessor :unit_name
417
442
 
418
443
  # Used to copy one unit to another
419
- # @param [Unit] from Unit to copy definition from
420
- # @return [Unit]
444
+ # @param from [RubyUnits::Unit] Unit to copy definition from
445
+ # @return [RubyUnits::Unit]
421
446
  def copy(from)
422
- @scalar = from.scalar
423
- @numerator = from.numerator
447
+ @scalar = from.scalar
448
+ @numerator = from.numerator
424
449
  @denominator = from.denominator
425
450
  @base = from.base?
426
- @signature = from.signature
451
+ @signature = from.signature
427
452
  @base_scalar = from.base_scalar
428
- @unit_name = begin
429
- from.unit_name
430
- rescue
431
- nil
432
- end
453
+ @unit_name = from.unit_name
433
454
  self
434
455
  end
435
456
 
@@ -464,26 +485,22 @@ module RubyUnits
464
485
  if options.size == 2
465
486
  # options[0] is the scalar
466
487
  # options[1] is a unit string
467
- begin
468
- cached = @@cached_units[options[1]] * options[0]
469
- copy(cached)
470
- rescue
471
- initialize("#{options[0]} #{(begin
472
- options[1].units
473
- rescue
474
- options[1]
475
- end)}")
488
+ cached = self.class.cached.get(options[1])
489
+ if cached.nil?
490
+ initialize("#{options[0]} #{options[1]}")
491
+ else
492
+ copy(cached * options[0])
476
493
  end
477
494
  return
478
495
  end
479
496
  if options.size == 3
480
497
  options[1] = options[1].join if options[1].is_a?(Array)
481
498
  options[2] = options[2].join if options[2].is_a?(Array)
482
- begin
483
- cached = @@cached_units["#{options[1]}/#{options[2]}"] * options[0]
484
- copy(cached)
485
- rescue
499
+ cached = self.class.cached.get("#{options[1]}/#{options[2]}")
500
+ if cached.nil?
486
501
  initialize("#{options[0]} #{options[1]}/#{options[2]}")
502
+ else
503
+ copy(cached) * options[0]
487
504
  end
488
505
  return
489
506
  end
@@ -519,34 +536,39 @@ module RubyUnits
519
536
  raise ArgumentError, 'Invalid Unit Format'
520
537
  end
521
538
  update_base_scalar
522
- raise ArgumentError, 'Temperatures must not be less than absolute zero' if temperature? && base_scalar < 0
539
+ raise ArgumentError, 'Temperatures must not be less than absolute zero' if temperature? && base_scalar.negative?
523
540
 
524
541
  unary_unit = units || ''
525
542
  if options.first.instance_of?(String)
526
543
  _opt_scalar, opt_units = self.class.parse_into_numbers_and_units(options[0])
527
- unless @@cached_units.keys.include?(opt_units) ||
544
+ if !(self.class.cached.keys.include?(opt_units) ||
528
545
  (opt_units =~ %r{\D/[\d+.]+}) ||
529
- (opt_units =~ %r{(#{self.class.temp_regex})|(#{STONE_LB_UNIT_REGEX})|(#{LBS_OZ_UNIT_REGEX})|(#{FEET_INCH_UNITS_REGEX})|%|(#{TIME_REGEX})|i\s?(.+)?|&plusmn;|\+\/-})
530
- @@cached_units[opt_units] = (scalar == 1 ? self : opt_units.to_unit) if opt_units && !opt_units.empty?
546
+ (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?)
547
+ self.class.cached.set(opt_units, scalar == 1 ? self : opt_units.to_unit)
531
548
  end
532
549
  end
533
- unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /#{self.class.temp_regex}/)
534
- @@cached_units[unary_unit] = (scalar == 1 ? self : unary_unit.to_unit)
550
+ unless self.class.cached.keys.include?(unary_unit) || (unary_unit =~ self.class.temp_regex)
551
+ self.class.cached.set(unary_unit, scalar == 1 ? self : unary_unit.to_unit)
535
552
  end
536
553
  [@scalar, @numerator, @denominator, @base_scalar, @signature, @base].each(&:freeze)
537
- self
554
+ super()
538
555
  end
539
556
 
540
557
  # @todo: figure out how to handle :counting units. This method should probably return :counting instead of :unitless for 'each'
541
558
  # return the kind of the unit (:mass, :length, etc...)
542
559
  # @return [Symbol]
543
560
  def kind
544
- @@kinds[signature]
561
+ self.class.kinds[signature]
545
562
  end
546
563
 
547
- # @return [Unit]
548
- def to_unit
549
- self
564
+ # Convert the unit to a Unit, possibly performing a conversion.
565
+ # > The ability to pass a Unit to convert to was added in v3.0.0 for
566
+ # > consistency with other uses of #to_unit.
567
+ #
568
+ # @param other [RubyUnits::Unit, String] unit to convert to
569
+ # @return [RubyUnits::Unit]
570
+ def to_unit(other = nil)
571
+ other ? convert_to(other) : self
550
572
  end
551
573
 
552
574
  alias unit to_unit
@@ -555,11 +577,12 @@ module RubyUnits
555
577
  # @return [Boolean]
556
578
  def base?
557
579
  return @base if defined? @base
580
+
558
581
  @base = (@numerator + @denominator)
559
582
  .compact
560
583
  .uniq
561
- .map { |unit| self.class.definition(unit) }
562
- .all? { |element| element.unity? || element.base? }
584
+ .map { self.class.definition(_1) }
585
+ .all? { _1.unity? || _1.base? }
563
586
  @base
564
587
  end
565
588
 
@@ -571,8 +594,9 @@ module RubyUnits
571
594
  # @todo this is brittle as it depends on the display_name of a unit, which can be changed
572
595
  def to_base
573
596
  return self if base?
574
- if @@unit_map[units] =~ /\A<(?:temp|deg)[CRF]>\Z/
575
- @signature = @@kinds.key(:temperature)
597
+
598
+ if self.class.unit_map[units] =~ /\A<(?:temp|deg)[CRF]>\Z/
599
+ @signature = self.class.kinds.key(:temperature)
576
600
  base = if temperature?
577
601
  convert_to('tempK')
578
602
  elsif degree?
@@ -581,32 +605,28 @@ module RubyUnits
581
605
  return base
582
606
  end
583
607
 
584
- cached = (begin
585
- (@@base_unit_cache[units] * scalar)
586
- rescue
587
- nil
588
- end)
589
- return cached if cached
608
+ cached_unit = self.class.base_unit_cache.get(units)
609
+ return cached_unit * scalar unless cached_unit.nil?
590
610
 
591
611
  num = []
592
612
  den = []
593
613
  q = Rational(1)
594
614
  @numerator.compact.each do |num_unit|
595
- if @@prefix_values[num_unit]
596
- q *= @@prefix_values[num_unit]
615
+ if self.class.prefix_values[num_unit]
616
+ q *= self.class.prefix_values[num_unit]
597
617
  else
598
- q *= @@unit_values[num_unit][:scalar] if @@unit_values[num_unit]
599
- num << @@unit_values[num_unit][:numerator] if @@unit_values[num_unit] && @@unit_values[num_unit][:numerator]
600
- den << @@unit_values[num_unit][:denominator] if @@unit_values[num_unit] && @@unit_values[num_unit][:denominator]
618
+ q *= self.class.unit_values[num_unit][:scalar] if self.class.unit_values[num_unit]
619
+ num << self.class.unit_values[num_unit][:numerator] if self.class.unit_values[num_unit] && self.class.unit_values[num_unit][:numerator]
620
+ den << self.class.unit_values[num_unit][:denominator] if self.class.unit_values[num_unit] && self.class.unit_values[num_unit][:denominator]
601
621
  end
602
622
  end
603
623
  @denominator.compact.each do |num_unit|
604
- if @@prefix_values[num_unit]
605
- q /= @@prefix_values[num_unit]
624
+ if self.class.prefix_values[num_unit]
625
+ q /= self.class.prefix_values[num_unit]
606
626
  else
607
- q /= @@unit_values[num_unit][:scalar] if @@unit_values[num_unit]
608
- den << @@unit_values[num_unit][:numerator] if @@unit_values[num_unit] && @@unit_values[num_unit][:numerator]
609
- num << @@unit_values[num_unit][:denominator] if @@unit_values[num_unit] && @@unit_values[num_unit][:denominator]
627
+ q /= self.class.unit_values[num_unit][:scalar] if self.class.unit_values[num_unit]
628
+ den << self.class.unit_values[num_unit][:numerator] if self.class.unit_values[num_unit] && self.class.unit_values[num_unit][:numerator]
629
+ num << self.class.unit_values[num_unit][:denominator] if self.class.unit_values[num_unit] && self.class.unit_values[num_unit][:denominator]
610
630
  end
611
631
  end
612
632
 
@@ -614,7 +634,7 @@ module RubyUnits
614
634
  den = den.flatten.compact
615
635
  num = UNITY_ARRAY if num.empty?
616
636
  base = self.class.new(self.class.eliminate_terms(q, num, den))
617
- @@base_unit_cache[units] = base
637
+ self.class.base_unit_cache.set(units, base)
618
638
  base * @scalar
619
639
  end
620
640
 
@@ -635,33 +655,41 @@ module RubyUnits
635
655
  #
636
656
  # @note Rational scalars that are equal to an integer will be represented as integers (i.e, 6/1 => 6, 4/2 => 2, etc..)
637
657
  # @param [Symbol] target_units
658
+ # @param [Float] precision - the precision to use when converting to a rational
638
659
  # @return [String]
639
- def to_s(target_units = nil)
660
+ def to_s(target_units = nil, precision: 0.0001)
640
661
  out = @output[target_units]
641
662
  return out if out
663
+
642
664
  separator = RubyUnits.configuration.separator
643
665
  case target_units
644
666
  when :ft
645
- inches = convert_to('in').scalar.to_int
646
- out = "#{(inches / 12).truncate}\'#{(inches % 12).round}\""
667
+ feet, inches = convert_to('in').scalar.abs.divmod(12)
668
+ improper, frac = inches.divmod(1)
669
+ frac = frac.zero? ? '' : "-#{frac.rationalize(precision)}"
670
+ out = "#{negative? ? '-' : nil}#{feet}'#{improper}#{frac}\""
647
671
  when :lbs
648
- ounces = convert_to('oz').scalar.to_int
649
- out = "#{(ounces / 16).truncate}#{separator}lbs, #{(ounces % 16).round}#{separator}oz"
672
+ pounds, ounces = convert_to('oz').scalar.abs.divmod(16)
673
+ improper, frac = ounces.divmod(1)
674
+ frac = frac.zero? ? '' : "-#{frac.rationalize(precision)}"
675
+ out = "#{negative? ? '-' : nil}#{pounds}#{separator}lbs #{improper}#{frac}#{separator}oz"
650
676
  when :stone
651
- pounds = convert_to('lbs').scalar.to_int
652
- out = "#{(pounds / 14).truncate}#{separator}stone, #{(pounds % 14).round}#{separator}lb"
677
+ stone, pounds = convert_to('lbs').scalar.abs.divmod(14)
678
+ improper, frac = pounds.divmod(1)
679
+ frac = frac.zero? ? '' : "-#{frac.rationalize(precision)}"
680
+ out = "#{negative? ? '-' : nil}#{stone}#{separator}stone #{improper}#{frac}#{separator}lbs"
653
681
  when String
654
682
  out = case target_units.strip
655
683
  when /\A\s*\Z/ # whitespace only
656
684
  ''
657
- when /(%[\-+\.\w#]+)\s*(.+)*/ # format string like '%0.2f in'
685
+ when /(%[-+.\w#]+)\s*(.+)*/ # format string like '%0.2f in'
658
686
  begin
659
687
  if Regexp.last_match(2) # unit specified, need to convert
660
688
  convert_to(Regexp.last_match(2)).to_s(Regexp.last_match(1))
661
689
  else
662
690
  "#{Regexp.last_match(1) % @scalar}#{separator}#{Regexp.last_match(2) || units}".strip
663
691
  end
664
- rescue # parse it like a strftime format string
692
+ rescue StandardError # parse it like a strftime format string
665
693
  (DateTime.new(0) + self).strftime(target_units)
666
694
  end
667
695
  when /(\S+)/ # unit only 'mm' or '1/mm'
@@ -688,6 +716,7 @@ module RubyUnits
688
716
  # @return [String]
689
717
  def inspect(dump = nil)
690
718
  return super() if dump
719
+
691
720
  to_s
692
721
  end
693
722
 
@@ -695,7 +724,7 @@ module RubyUnits
695
724
  # @return [Boolean]
696
725
  # @todo use unit definition to determine if it's a temperature instead of a regex
697
726
  def temperature?
698
- degree? && !(@@unit_map[units] =~ /temp[CFRK]/).nil?
727
+ degree? && units.match?(self.class.temp_regex)
699
728
  end
700
729
 
701
730
  alias is_temperature? temperature?
@@ -713,7 +742,8 @@ module RubyUnits
713
742
  # @return [String] possible values: degC, degF, degR, or degK
714
743
  def temperature_scale
715
744
  return nil unless temperature?
716
- "deg#{@@unit_map[units][/temp([CFRK])/, 1]}"
745
+
746
+ "deg#{self.class.unit_map[units][/temp([CFRK])/, 1]}"
717
747
  end
718
748
 
719
749
  # returns true if no associated units
@@ -726,17 +756,19 @@ module RubyUnits
726
756
  # Compare two Unit objects. Throws an exception if they are not of compatible types.
727
757
  # Comparisons are done based on the value of the unit in base SI units.
728
758
  # @param [Object] other
729
- # @return [-1|0|1|nil]
759
+ # @return [Integer,nil]
730
760
  # @raise [NoMethodError] when other does not define <=>
731
761
  # @raise [ArgumentError] when units are not compatible
732
762
  def <=>(other)
733
763
  raise NoMethodError, "undefined method `<=>' for #{base_scalar.inspect}" unless base_scalar.respond_to?(:<=>)
764
+
734
765
  if other.nil?
735
766
  base_scalar <=> nil
736
767
  elsif !temperature? && other.respond_to?(:zero?) && other.zero?
737
768
  base_scalar <=> 0
738
769
  elsif other.instance_of?(Unit)
739
770
  raise ArgumentError, "Incompatible Units ('#{units}' not compatible with '#{other.units}')" unless self =~ other
771
+
740
772
  base_scalar <=> other.base_scalar
741
773
  else
742
774
  x, y = coerce(other)
@@ -757,6 +789,7 @@ module RubyUnits
757
789
  zero?
758
790
  elsif other.instance_of?(Unit)
759
791
  return false unless self =~ other
792
+
760
793
  base_scalar == other.base_scalar
761
794
  else
762
795
  begin
@@ -768,9 +801,10 @@ module RubyUnits
768
801
  end
769
802
  end
770
803
 
771
- # check to see if units are compatible, but not the scalar part
772
- # this check is done by comparing signatures for performance reasons
773
- # if passed a string, it will create a unit object with the string and then do the comparison
804
+ # Check to see if units are compatible, ignoring the scalar part. This check is done by comparing unit signatures
805
+ # for performance reasons. If passed a string, this will create a [Unit] object with the string and then do the
806
+ # comparison.
807
+ #
774
808
  # @example this permits a syntax like:
775
809
  # unit =~ "mm"
776
810
  # @note if you want to do a regexp comparison of the unit string do this ...
@@ -778,17 +812,12 @@ module RubyUnits
778
812
  # @param [Object] other
779
813
  # @return [Boolean]
780
814
  def =~(other)
781
- case other
782
- when Unit
783
- signature == other.signature
784
- else
785
- begin
786
- x, y = coerce(other)
787
- return x =~ y
788
- rescue ArgumentError
789
- return false
790
- end
791
- end
815
+ return signature == other.signature if other.is_a?(Unit)
816
+
817
+ x, y = coerce(other)
818
+ x =~ y
819
+ rescue ArgumentError # return false when `other` cannot be converted to a [Unit]
820
+ false
792
821
  end
793
822
 
794
823
  alias compatible? =~
@@ -807,9 +836,9 @@ module RubyUnits
807
836
  else
808
837
  begin
809
838
  x, y = coerce(other)
810
- return x === y
839
+ x === y
811
840
  rescue ArgumentError
812
- return false
841
+ false
813
842
  end
814
843
  end
815
844
  end
@@ -832,6 +861,7 @@ module RubyUnits
832
861
  other.dup
833
862
  elsif self =~ other
834
863
  raise ArgumentError, 'Cannot add two temperatures' if [self, other].all?(&:temperature?)
864
+
835
865
  if [self, other].any?(&:temperature?)
836
866
  if temperature?
837
867
  self.class.new(scalar: (scalar + other.convert_to(temperature_scale).scalar), numerator: @numerator, denominator: @denominator, signature: @signature)
@@ -881,7 +911,7 @@ module RubyUnits
881
911
  raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
882
912
  end
883
913
  when Time
884
- 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'
914
+ raise ArgumentError, 'Date and Time objects represent fixed points in time and cannot be subtracted from a Unit'
885
915
  else
886
916
  x, y = coerce(other)
887
917
  y - x
@@ -896,6 +926,7 @@ module RubyUnits
896
926
  case other
897
927
  when Unit
898
928
  raise ArgumentError, 'Cannot multiply by temperatures' if [other, self].any?(&:temperature?)
929
+
899
930
  opts = self.class.eliminate_terms(@scalar * other.scalar, @numerator + other.numerator, @denominator + other.denominator)
900
931
  opts[:signature] = @signature + other.signature
901
932
  self.class.new(opts)
@@ -918,6 +949,7 @@ module RubyUnits
918
949
  when Unit
919
950
  raise ZeroDivisionError if other.zero?
920
951
  raise ArgumentError, 'Cannot divide with temperatures' if [other, self].any?(&:temperature?)
952
+
921
953
  sc = Rational(@scalar, other.scalar)
922
954
  sc = sc.numerator if sc.denominator == 1
923
955
  opts = self.class.eliminate_terms(sc, @numerator + other.denominator, @denominator + other.numerator)
@@ -925,6 +957,7 @@ module RubyUnits
925
957
  self.class.new(opts)
926
958
  when Numeric
927
959
  raise ZeroDivisionError if other.zero?
960
+
928
961
  sc = Rational(@scalar, other)
929
962
  sc = sc.numerator if sc.denominator == 1
930
963
  self.class.new(scalar: sc, numerator: @numerator, denominator: @denominator, signature: @signature)
@@ -934,26 +967,50 @@ module RubyUnits
934
967
  end
935
968
  end
936
969
 
937
- # divide two units and return quotient and remainder
938
- # when both units are in the same units we just use divmod on the raw scalars
939
- # otherwise we use the scalar of the base unit which will be a float
940
- # @param [Object] other
941
- # @return [Array]
970
+ # Returns the remainder when one unit is divided by another
971
+ #
972
+ # @param [Unit] other
973
+ # @return [Unit]
974
+ # @raise [ArgumentError] if units are not compatible
975
+ def remainder(other)
976
+ raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless compatible_with?(other)
977
+
978
+ self.class.new(base_scalar.remainder(other.to_unit.base_scalar), to_base.units).convert_to(self)
979
+ end
980
+
981
+ # Divide two units and return quotient and remainder
982
+ #
983
+ # @param [Unit] other
984
+ # @return [Array(Integer, Unit)]
985
+ # @raise [ArgumentError] if units are not compatible
942
986
  def divmod(other)
943
- raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ other
944
- return scalar.divmod(other.scalar) if units == other.units
945
- to_base.scalar.divmod(other.to_base.scalar)
987
+ raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless compatible_with?(other)
988
+
989
+ [quo(other).to_base.floor, self % other]
946
990
  end
947
991
 
948
- # perform a modulo on a unit, will raise an exception if the units are not compatible
949
- # @param [Object] other
992
+ # Perform a modulo on a unit, will raise an exception if the units are not compatible
993
+ #
994
+ # @param [Unit] other
950
995
  # @return [Integer]
996
+ # @raise [ArgumentError] if units are not compatible
951
997
  def %(other)
952
- divmod(other).last
998
+ raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless compatible_with?(other)
999
+
1000
+ self.class.new(base_scalar % other.to_unit.base_scalar, to_base.units).convert_to(self)
953
1001
  end
1002
+ alias modulo %
954
1003
 
955
- # Exponentiate. Only takes integer powers.
956
- # Note that anything raised to the power of 0 results in a Unit object with a scalar of 1, and no units.
1004
+ # @param [Object] other
1005
+ # @return [Unit]
1006
+ # @raise [ZeroDivisionError] if other is zero
1007
+ def quo(other)
1008
+ self / other
1009
+ end
1010
+ alias fdiv quo
1011
+
1012
+ # Exponentiation. Only takes integer powers.
1013
+ # Note that anything raised to the power of 0 results in a [Unit] object with a scalar of 1, and no units.
957
1014
  # Throws an exception if exponent is not an integer.
958
1015
  # Ideally this routine should accept a float for the exponent
959
1016
  # It should then convert the float to a rational and raise the unit by the numerator and root it by the denominator
@@ -968,6 +1025,7 @@ module RubyUnits
968
1025
  # @raise [ArgumentError] when an invalid exponent is passed
969
1026
  def **(other)
970
1027
  raise ArgumentError, 'Cannot raise a temperature to a power' if temperature?
1028
+
971
1029
  if other.is_a?(Numeric)
972
1030
  return inverse if other == -1
973
1031
  return self if other == 1
@@ -975,14 +1033,16 @@ module RubyUnits
975
1033
  end
976
1034
  case other
977
1035
  when Rational
978
- return power(other.numerator).root(other.denominator)
1036
+ power(other.numerator).root(other.denominator)
979
1037
  when Integer
980
- return power(other)
1038
+ power(other)
981
1039
  when Float
982
1040
  return self**other.to_i if other == other.to_i
983
- valid = (1..9).map { |n| Rational(1, n) }
1041
+
1042
+ valid = (1..9).map { Rational(1, _1) }
984
1043
  raise ArgumentError, 'Not a n-th root (1..9), use 1/n' unless valid.include? other.abs
985
- return root(Rational(1, other).to_int)
1044
+
1045
+ root(Rational(1, other).to_int)
986
1046
  when Complex
987
1047
  raise ArgumentError, 'exponentiation of complex numbers is not supported.'
988
1048
  else
@@ -1002,6 +1062,7 @@ module RubyUnits
1002
1062
  return 1 if n.zero?
1003
1063
  return self if n == 1
1004
1064
  return (1..(n - 1).to_i).inject(self) { |acc, _elem| acc * self } if n >= 0
1065
+
1005
1066
  (1..-(n - 1).to_i).inject(self) { |acc, _elem| acc / self }
1006
1067
  end
1007
1068
 
@@ -1009,7 +1070,7 @@ module RubyUnits
1009
1070
  # if n < 0, returns 1/unit^(1/n)
1010
1071
  # @param [Integer] n
1011
1072
  # @return [Unit]
1012
- # @raise [ArgumentError] when attemptint to take the root of a temperature
1073
+ # @raise [ArgumentError] when attempting to take the root of a temperature
1013
1074
  # @raise [ArgumentError] when n is not an integer
1014
1075
  # @raise [ArgumentError] when n is 0
1015
1076
  def root(n)
@@ -1017,22 +1078,23 @@ module RubyUnits
1017
1078
  raise ArgumentError, 'Exponent must an Integer' unless n.is_a?(Integer)
1018
1079
  raise ArgumentError, '0th root undefined' if n.zero?
1019
1080
  return self if n == 1
1020
- return root(n.abs).inverse if n < 0
1081
+ return root(n.abs).inverse if n.negative?
1021
1082
 
1022
1083
  vec = unit_signature_vector
1023
- vec = vec.map { |x| x % n }
1084
+ vec = vec.map { _1 % n }
1024
1085
  raise ArgumentError, 'Illegal root' unless vec.max.zero?
1086
+
1025
1087
  num = @numerator.dup
1026
1088
  den = @denominator.dup
1027
1089
 
1028
1090
  @numerator.uniq.each do |item|
1029
- x = num.find_all { |i| i == item }.size
1091
+ x = num.find_all { _1 == item }.size
1030
1092
  r = ((x / n) * (n - 1)).to_int
1031
1093
  r.times { num.delete_at(num.index(item)) }
1032
1094
  end
1033
1095
 
1034
1096
  @denominator.uniq.each do |item|
1035
- x = den.find_all { |i| i == item }.size
1097
+ x = den.find_all { _1 == item }.size
1036
1098
  r = ((x / n) * (n - 1)).to_int
1037
1099
  r.times { den.delete_at(den.index(item)) }
1038
1100
  end
@@ -1089,7 +1151,7 @@ module RubyUnits
1089
1151
  return self if target_unit == start_unit
1090
1152
 
1091
1153
  # @type [Numeric]
1092
- @base_scalar ||= case @@unit_map[start_unit]
1154
+ @base_scalar ||= case self.class.unit_map[start_unit]
1093
1155
  when '<tempC>'
1094
1156
  @scalar + 273.15
1095
1157
  when '<tempK>'
@@ -1100,7 +1162,7 @@ module RubyUnits
1100
1162
  @scalar.to_r * Rational(5, 9)
1101
1163
  end
1102
1164
  # @type [Numeric]
1103
- q = case @@unit_map[target_unit]
1165
+ q = case self.class.unit_map[target_unit]
1104
1166
  when '<tempC>'
1105
1167
  @base_scalar - 273.15
1106
1168
  when '<tempK>'
@@ -1110,7 +1172,7 @@ module RubyUnits
1110
1172
  when '<tempR>'
1111
1173
  @base_scalar.to_r * Rational(9, 5)
1112
1174
  end
1113
- return self.class.new("#{q} #{target_unit}")
1175
+ self.class.new("#{q} #{target_unit}")
1114
1176
  else
1115
1177
  # @type [Unit]
1116
1178
  target = case other
@@ -1125,10 +1187,10 @@ module RubyUnits
1125
1187
 
1126
1188
  raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ target
1127
1189
 
1128
- numerator1 = @numerator.map { |x| @@prefix_values[x] || x }.map { |i| i.is_a?(Numeric) ? i : @@unit_values[i][:scalar] }.compact
1129
- denominator1 = @denominator.map { |x| @@prefix_values[x] || x }.map { |i| i.is_a?(Numeric) ? i : @@unit_values[i][:scalar] }.compact
1130
- numerator2 = target.numerator.map { |x| @@prefix_values[x] || x }.map { |x| x.is_a?(Numeric) ? x : @@unit_values[x][:scalar] }.compact
1131
- denominator2 = target.denominator.map { |x| @@prefix_values[x] || x }.map { |x| x.is_a?(Numeric) ? x : @@unit_values[x][:scalar] }.compact
1190
+ numerator1 = @numerator.map { self.class.prefix_values[_1] || _1 }.map { _1.is_a?(Numeric) ? _1 : self.class.unit_values[_1][:scalar] }.compact
1191
+ denominator1 = @denominator.map { self.class.prefix_values[_1] || _1 }.map { _1.is_a?(Numeric) ? _1 : self.class.unit_values[_1][:scalar] }.compact
1192
+ numerator2 = target.numerator.map { self.class.prefix_values[_1] || _1 }.map { _1.is_a?(Numeric) ? _1 : self.class.unit_values[_1][:scalar] }.compact
1193
+ denominator2 = target.denominator.map { self.class.prefix_values[_1] || _1 }.map { _1.is_a?(Numeric) ? _1 : self.class.unit_values[_1][:scalar] }.compact
1132
1194
 
1133
1195
  # If the scalar is an Integer, convert it to a Rational number so that
1134
1196
  # if the value is scaled during conversion, resolution is not lost due
@@ -1138,7 +1200,6 @@ module RubyUnits
1138
1200
  q = conversion_scalar * (numerator1 + denominator2).reduce(1, :*) / (numerator2 + denominator1).reduce(1, :*)
1139
1201
  # Convert the scalar to an Integer if the result is equivalent to an
1140
1202
  # integer
1141
-
1142
1203
  q = q.to_i if @scalar.is_a?(Integer) && q.to_i == q
1143
1204
  self.class.new(scalar: q, numerator: target.numerator, denominator: target.denominator, signature: target.signature)
1144
1205
  end
@@ -1152,6 +1213,7 @@ module RubyUnits
1152
1213
  # @raise [RuntimeError] when not unitless
1153
1214
  def to_f
1154
1215
  return @scalar.to_f if unitless?
1216
+
1155
1217
  raise "Cannot convert '#{self}' to Float unless unitless. Use Unit#scalar"
1156
1218
  end
1157
1219
 
@@ -1160,6 +1222,7 @@ module RubyUnits
1160
1222
  # @raise [RuntimeError] when not unitless
1161
1223
  def to_c
1162
1224
  return Complex(@scalar) if unitless?
1225
+
1163
1226
  raise "Cannot convert '#{self}' to Complex unless unitless. Use Unit#scalar"
1164
1227
  end
1165
1228
 
@@ -1168,6 +1231,7 @@ module RubyUnits
1168
1231
  # @raise [RuntimeError] when not unitless
1169
1232
  def to_i
1170
1233
  return @scalar.to_int if unitless?
1234
+
1171
1235
  raise "Cannot convert '#{self}' to Integer unless unitless. Use Unit#scalar"
1172
1236
  end
1173
1237
 
@@ -1178,6 +1242,7 @@ module RubyUnits
1178
1242
  # @raise [RuntimeError] when not unitless
1179
1243
  def to_r
1180
1244
  return @scalar.to_r if unitless?
1245
+
1181
1246
  raise "Cannot convert '#{self}' to Rational unless unitless. Use Unit#scalar"
1182
1247
  end
1183
1248
 
@@ -1200,26 +1265,26 @@ module RubyUnits
1200
1265
  den = @denominator.clone.compact
1201
1266
 
1202
1267
  unless num == UNITY_ARRAY
1203
- definitions = num.map { |element| self.class.definition(element) }
1268
+ definitions = num.map { self.class.definition(_1) }
1204
1269
  definitions.reject!(&:prefix?) unless with_prefix
1205
- definitions = definitions.chunk_while { |defn, _| defn.prefix? }.to_a
1206
- output_numerator = definitions.map { |element| element.map(&:display_name).join }
1270
+ definitions = definitions.chunk_while { |definition, _| definition.prefix? }.to_a
1271
+ output_numerator = definitions.map { _1.map(&:display_name).join }
1207
1272
  end
1208
1273
 
1209
1274
  unless den == UNITY_ARRAY
1210
- definitions = den.map { |element| self.class.definition(element) }
1275
+ definitions = den.map { self.class.definition(_1) }
1211
1276
  definitions.reject!(&:prefix?) unless with_prefix
1212
- definitions = definitions.chunk_while { |defn, _| defn.prefix? }.to_a
1213
- output_denominator = definitions.map { |element| element.map(&:display_name).join }
1277
+ definitions = definitions.chunk_while { |definition, _| definition.prefix? }.to_a
1278
+ output_denominator = definitions.map { _1.map(&:display_name).join }
1214
1279
  end
1215
1280
 
1216
1281
  on = output_numerator
1217
1282
  .uniq
1218
- .map { |x| [x, output_numerator.count(x)] }
1283
+ .map { [_1, output_numerator.count(_1)] }
1219
1284
  .map { |element, power| (element.to_s.strip + (power > 1 ? "^#{power}" : '')) }
1220
1285
  od = output_denominator
1221
1286
  .uniq
1222
- .map { |x| [x, output_denominator.count(x)] }
1287
+ .map { [_1, output_denominator.count(_1)] }
1223
1288
  .map { |element, power| (element.to_s.strip + (power > 1 ? "^#{power}" : '')) }
1224
1289
  "#{on.join('*')}#{od.empty? ? '' : "/#{od.join('*')}"}".strip
1225
1290
  end
@@ -1228,6 +1293,7 @@ module RubyUnits
1228
1293
  # @return [Numeric,Unit]
1229
1294
  def -@
1230
1295
  return -@scalar if unitless?
1296
+
1231
1297
  dup * -1
1232
1298
  end
1233
1299
 
@@ -1235,6 +1301,7 @@ module RubyUnits
1235
1301
  # @return [Numeric,Unit]
1236
1302
  def abs
1237
1303
  return @scalar.abs if unitless?
1304
+
1238
1305
  self.class.new(@scalar.abs, @numerator, @denominator)
1239
1306
  end
1240
1307
 
@@ -1282,6 +1349,7 @@ module RubyUnits
1282
1349
  # @raise [ArgumentError] when scalar is not equal to an integer
1283
1350
  def succ
1284
1351
  raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
1352
+
1285
1353
  self.class.new(@scalar.to_i.succ, @numerator, @denominator)
1286
1354
  end
1287
1355
 
@@ -1293,6 +1361,7 @@ module RubyUnits
1293
1361
  # @raise [ArgumentError] when scalar is not equal to an integer
1294
1362
  def pred
1295
1363
  raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
1364
+
1296
1365
  self.class.new(@scalar.to_i.pred, @numerator, @denominator)
1297
1366
  end
1298
1367
 
@@ -1306,7 +1375,7 @@ module RubyUnits
1306
1375
 
1307
1376
  # convert a duration to a DateTime. This will work so long as the duration is the duration from the zero date
1308
1377
  # defined by DateTime
1309
- # @return [DateTime]
1378
+ # @return [::DateTime]
1310
1379
  def to_datetime
1311
1380
  DateTime.new!(convert_to('d').scalar)
1312
1381
  end
@@ -1333,11 +1402,11 @@ module RubyUnits
1333
1402
  def before(time_point = ::Time.now)
1334
1403
  case time_point
1335
1404
  when Time, Date, DateTime
1336
- return (begin
1337
- time_point - self
1338
- rescue
1339
- time_point.to_datetime - self
1340
- end)
1405
+ (begin
1406
+ time_point - self
1407
+ rescue StandardError
1408
+ time_point.to_datetime - self
1409
+ end)
1341
1410
  else
1342
1411
  raise ArgumentError, 'Must specify a Time, Date, or DateTime'
1343
1412
  end
@@ -1352,9 +1421,9 @@ module RubyUnits
1352
1421
  def since(time_point)
1353
1422
  case time_point
1354
1423
  when Time
1355
- (Time.now - time_point).to_unit('s').convert_to(self)
1424
+ self.class.new(::Time.now - time_point, 'second').convert_to(self)
1356
1425
  when DateTime, Date
1357
- (DateTime.now - time_point).to_unit('d').convert_to(self)
1426
+ self.class.new(::DateTime.now - time_point, 'day').convert_to(self)
1358
1427
  else
1359
1428
  raise ArgumentError, 'Must specify a Time, Date, or DateTime'
1360
1429
  end
@@ -1366,9 +1435,9 @@ module RubyUnits
1366
1435
  def until(time_point)
1367
1436
  case time_point
1368
1437
  when Time
1369
- (time_point - Time.now).to_unit('s').convert_to(self)
1438
+ self.class.new(time_point - ::Time.now, 'second').convert_to(self)
1370
1439
  when DateTime, Date
1371
- (time_point - DateTime.now).to_unit('d').convert_to(self)
1440
+ self.class.new(time_point - ::DateTime.now, 'day').convert_to(self)
1372
1441
  else
1373
1442
  raise ArgumentError, 'Must specify a Time, Date, or DateTime'
1374
1443
  end
@@ -1382,10 +1451,10 @@ module RubyUnits
1382
1451
  case time_point
1383
1452
  when Time, DateTime, Date
1384
1453
  (begin
1385
- time_point + self
1386
- rescue
1387
- time_point.to_datetime + self
1388
- end)
1454
+ time_point + self
1455
+ rescue StandardError
1456
+ time_point.to_datetime + self
1457
+ end)
1389
1458
  else
1390
1459
  raise ArgumentError, 'Must specify a Time, Date, or DateTime'
1391
1460
  end
@@ -1394,29 +1463,28 @@ module RubyUnits
1394
1463
  alias after from
1395
1464
  alias from_now from
1396
1465
 
1397
- # automatically coerce objects to units when possible
1398
- # if an object defines a 'to_unit' method, it will be coerced using that method
1466
+ # Automatically coerce objects to [Unit] when possible. If an object defines a '#to_unit' method, it will be coerced
1467
+ # using that method.
1468
+ #
1399
1469
  # @param other [Object, #to_unit]
1400
- # @return [Array]
1470
+ # @return [Array(Unit, Unit)]
1471
+ # @raise [ArgumentError] when `other` cannot be converted to a [Unit]
1401
1472
  def coerce(other)
1402
- return [other.to_unit, self] if other.respond_to? :to_unit
1403
- case other
1404
- when Unit
1405
- [other, self]
1406
- else
1407
- [self.class.new(other), self]
1408
- end
1473
+ return [other.to_unit, self] if other.respond_to?(:to_unit)
1474
+
1475
+ [self.class.new(other), self]
1409
1476
  end
1410
1477
 
1411
1478
  # returns a new unit that has been scaled to be more in line with typical usage.
1412
1479
  def best_prefix
1413
1480
  return to_base if scalar.zero?
1481
+
1414
1482
  best_prefix = if kind == :information
1415
- @@prefix_values.key(2**((Math.log(base_scalar, 2) / 10.0).floor * 10))
1483
+ self.class.prefix_values.key(2**((::Math.log(base_scalar, 2) / 10.0).floor * 10))
1416
1484
  else
1417
- @@prefix_values.key(10**((Math.log10(base_scalar) / 3.0).floor * 3))
1485
+ self.class.prefix_values.key(10**((::Math.log10(base_scalar) / 3.0).floor * 3))
1418
1486
  end
1419
- to(self.class.new(@@prefix_map.key(best_prefix) + units(with_prefix: false)))
1487
+ to(self.class.new(self.class.prefix_map.key(best_prefix) + units(with_prefix: false)))
1420
1488
  end
1421
1489
 
1422
1490
  # override hash method so objects with same values are considered equal
@@ -1453,18 +1521,20 @@ module RubyUnits
1453
1521
  # @raise [ArgumentError] when exponent associated with a unit is > 20 or < -20
1454
1522
  def unit_signature_vector
1455
1523
  return to_base.unit_signature_vector unless base?
1456
- vector = Array.new(SIGNATURE_VECTOR.size, 0)
1524
+
1525
+ vector = ::Array.new(SIGNATURE_VECTOR.size, 0)
1457
1526
  # it's possible to have a kind that misses the array... kinds like :counting
1458
1527
  # are more like prefixes, so don't use them to calculate the vector
1459
- @numerator.map { |element| self.class.definition(element) }.each do |definition|
1528
+ @numerator.map { self.class.definition(_1) }.each do |definition|
1460
1529
  index = SIGNATURE_VECTOR.index(definition.kind)
1461
1530
  vector[index] += 1 if index
1462
1531
  end
1463
- @denominator.map { |element| self.class.definition(element) }.each do |definition|
1532
+ @denominator.map { self.class.definition(_1) }.each do |definition|
1464
1533
  index = SIGNATURE_VECTOR.index(definition.kind)
1465
1534
  vector[index] -= 1 if index
1466
1535
  end
1467
- raise ArgumentError, 'Power out of range (-20 < net power of a unit < 20)' if vector.any? { |x| x.abs >= 20 }
1536
+ raise ArgumentError, 'Power out of range (-20 < net power of a unit < 20)' if vector.any? { _1.abs >= 20 }
1537
+
1468
1538
  vector
1469
1539
  end
1470
1540
 
@@ -1486,8 +1556,9 @@ module RubyUnits
1486
1556
  # @return [Array]
1487
1557
  def unit_signature
1488
1558
  return @signature unless @signature.nil?
1559
+
1489
1560
  vector = unit_signature_vector
1490
- vector.each_with_index { |item, index| vector[index] = item * 20**index }
1561
+ vector.each_with_index { |item, index| vector[index] = item * (20**index) }
1491
1562
  @signature = vector.inject(0) { |acc, elem| acc + elem }
1492
1563
  @signature
1493
1564
  end
@@ -1503,39 +1574,52 @@ module RubyUnits
1503
1574
  # "GPa" -- creates a unit with scalar 1 with units 'GPa'
1504
1575
  # 6'4" -- recognized as 6 feet + 4 inches
1505
1576
  # 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
1506
- # @return [nil | Unit]
1577
+ # @return [nil,RubyUnits::Unit]
1507
1578
  # @todo This should either be a separate class or at least a class method
1508
1579
  def parse(passed_unit_string = '0')
1509
1580
  unit_string = passed_unit_string.dup
1510
1581
  unit_string = "#{Regexp.last_match(1)} USD" if unit_string =~ /\$\s*(#{NUMBER_REGEX})/
1511
1582
  unit_string.gsub!("\u00b0".force_encoding('utf-8'), 'deg') if unit_string.encoding == Encoding::UTF_8
1512
1583
 
1513
- unit_string.gsub!(/[%'"#]/, '%' => 'percent', "'" => 'feet', '"' => 'inch', '#' => 'pound')
1514
-
1515
- if defined?(Complex) && unit_string =~ COMPLEX_NUMBER
1516
- real, imaginary, unit_s = unit_string.scan(COMPLEX_REGEX)[0]
1517
- result = self.class.new(unit_s || '1') * Complex(real.to_f, imaginary.to_f)
1584
+ unit_string.gsub!(/[%'"#_,]/, '%' => 'percent', "'" => 'feet', '"' => 'inch', '#' => 'pound', '_' => '', ',' => '')
1585
+ if unit_string.start_with?(COMPLEX_NUMBER)
1586
+ match = unit_string.match(COMPLEX_REGEX)
1587
+ real = Float(match[:real]) if match[:real]
1588
+ imaginary = Float(match[:imaginary])
1589
+ unit_s = match[:unit]
1590
+ real = real.to_i if real.to_i == real
1591
+ imaginary = imaginary.to_i if imaginary.to_i == imaginary
1592
+ complex = Complex(real || 0, imaginary)
1593
+ complex = complex.to_i if complex.imaginary.zero? && complex.real == complex.real.to_i
1594
+ result = self.class.new(unit_s || 1) * complex
1518
1595
  copy(result)
1519
1596
  return
1520
1597
  end
1521
1598
 
1522
- if defined?(Rational) && unit_string =~ RATIONAL_NUMBER
1523
- sign, proper, numerator, denominator, unit_s = unit_string.scan(RATIONAL_REGEX)[0]
1524
- sign = sign == '-' ? -1 : 1
1525
- rational = sign * (proper.to_i + Rational(numerator.to_i, denominator.to_i))
1526
- result = self.class.new(unit_s || '1') * rational
1599
+ if unit_string.start_with?(RATIONAL_NUMBER)
1600
+ match = unit_string.match(RATIONAL_REGEX)
1601
+ numerator = Integer(match[:numerator])
1602
+ denominator = Integer(match[:denominator])
1603
+ raise ArgumentError, 'Improper fractions must have a whole number part' if !match[:proper].nil? && !match[:proper].match?(/^#{INTEGER_REGEX}$/)
1604
+
1605
+ proper = match[:proper].to_i
1606
+ unit_s = match[:unit]
1607
+ rational = if proper.negative?
1608
+ (proper - Rational(numerator, denominator))
1609
+ else
1610
+ (proper + Rational(numerator, denominator))
1611
+ end
1612
+ rational = rational.to_int if rational.to_int == rational
1613
+ result = self.class.new(unit_s || 1) * rational
1527
1614
  copy(result)
1528
1615
  return
1529
1616
  end
1530
1617
 
1531
- unit_string =~ NUMBER_REGEX
1532
- unit = @@cached_units[Regexp.last_match(2)]
1533
- mult = begin
1534
- (Regexp.last_match(1).empty? ? 1.0 : Regexp.last_match(1).to_f)
1535
- rescue
1536
- 1.0
1537
- end
1618
+ match = unit_string.match(NUMBER_REGEX)
1619
+ unit = self.class.cached.get(match[:unit])
1620
+ mult = match[:scalar] == '' ? 1.0 : match[:scalar].to_f
1538
1621
  mult = mult.to_int if mult.to_int == mult
1622
+
1539
1623
  if unit
1540
1624
  copy(unit)
1541
1625
  @scalar *= mult
@@ -1543,52 +1627,70 @@ module RubyUnits
1543
1627
  return self
1544
1628
  end
1545
1629
 
1546
- while unit_string.gsub!(/(<#{@@unit_regex})><(#{@@unit_regex}>)/, '\1*\2')
1630
+ while unit_string.gsub!(/(<#{self.class.unit_regex})><(#{self.class.unit_regex}>)/, '\1*\2')
1547
1631
  # collapse <x><y><z> into <x*y*z>...
1548
1632
  end
1549
1633
  # ... and then strip the remaining brackets for x*y*z
1550
1634
  unit_string.gsub!(/[<>]/, '')
1551
1635
 
1552
- if unit_string =~ TIME_REGEX
1553
- hours, minutes, seconds, microseconds = unit_string.scan(TIME_REGEX)[0]
1554
- raise ArgumentError,'Invalid Duration' if [hours, minutes, seconds, microseconds].all?(&:nil?)
1636
+ if (match = unit_string.match(TIME_REGEX))
1637
+ hours = match[:hour]
1638
+ minutes = match[:min]
1639
+ seconds = match[:sec]
1640
+ milliseconds = match[:msec]
1641
+ raise ArgumentError, 'Invalid Duration' if [hours, minutes, seconds, milliseconds].all?(&:nil?)
1555
1642
 
1556
- result = self.class.new("#{hours || 0} h") +
1643
+ result = self.class.new("#{hours || 0} hours") +
1557
1644
  self.class.new("#{minutes || 0} minutes") +
1558
1645
  self.class.new("#{seconds || 0} seconds") +
1559
- self.class.new("#{microseconds || 0} usec")
1646
+ self.class.new("#{milliseconds || 0} milliseconds")
1560
1647
  copy(result)
1561
1648
  return
1562
1649
  end
1563
1650
 
1564
1651
  # Special processing for unusual unit strings
1565
1652
  # feet -- 6'5"
1566
- feet, inches = unit_string.scan(FEET_INCH_REGEX)[0]
1567
- if feet && inches
1568
- result = self.class.new("#{feet} ft") + self.class.new("#{inches} inches")
1653
+ if (match = unit_string.match(FEET_INCH_REGEX))
1654
+ feet = Integer(match[:feet])
1655
+ inches = match[:inches]
1656
+ result = if feet.negative?
1657
+ self.class.new("#{feet} ft") - self.class.new("#{inches} inches")
1658
+ else
1659
+ self.class.new("#{feet} ft") + self.class.new("#{inches} inches")
1660
+ end
1569
1661
  copy(result)
1570
1662
  return
1571
1663
  end
1572
1664
 
1573
1665
  # weight -- 8 lbs 12 oz
1574
- pounds, oz = unit_string.scan(LBS_OZ_REGEX)[0]
1575
- if pounds && oz
1576
- result = self.class.new("#{pounds} lbs") + self.class.new("#{oz} oz")
1666
+ if (match = unit_string.match(LBS_OZ_REGEX))
1667
+ pounds = Integer(match[:pounds])
1668
+ oz = match[:oz]
1669
+ result = if pounds.negative?
1670
+ self.class.new("#{pounds} lbs") - self.class.new("#{oz} oz")
1671
+ else
1672
+ self.class.new("#{pounds} lbs") + self.class.new("#{oz} oz")
1673
+ end
1577
1674
  copy(result)
1578
1675
  return
1579
1676
  end
1580
1677
 
1581
1678
  # stone -- 3 stone 5, 2 stone, 14 stone 3 pounds, etc.
1582
- stone, pounds = unit_string.scan(STONE_LB_REGEX)[0]
1583
- if stone && pounds
1584
- result = self.class.new("#{stone} stone") + self.class.new("#{pounds} lbs")
1679
+ if (match = unit_string.match(STONE_LB_REGEX))
1680
+ stone = Integer(match[:stone])
1681
+ pounds = match[:pounds]
1682
+ result = if stone.negative?
1683
+ self.class.new("#{stone} stone") - self.class.new("#{pounds} lbs")
1684
+ else
1685
+ self.class.new("#{stone} stone") + self.class.new("#{pounds} lbs")
1686
+ end
1585
1687
  copy(result)
1586
1688
  return
1587
1689
  end
1588
1690
 
1589
1691
  # more than one per. I.e., "1 m/s/s"
1590
1692
  raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.count('/') > 1
1591
- raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string =~ /\s[02-9]/
1693
+ raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized #{unit_string}") if unit_string =~ /\s[02-9]/
1592
1694
 
1593
1695
  @scalar, top, bottom = unit_string.scan(UNIT_STRING_REGEX)[0] # parse the string into parts
1594
1696
  top.scan(TOP_REGEX).each do |item|
@@ -1596,7 +1698,7 @@ module RubyUnits
1596
1698
  x = "#{item[0]} "
1597
1699
  if n >= 0
1598
1700
  top.gsub!(/#{item[0]}(\^|\*\*)#{n}/) { x * n }
1599
- elsif n < 0
1701
+ elsif n.negative?
1600
1702
  bottom = "#{bottom} #{x * -n}"
1601
1703
  top.gsub!(/#{item[0]}(\^|\*\*)#{n}/, '')
1602
1704
  end
@@ -1627,15 +1729,15 @@ module RubyUnits
1627
1729
 
1628
1730
  # eliminate all known terms from this string. This is a quick check to see if the passed unit
1629
1731
  # contains terms that are not defined.
1630
- used = "#{top} #{bottom}".to_s.gsub(self.class.unit_match_regex, '').gsub(%r{[\d\*, "'_^\/\$]}, '')
1732
+ used = "#{top} #{bottom}".to_s.gsub(self.class.unit_match_regex, '').gsub(%r{[\d*, "'_^/$]}, '')
1631
1733
  raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") unless used.empty?
1632
1734
 
1633
1735
  @numerator = @numerator.map do |item|
1634
- @@prefix_map[item[0]] ? [@@prefix_map[item[0]], @@unit_map[item[1]]] : [@@unit_map[item[1]]]
1736
+ self.class.prefix_map[item[0]] ? [self.class.prefix_map[item[0]], self.class.unit_map[item[1]]] : [self.class.unit_map[item[1]]]
1635
1737
  end.flatten.compact.delete_if(&:empty?)
1636
1738
 
1637
1739
  @denominator = @denominator.map do |item|
1638
- @@prefix_map[item[0]] ? [@@prefix_map[item[0]], @@unit_map[item[1]]] : [@@unit_map[item[1]]]
1740
+ self.class.prefix_map[item[0]] ? [self.class.prefix_map[item[0]], self.class.unit_map[item[1]]] : [self.class.unit_map[item[1]]]
1639
1741
  end.flatten.compact.delete_if(&:empty?)
1640
1742
 
1641
1743
  @numerator = UNITY_ARRAY if @numerator.empty?