ruby-units 2.4.1 → 4.0.0

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