ruby-units 2.3.0 → 2.4.1
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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +32 -0
- data/.csslintrc +2 -0
- data/.eslintignore +1 -0
- data/.eslintrc +213 -0
- data/.github/dependabot.yml +16 -0
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/tests.yml +49 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.rubocop.yml +24 -0
- data/.ruby-version +1 -0
- data/.solargraph.yml +16 -0
- data/CHANGELOG.txt +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +183 -0
- data/Guardfile +31 -0
- data/LICENSE.txt +18 -17
- data/README.md +124 -96
- data/Rakefile +3 -24
- data/lib/ruby-units.rb +0 -1
- data/lib/ruby_units/configuration.rb +5 -4
- data/lib/ruby_units/definition.rb +6 -2
- data/lib/ruby_units/namespaced.rb +0 -3
- data/lib/ruby_units/unit.rb +230 -221
- data/lib/ruby_units/version.rb +1 -4
- data/ruby-units.gemspec +37 -79
- metadata +121 -23
- data/VERSION +0 -1
data/lib/ruby_units/unit.rb
CHANGED
@@ -22,7 +22,6 @@ require 'date'
|
|
22
22
|
#
|
23
23
|
module RubyUnits
|
24
24
|
class Unit < Numeric
|
25
|
-
VERSION = Unit::Version::STRING
|
26
25
|
@@definitions = {}
|
27
26
|
@@prefix_values = {}
|
28
27
|
@@prefix_map = {}
|
@@ -32,30 +31,41 @@ module RubyUnits
|
|
32
31
|
@@unit_match_regex = nil
|
33
32
|
UNITY = '<1>'.freeze
|
34
33
|
UNITY_ARRAY = [UNITY].freeze
|
35
|
-
# ideally we would like to generate this regex from the alias for a 'feet'
|
36
|
-
# defined at the point in the code where we
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
34
|
+
# ideally we would like to generate this regex from the alias for a 'feet'
|
35
|
+
# and 'inches', but they aren't defined at the point in the code where we
|
36
|
+
# need this regex.
|
37
|
+
FEET_INCH_UNITS_REGEX = /(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inch(?:es)?)/.freeze
|
38
|
+
FEET_INCH_REGEX = /(\d+)\s*#{FEET_INCH_UNITS_REGEX}/.freeze
|
39
|
+
# ideally we would like to generate this regex from the alias for a 'pound'
|
40
|
+
# and 'ounce', but they aren't defined at the point in the code where we
|
41
|
+
# need this regex.
|
42
|
+
LBS_OZ_UNIT_REGEX = /(?:#|lbs?|pounds?|pound-mass)+[\s,]*(\d+)\s*(?:ozs?|ounces?)/.freeze
|
43
|
+
LBS_OZ_REGEX = /(\d+)\s*#{LBS_OZ_UNIT_REGEX}/.freeze
|
44
|
+
# ideally we would like to generate this regex from the alias for a 'stone'
|
45
|
+
# and 'pound', but they aren't defined at the point in the code where we
|
46
|
+
# need this regex. also note that the plural of 'stone' is still 'stone',
|
47
|
+
# but we accept 'stones' anyway.
|
48
|
+
STONE_LB_UNIT_REGEX = /(?:sts?|stones?)+[\s,]*(\d+)\s*(?:#|lbs?|pounds?|pound-mass)*/.freeze
|
49
|
+
STONE_LB_REGEX = /(\d+)\s*#{STONE_LB_UNIT_REGEX}/.freeze
|
50
|
+
# Time formats: 12:34:56,78, (hh:mm:ss,msec) etc.
|
51
|
+
TIME_REGEX = /(?<hour>\d+):(?<min>\d+):(?:(?<sec>\d+))?(?:,(?<msec>\d+))?/.freeze
|
52
|
+
# Scientific notation: 1, -1, +1, 1.2, +1.2, -1.2, 123.4E5, +123.4e5,
|
53
|
+
# -123.4E+5, -123.4e-5, etc.
|
54
|
+
SCI_NUMBER = /([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)/.freeze
|
55
|
+
# Rational number, including improper fractions: 1 2/3, -1 2/3, 5/3, etc.
|
56
|
+
RATIONAL_NUMBER = %r{\(?([+-])?(\d+[ -])?(\d+)\/(\d+)\)?}.freeze
|
57
|
+
# Complex numbers: 1+2i, 1.0+2.0i, -1-1i, etc.
|
58
|
+
COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/.freeze
|
59
|
+
# Any Complex, Rational, or scientific number
|
60
|
+
ANY_NUMBER = /(#{COMPLEX_NUMBER}|#{RATIONAL_NUMBER}|#{SCI_NUMBER})/.freeze
|
61
|
+
ANY_NUMBER_REGEX = /(?:#{ANY_NUMBER})?\s?([^-\d.].*)?/.freeze
|
62
|
+
NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/.freeze
|
63
|
+
UNIT_STRING_REGEX = %r{#{SCI_NUMBER}*\s*([^\/]*)\/*(.+)*}.freeze
|
64
|
+
TOP_REGEX = /([^ \*]+)(?:\^|\*\*)([\d-]+)/.freeze
|
65
|
+
BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/.freeze
|
66
|
+
NUMBER_UNIT_REGEX = /#{SCI_NUMBER}?(.*)/.freeze
|
67
|
+
COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/.freeze
|
68
|
+
RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(.+)?/.freeze
|
59
69
|
KELVIN = ['<kelvin>'].freeze
|
60
70
|
FAHRENHEIT = ['<fahrenheit>'].freeze
|
61
71
|
RANKINE = ['<rankine>'].freeze
|
@@ -140,11 +150,11 @@ module RubyUnits
|
|
140
150
|
@@unit_match_regex = nil
|
141
151
|
@@prefix_regex = nil
|
142
152
|
|
143
|
-
@@definitions.
|
153
|
+
@@definitions.each_value do |definition|
|
144
154
|
use_definition(definition)
|
145
155
|
end
|
146
156
|
|
147
|
-
|
157
|
+
new(1)
|
148
158
|
true
|
149
159
|
end
|
150
160
|
|
@@ -156,7 +166,7 @@ module RubyUnits
|
|
156
166
|
end
|
157
167
|
|
158
168
|
# return the unit definition for a unit
|
159
|
-
# @param [String]
|
169
|
+
# @param unit_name [String]
|
160
170
|
# @return [RubyUnits::Unit::Definition, nil]
|
161
171
|
def self.definition(unit_name)
|
162
172
|
unit = unit_name =~ /^<.+>$/ ? unit_name : "<#{unit_name}>"
|
@@ -164,12 +174,12 @@ module RubyUnits
|
|
164
174
|
end
|
165
175
|
|
166
176
|
# return a list of all defined units
|
167
|
-
# @return [Array]
|
177
|
+
# @return [Array<RubyUnits::Units::Definition>]
|
168
178
|
def self.definitions
|
169
179
|
@@definitions
|
170
180
|
end
|
171
181
|
|
172
|
-
# @param [RubyUnits::Unit::Definition
|
182
|
+
# @param [RubyUnits::Unit::Definition, String] unit_definition
|
173
183
|
# @param [Block] block
|
174
184
|
# @return [RubyUnits::Unit::Definition]
|
175
185
|
# @raise [ArgumentError] when passed a non-string if using the block form
|
@@ -188,33 +198,38 @@ module RubyUnits
|
|
188
198
|
raise ArgumentError, 'When using the block form of RubyUnits::Unit.define, pass the name of the unit' unless unit_definition.instance_of?(String)
|
189
199
|
unit_definition = RubyUnits::Unit::Definition.new(unit_definition, &block)
|
190
200
|
end
|
191
|
-
|
192
|
-
|
201
|
+
definitions[unit_definition.name] = unit_definition
|
202
|
+
use_definition(unit_definition)
|
193
203
|
unit_definition
|
194
204
|
end
|
195
205
|
|
206
|
+
# Get the definition for a unit and allow it to be redefined
|
207
|
+
#
|
196
208
|
# @param [String] name Name of unit to redefine
|
197
|
-
# @param [Block]
|
209
|
+
# @param [Block] _block
|
198
210
|
# @raise [ArgumentError] if a block is not given
|
199
|
-
# @
|
211
|
+
# @yieldparam [RubyUnits::Unit::Definition] the definition of the unit being
|
212
|
+
# redefined
|
200
213
|
# @return (see RubyUnits::Unit.define)
|
201
|
-
|
202
|
-
def self.redefine!(name)
|
214
|
+
def self.redefine!(name, &_block)
|
203
215
|
raise ArgumentError, 'A block is required to redefine a unit' unless block_given?
|
216
|
+
|
204
217
|
unit_definition = definition(name)
|
205
218
|
raise(ArgumentError, "'#{name}' Unit not recognized") unless unit_definition
|
219
|
+
|
206
220
|
yield unit_definition
|
207
221
|
@@definitions.delete("<#{name}>")
|
208
222
|
define(unit_definition)
|
209
|
-
|
223
|
+
setup
|
210
224
|
end
|
211
225
|
|
212
|
-
# @param [String] name of unit to undefine
|
213
|
-
# @return (see RubyUnits::Unit.setup)
|
214
226
|
# Undefine a unit. Will not raise an exception for unknown units.
|
227
|
+
#
|
228
|
+
# @param unit [String] name of unit to undefine
|
229
|
+
# @return (see RubyUnits::Unit.setup)
|
215
230
|
def self.undefine!(unit)
|
216
231
|
@@definitions.delete("<#{unit}>")
|
217
|
-
|
232
|
+
setup
|
218
233
|
end
|
219
234
|
|
220
235
|
# @return [Hash]
|
@@ -226,7 +241,7 @@ module RubyUnits
|
|
226
241
|
def self.clear_cache
|
227
242
|
@@cached_units = {}
|
228
243
|
@@base_unit_cache = {}
|
229
|
-
|
244
|
+
new(1)
|
230
245
|
true
|
231
246
|
end
|
232
247
|
|
@@ -241,104 +256,74 @@ module RubyUnits
|
|
241
256
|
# @return [Unit]
|
242
257
|
def self.parse(input)
|
243
258
|
first, second = input.scan(/(.+)\s(?:in|to|as)\s(.+)/i).first
|
244
|
-
second.nil? ?
|
259
|
+
second.nil? ? new(first) : new(first).convert_to(second)
|
245
260
|
end
|
246
261
|
|
247
|
-
# @param [Numeric]
|
248
|
-
# @param [Array]
|
249
|
-
# @param [Array]
|
262
|
+
# @param q [Numeric] quantity
|
263
|
+
# @param n [Array] numerator
|
264
|
+
# @param d [Array] denominator
|
250
265
|
# @return [Hash]
|
251
266
|
def self.eliminate_terms(q, n, d)
|
252
267
|
num = n.dup
|
253
268
|
den = d.dup
|
269
|
+
num.delete(UNITY)
|
270
|
+
den.delete(UNITY)
|
254
271
|
|
255
|
-
num.delete_if { |v| v == UNITY }
|
256
|
-
den.delete_if { |v| v == UNITY }
|
257
272
|
combined = Hash.new(0)
|
258
273
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
k = [num[i], num[i + 1]]
|
264
|
-
i += 2
|
265
|
-
else
|
266
|
-
k = num[i]
|
267
|
-
i += 1
|
268
|
-
end
|
269
|
-
combined[k] += 1 unless k.nil? || k == UNITY
|
270
|
-
end
|
271
|
-
|
272
|
-
j = 0
|
273
|
-
loop do
|
274
|
-
break if j > den.size
|
275
|
-
if @@prefix_values.key? den[j]
|
276
|
-
k = [den[j], den[j + 1]]
|
277
|
-
j += 2
|
278
|
-
else
|
279
|
-
k = den[j]
|
280
|
-
j += 1
|
281
|
-
end
|
282
|
-
combined[k] -= 1 unless k.nil? || k == UNITY
|
274
|
+
[[num, 1], [den, -1]].each do |array, increment|
|
275
|
+
array.chunk_while { |elt_before, _| definition(elt_before).prefix? }
|
276
|
+
.to_a
|
277
|
+
.each { |unit| combined[unit] += increment }
|
283
278
|
end
|
284
279
|
|
285
280
|
num = []
|
286
281
|
den = []
|
287
282
|
combined.each do |key, value|
|
288
|
-
if value
|
283
|
+
if value.positive?
|
289
284
|
value.times { num << key }
|
290
|
-
elsif value
|
285
|
+
elsif value.negative?
|
291
286
|
value.abs.times { den << key }
|
292
287
|
end
|
293
288
|
end
|
294
289
|
num = UNITY_ARRAY if num.empty?
|
295
290
|
den = UNITY_ARRAY if den.empty?
|
296
|
-
{ scalar: q, numerator: num.flatten
|
291
|
+
{ scalar: q, numerator: num.flatten, denominator: den.flatten }
|
292
|
+
end
|
293
|
+
|
294
|
+
# Creates a new unit from the current one with all common terms eliminated.
|
295
|
+
#
|
296
|
+
# @return [RubyUnits::Unit]
|
297
|
+
def eliminate_terms
|
298
|
+
self.class.new(self.class.eliminate_terms(@scalar, @numerator, @denominator))
|
297
299
|
end
|
298
300
|
|
299
301
|
# return an array of base units
|
300
302
|
# @return [Array]
|
301
303
|
def self.base_units
|
302
|
-
@@base_units ||= @@definitions.dup.delete_if { |_, defn| !defn.base? }.keys.map { |u|
|
304
|
+
@@base_units ||= @@definitions.dup.delete_if { |_, defn| !defn.base? }.keys.map { |u| new(u) }
|
303
305
|
end
|
304
306
|
|
305
307
|
# parse a string consisting of a number and a unit string
|
306
308
|
# NOTE: This does not properly handle units formatted like '12mg/6ml'
|
307
309
|
# @param [String] string
|
308
|
-
# @return [Array] consisting of [
|
310
|
+
# @return [Array(Numeric, String)] consisting of [number, "unit"]
|
309
311
|
def self.parse_into_numbers_and_units(string)
|
310
|
-
|
311
|
-
sci = /[+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*/
|
312
|
-
# rational numbers.... -1/3, 1/5, 20/100, -6 1/2, -6-1/2
|
313
|
-
rational = %r{\(?[+-]?(?:\d+[ -])?\d+\/\d+\)?}
|
314
|
-
# complex numbers... -1.2+3i, +1.2-3.3i
|
315
|
-
complex = /#{sci}{2,2}i/
|
316
|
-
anynumber = /(?:(#{complex}|#{rational}|#{sci}))?\s?([^-\d\.].*)?/
|
317
|
-
|
318
|
-
num, unit = string.scan(anynumber).first
|
312
|
+
num, unit = string.scan(ANY_NUMBER_REGEX).first
|
319
313
|
|
320
314
|
[
|
321
315
|
case num
|
322
|
-
when
|
316
|
+
when nil # This happens when no number is passed and we are parsing a pure unit string
|
323
317
|
1
|
324
|
-
when
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
# if it has whitespace, it will be of the form '6 1/2'
|
334
|
-
if num =~ RATIONAL_NUMBER
|
335
|
-
sign = Regexp.last_match(1) == '-' ? -1 : 1
|
336
|
-
n = Regexp.last_match(2).to_i
|
337
|
-
f = Rational(Regexp.last_match(3).to_i, Regexp.last_match(4).to_i)
|
338
|
-
sign * (n + f)
|
339
|
-
else
|
340
|
-
Rational(*num.split('/').map(&:to_i))
|
341
|
-
end
|
318
|
+
when COMPLEX_NUMBER
|
319
|
+
num.to_c
|
320
|
+
when RATIONAL_NUMBER
|
321
|
+
# We use this method instead of relying on `to_r` because it does not
|
322
|
+
# handle improper fractions correctly.
|
323
|
+
sign = Regexp.last_match(1) == '-' ? -1 : 1
|
324
|
+
n = Regexp.last_match(2).to_i
|
325
|
+
f = Rational(Regexp.last_match(3).to_i, Regexp.last_match(4).to_i)
|
326
|
+
sign * (n + f)
|
342
327
|
else
|
343
328
|
num.to_f
|
344
329
|
end,
|
@@ -356,7 +341,7 @@ module RubyUnits
|
|
356
341
|
# return a regex used to match units
|
357
342
|
# @return [RegExp]
|
358
343
|
def self.unit_match_regex
|
359
|
-
@@unit_match_regex ||= /(#{
|
344
|
+
@@unit_match_regex ||= /(#{prefix_regex})??(#{unit_regex})\b/
|
360
345
|
end
|
361
346
|
|
362
347
|
# return a regexp fragment used to match prefixes
|
@@ -366,11 +351,14 @@ module RubyUnits
|
|
366
351
|
@@prefix_regex ||= @@prefix_map.keys.sort_by { |prefix| [prefix.length, prefix] }.reverse.join('|')
|
367
352
|
end
|
368
353
|
|
354
|
+
# Generates (and memoizes) a regexp matching any of the temperature units or their aliases.
|
355
|
+
#
|
356
|
+
# @return [RegExp]
|
369
357
|
def self.temp_regex
|
370
358
|
@@temp_regex ||= begin
|
371
359
|
temp_units = %w[tempK tempC tempF tempR degK degC degF degR]
|
372
360
|
aliases = temp_units.map do |unit|
|
373
|
-
d =
|
361
|
+
d = definition(unit)
|
374
362
|
d && d.aliases
|
375
363
|
end.flatten.compact
|
376
364
|
regex_str = aliases.empty? ? '(?!x)x' : aliases.join('|')
|
@@ -379,6 +367,8 @@ module RubyUnits
|
|
379
367
|
end
|
380
368
|
|
381
369
|
# inject a definition into the internal array and set it up for use
|
370
|
+
#
|
371
|
+
# @param definition [RubyUnits::Unit::Definition]
|
382
372
|
def self.use_definition(definition)
|
383
373
|
@@unit_match_regex = nil # invalidate the unit match regex
|
384
374
|
@@temp_regex = nil # invalidate the temp regex
|
@@ -470,6 +460,7 @@ module RubyUnits
|
|
470
460
|
@signature = nil
|
471
461
|
@output = {}
|
472
462
|
raise ArgumentError, 'Invalid Unit Format' if options[0].nil?
|
463
|
+
|
473
464
|
if options.size == 2
|
474
465
|
# options[0] is the scalar
|
475
466
|
# options[1] is a unit string
|
@@ -529,16 +520,17 @@ module RubyUnits
|
|
529
520
|
end
|
530
521
|
update_base_scalar
|
531
522
|
raise ArgumentError, 'Temperatures must not be less than absolute zero' if temperature? && base_scalar < 0
|
523
|
+
|
532
524
|
unary_unit = units || ''
|
533
525
|
if options.first.instance_of?(String)
|
534
|
-
_opt_scalar, opt_units =
|
526
|
+
_opt_scalar, opt_units = self.class.parse_into_numbers_and_units(options[0])
|
535
527
|
unless @@cached_units.keys.include?(opt_units) ||
|
536
|
-
(opt_units =~ %r{\D/[\d
|
537
|
-
(opt_units =~ %r{(#{
|
528
|
+
(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?(.+)?|±|\+\/-})
|
538
530
|
@@cached_units[opt_units] = (scalar == 1 ? self : opt_units.to_unit) if opt_units && !opt_units.empty?
|
539
531
|
end
|
540
532
|
end
|
541
|
-
unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /#{
|
533
|
+
unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /#{self.class.temp_regex}/)
|
542
534
|
@@cached_units[unary_unit] = (scalar == 1 ? self : unary_unit.to_unit)
|
543
535
|
end
|
544
536
|
[@scalar, @numerator, @denominator, @base_scalar, @signature, @base].each(&:freeze)
|
@@ -566,7 +558,7 @@ module RubyUnits
|
|
566
558
|
@base = (@numerator + @denominator)
|
567
559
|
.compact
|
568
560
|
.uniq
|
569
|
-
.map { |unit|
|
561
|
+
.map { |unit| self.class.definition(unit) }
|
570
562
|
.all? { |element| element.unity? || element.base? }
|
571
563
|
@base
|
572
564
|
end
|
@@ -621,7 +613,7 @@ module RubyUnits
|
|
621
613
|
num = num.flatten.compact
|
622
614
|
den = den.flatten.compact
|
623
615
|
num = UNITY_ARRAY if num.empty?
|
624
|
-
base =
|
616
|
+
base = self.class.new(self.class.eliminate_terms(q, num, den))
|
625
617
|
@@base_unit_cache[units] = base
|
626
618
|
base * @scalar
|
627
619
|
end
|
@@ -842,12 +834,12 @@ module RubyUnits
|
|
842
834
|
raise ArgumentError, 'Cannot add two temperatures' if [self, other].all?(&:temperature?)
|
843
835
|
if [self, other].any?(&:temperature?)
|
844
836
|
if temperature?
|
845
|
-
|
837
|
+
self.class.new(scalar: (scalar + other.convert_to(temperature_scale).scalar), numerator: @numerator, denominator: @denominator, signature: @signature)
|
846
838
|
else
|
847
|
-
|
839
|
+
self.class.new(scalar: (other.scalar + convert_to(other.temperature_scale).scalar), numerator: other.numerator, denominator: other.denominator, signature: other.signature)
|
848
840
|
end
|
849
841
|
else
|
850
|
-
|
842
|
+
self.class.new(scalar: (base_scalar + other.base_scalar), numerator: base.numerator, denominator: base.denominator, signature: @signature).convert_to(self)
|
851
843
|
end
|
852
844
|
else
|
853
845
|
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
|
@@ -877,13 +869,13 @@ module RubyUnits
|
|
877
869
|
end
|
878
870
|
elsif self =~ other
|
879
871
|
if [self, other].all?(&:temperature?)
|
880
|
-
|
872
|
+
self.class.new(scalar: (base_scalar - other.base_scalar), numerator: KELVIN, denominator: UNITY_ARRAY, signature: @signature).convert_to(temperature_scale)
|
881
873
|
elsif temperature?
|
882
|
-
|
874
|
+
self.class.new(scalar: (base_scalar - other.base_scalar), numerator: ['<tempK>'], denominator: UNITY_ARRAY, signature: @signature).convert_to(self)
|
883
875
|
elsif other.temperature?
|
884
876
|
raise ArgumentError, 'Cannot subtract a temperature from a differential degree unit'
|
885
877
|
else
|
886
|
-
|
878
|
+
self.class.new(scalar: (base_scalar - other.base_scalar), numerator: base.numerator, denominator: base.denominator, signature: @signature).convert_to(self)
|
887
879
|
end
|
888
880
|
else
|
889
881
|
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
|
@@ -904,11 +896,11 @@ module RubyUnits
|
|
904
896
|
case other
|
905
897
|
when Unit
|
906
898
|
raise ArgumentError, 'Cannot multiply by temperatures' if [other, self].any?(&:temperature?)
|
907
|
-
opts =
|
899
|
+
opts = self.class.eliminate_terms(@scalar * other.scalar, @numerator + other.numerator, @denominator + other.denominator)
|
908
900
|
opts[:signature] = @signature + other.signature
|
909
|
-
|
901
|
+
self.class.new(opts)
|
910
902
|
when Numeric
|
911
|
-
|
903
|
+
self.class.new(scalar: @scalar * other, numerator: @numerator, denominator: @denominator, signature: @signature)
|
912
904
|
else
|
913
905
|
x, y = coerce(other)
|
914
906
|
x * y
|
@@ -928,14 +920,14 @@ module RubyUnits
|
|
928
920
|
raise ArgumentError, 'Cannot divide with temperatures' if [other, self].any?(&:temperature?)
|
929
921
|
sc = Rational(@scalar, other.scalar)
|
930
922
|
sc = sc.numerator if sc.denominator == 1
|
931
|
-
opts =
|
923
|
+
opts = self.class.eliminate_terms(sc, @numerator + other.denominator, @denominator + other.numerator)
|
932
924
|
opts[:signature] = @signature - other.signature
|
933
|
-
|
925
|
+
self.class.new(opts)
|
934
926
|
when Numeric
|
935
927
|
raise ZeroDivisionError if other.zero?
|
936
928
|
sc = Rational(@scalar, other)
|
937
929
|
sc = sc.numerator if sc.denominator == 1
|
938
|
-
|
930
|
+
self.class.new(scalar: sc, numerator: @numerator, denominator: @denominator, signature: @signature)
|
939
931
|
else
|
940
932
|
x, y = coerce(other)
|
941
933
|
y / x
|
@@ -1044,13 +1036,13 @@ module RubyUnits
|
|
1044
1036
|
r = ((x / n) * (n - 1)).to_int
|
1045
1037
|
r.times { den.delete_at(den.index(item)) }
|
1046
1038
|
end
|
1047
|
-
|
1039
|
+
self.class.new(scalar: @scalar**Rational(1, n), numerator: num, denominator: den)
|
1048
1040
|
end
|
1049
1041
|
|
1050
1042
|
# returns inverse of Unit (1/unit)
|
1051
1043
|
# @return [Unit]
|
1052
1044
|
def inverse
|
1053
|
-
|
1045
|
+
self.class.new('1') / self
|
1054
1046
|
end
|
1055
1047
|
|
1056
1048
|
# convert to a specified unit string or to the same units as another Unit
|
@@ -1061,13 +1053,17 @@ module RubyUnits
|
|
1061
1053
|
# To convert a Unit object to match another Unit object, use:
|
1062
1054
|
# unit1 >>= unit2
|
1063
1055
|
#
|
1064
|
-
# Special handling for temperature conversions is supported. If the Unit
|
1065
|
-
# from one temperature unit to another, the proper
|
1066
|
-
# Supports Kelvin, Celsius, Fahrenheit,
|
1056
|
+
# Special handling for temperature conversions is supported. If the Unit
|
1057
|
+
# object is converted from one temperature unit to another, the proper
|
1058
|
+
# temperature offsets will be used. Supports Kelvin, Celsius, Fahrenheit,
|
1059
|
+
# and Rankine scales.
|
1067
1060
|
#
|
1068
|
-
# @note If temperature is part of a compound unit, the temperature will be
|
1069
|
-
# and the units will be scaled appropriately.
|
1070
|
-
# @
|
1061
|
+
# @note If temperature is part of a compound unit, the temperature will be
|
1062
|
+
# treated as a differential and the units will be scaled appropriately.
|
1063
|
+
# @note When converting units with Integer scalars, the scalar will be
|
1064
|
+
# converted to a Rational to avoid unexpected behavior caused by Integer
|
1065
|
+
# division.
|
1066
|
+
# @param other [Unit, String]
|
1071
1067
|
# @return [Unit]
|
1072
1068
|
# @raise [ArgumentError] when attempting to convert a degree to a temperature
|
1073
1069
|
# @raise [ArgumentError] when target unit is unknown
|
@@ -1076,14 +1072,23 @@ module RubyUnits
|
|
1076
1072
|
return self if other.nil?
|
1077
1073
|
return self if TrueClass === other
|
1078
1074
|
return self if FalseClass === other
|
1079
|
-
|
1075
|
+
|
1076
|
+
if (other.is_a?(Unit) && other.temperature?) || (other.is_a?(String) && other =~ self.class.temp_regex)
|
1080
1077
|
raise ArgumentError, 'Receiver is not a temperature unit' unless degree?
|
1078
|
+
|
1081
1079
|
start_unit = units
|
1082
|
-
|
1080
|
+
# @type [String]
|
1081
|
+
target_unit = case other
|
1082
|
+
when Unit
|
1083
1083
|
other.units
|
1084
|
-
|
1084
|
+
when String
|
1085
1085
|
other
|
1086
|
+
else
|
1087
|
+
raise ArgumentError, 'Unknown target units'
|
1086
1088
|
end
|
1089
|
+
return self if target_unit == start_unit
|
1090
|
+
|
1091
|
+
# @type [Numeric]
|
1087
1092
|
@base_scalar ||= case @@unit_map[start_unit]
|
1088
1093
|
when '<tempC>'
|
1089
1094
|
@scalar + 273.15
|
@@ -1094,36 +1099,48 @@ module RubyUnits
|
|
1094
1099
|
when '<tempR>'
|
1095
1100
|
@scalar.to_r * Rational(5, 9)
|
1096
1101
|
end
|
1102
|
+
# @type [Numeric]
|
1097
1103
|
q = case @@unit_map[target_unit]
|
1098
1104
|
when '<tempC>'
|
1099
|
-
@base_scalar - 273.
|
1105
|
+
@base_scalar - 273.15
|
1100
1106
|
when '<tempK>'
|
1101
1107
|
@base_scalar
|
1102
1108
|
when '<tempF>'
|
1103
|
-
@base_scalar.to_r * Rational(9, 5) - 459.67r
|
1109
|
+
(@base_scalar.to_r * Rational(9, 5)) - 459.67r
|
1104
1110
|
when '<tempR>'
|
1105
1111
|
@base_scalar.to_r * Rational(9, 5)
|
1106
1112
|
end
|
1107
|
-
return
|
1113
|
+
return self.class.new("#{q} #{target_unit}")
|
1108
1114
|
else
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1115
|
+
# @type [Unit]
|
1116
|
+
target = case other
|
1117
|
+
when Unit
|
1118
|
+
other
|
1119
|
+
when String
|
1120
|
+
self.class.new(other)
|
1121
|
+
else
|
1122
|
+
raise ArgumentError, 'Unknown target units'
|
1123
|
+
end
|
1124
|
+
return self if target.units == units
|
1125
|
+
|
1118
1126
|
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ target
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
+
|
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
|
1132
|
+
|
1133
|
+
# If the scalar is an Integer, convert it to a Rational number so that
|
1134
|
+
# if the value is scaled during conversion, resolution is not lost due
|
1135
|
+
# to integer math
|
1136
|
+
# @type [Rational, Numeric]
|
1137
|
+
conversion_scalar = @scalar.is_a?(Integer) ? @scalar.to_r : @scalar
|
1138
|
+
q = conversion_scalar * (numerator1 + denominator2).reduce(1, :*) / (numerator2 + denominator1).reduce(1, :*)
|
1139
|
+
# Convert the scalar to an Integer if the result is equivalent to an
|
1140
|
+
# integer
|
1141
|
+
|
1142
|
+
q = q.to_i if @scalar.is_a?(Integer) && q.to_i == q
|
1143
|
+
self.class.new(scalar: q, numerator: target.numerator, denominator: target.denominator, signature: target.signature)
|
1127
1144
|
end
|
1128
1145
|
end
|
1129
1146
|
|
@@ -1170,52 +1187,29 @@ module RubyUnits
|
|
1170
1187
|
to_s
|
1171
1188
|
end
|
1172
1189
|
|
1173
|
-
#
|
1190
|
+
# Returns the 'unit' part of the Unit object without the scalar
|
1191
|
+
#
|
1192
|
+
# @param with_prefix [Boolean] include prefixes in output
|
1174
1193
|
# @return [String]
|
1175
1194
|
def units(with_prefix: true)
|
1176
1195
|
return '' if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY
|
1196
|
+
|
1177
1197
|
output_numerator = ['1']
|
1178
1198
|
output_denominator = []
|
1179
1199
|
num = @numerator.clone.compact
|
1180
1200
|
den = @denominator.clone.compact
|
1181
1201
|
|
1182
1202
|
unless num == UNITY_ARRAY
|
1183
|
-
definitions = num.map { |element|
|
1203
|
+
definitions = num.map { |element| self.class.definition(element) }
|
1184
1204
|
definitions.reject!(&:prefix?) unless with_prefix
|
1185
|
-
|
1186
|
-
# see https://github.com/jruby/jruby/issues/4410
|
1187
|
-
# TODO: fix this after jruby fixes their bug.
|
1188
|
-
definitions = if definitions.respond_to?(:chunk_while) && RUBY_ENGINE != 'jruby'
|
1189
|
-
definitions.chunk_while { |defn, _| defn.prefix? }.to_a
|
1190
|
-
else # chunk_while is new to ruby 2.3+, so fallback to less efficient methods for older ruby
|
1191
|
-
result = []
|
1192
|
-
enumerator = definitions.to_enum
|
1193
|
-
loop do
|
1194
|
-
first = enumerator.next
|
1195
|
-
result << (first.prefix? ? [first, enumerator.next] : [first])
|
1196
|
-
end
|
1197
|
-
result
|
1198
|
-
end
|
1205
|
+
definitions = definitions.chunk_while { |defn, _| defn.prefix? }.to_a
|
1199
1206
|
output_numerator = definitions.map { |element| element.map(&:display_name).join }
|
1200
1207
|
end
|
1201
1208
|
|
1202
1209
|
unless den == UNITY_ARRAY
|
1203
|
-
definitions = den.map { |element|
|
1210
|
+
definitions = den.map { |element| self.class.definition(element) }
|
1204
1211
|
definitions.reject!(&:prefix?) unless with_prefix
|
1205
|
-
|
1206
|
-
# see https://github.com/jruby/jruby/issues/4410
|
1207
|
-
# TODO: fix this after jruby fixes their bug.
|
1208
|
-
definitions = if definitions.respond_to?(:chunk_while) && RUBY_ENGINE != 'jruby'
|
1209
|
-
definitions.chunk_while { |defn, _| defn.prefix? }.to_a
|
1210
|
-
else # chunk_while is new to ruby 2.3+, so fallback to less efficient methods for older ruby
|
1211
|
-
result = []
|
1212
|
-
enumerator = definitions.to_enum
|
1213
|
-
loop do
|
1214
|
-
first = enumerator.next
|
1215
|
-
result << (first.prefix? ? [first, enumerator.next] : [first])
|
1216
|
-
end
|
1217
|
-
result
|
1218
|
-
end
|
1212
|
+
definitions = definitions.chunk_while { |defn, _| defn.prefix? }.to_a
|
1219
1213
|
output_denominator = definitions.map { |element| element.map(&:display_name).join }
|
1220
1214
|
end
|
1221
1215
|
|
@@ -1227,7 +1221,7 @@ module RubyUnits
|
|
1227
1221
|
.uniq
|
1228
1222
|
.map { |x| [x, output_denominator.count(x)] }
|
1229
1223
|
.map { |element, power| (element.to_s.strip + (power > 1 ? "^#{power}" : '')) }
|
1230
|
-
"#{on.join('*')}#{od.empty? ? '' :
|
1224
|
+
"#{on.join('*')}#{od.empty? ? '' : "/#{od.join('*')}"}".strip
|
1231
1225
|
end
|
1232
1226
|
|
1233
1227
|
# negates the scalar of the Unit
|
@@ -1241,32 +1235,45 @@ module RubyUnits
|
|
1241
1235
|
# @return [Numeric,Unit]
|
1242
1236
|
def abs
|
1243
1237
|
return @scalar.abs if unitless?
|
1244
|
-
|
1238
|
+
self.class.new(@scalar.abs, @numerator, @denominator)
|
1245
1239
|
end
|
1246
1240
|
|
1247
1241
|
# ceil of a unit
|
1248
1242
|
# @return [Numeric,Unit]
|
1249
|
-
def ceil
|
1250
|
-
return @scalar.ceil if unitless?
|
1251
|
-
|
1243
|
+
def ceil(*args)
|
1244
|
+
return @scalar.ceil(*args) if unitless?
|
1245
|
+
|
1246
|
+
self.class.new(@scalar.ceil(*args), @numerator, @denominator)
|
1252
1247
|
end
|
1253
1248
|
|
1254
1249
|
# @return [Numeric,Unit]
|
1255
|
-
def floor
|
1256
|
-
return @scalar.floor if unitless?
|
1257
|
-
|
1250
|
+
def floor(*args)
|
1251
|
+
return @scalar.floor(*args) if unitless?
|
1252
|
+
|
1253
|
+
self.class.new(@scalar.floor(*args), @numerator, @denominator)
|
1258
1254
|
end
|
1259
1255
|
|
1256
|
+
# Round the unit according to the rules of the scalar's class. Call this
|
1257
|
+
# with the arguments appropriate for the scalar's class (e.g., Integer,
|
1258
|
+
# Rational, etc..). Because unit conversions can often result in Rational
|
1259
|
+
# scalars (to preserve precision), it may be advisable to use +to_s+ to
|
1260
|
+
# format output instead of using +round+.
|
1261
|
+
# @example
|
1262
|
+
# RubyUnits::Unit.new('21870 mm/min').convert_to('m/min').round(1) #=> 2187/100 m/min
|
1263
|
+
# RubyUnits::Unit.new('21870 mm/min').convert_to('m/min').to_s('%0.1f') #=> 21.9 m/min
|
1264
|
+
#
|
1260
1265
|
# @return [Numeric,Unit]
|
1261
|
-
def round(
|
1262
|
-
return @scalar.round(
|
1263
|
-
|
1266
|
+
def round(*args, **kwargs)
|
1267
|
+
return @scalar.round(*args, **kwargs) if unitless?
|
1268
|
+
|
1269
|
+
self.class.new(@scalar.round(*args, **kwargs), @numerator, @denominator)
|
1264
1270
|
end
|
1265
1271
|
|
1266
1272
|
# @return [Numeric, Unit]
|
1267
|
-
def truncate
|
1268
|
-
return @scalar.truncate if unitless?
|
1269
|
-
|
1273
|
+
def truncate(*args)
|
1274
|
+
return @scalar.truncate(*args) if unitless?
|
1275
|
+
|
1276
|
+
self.class.new(@scalar.truncate(*args), @numerator, @denominator)
|
1270
1277
|
end
|
1271
1278
|
|
1272
1279
|
# returns next unit in a range. '1 mm'.to_unit.succ #=> '2 mm'.to_unit
|
@@ -1275,7 +1282,7 @@ module RubyUnits
|
|
1275
1282
|
# @raise [ArgumentError] when scalar is not equal to an integer
|
1276
1283
|
def succ
|
1277
1284
|
raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
|
1278
|
-
|
1285
|
+
self.class.new(@scalar.to_i.succ, @numerator, @denominator)
|
1279
1286
|
end
|
1280
1287
|
|
1281
1288
|
alias next succ
|
@@ -1286,7 +1293,7 @@ module RubyUnits
|
|
1286
1293
|
# @raise [ArgumentError] when scalar is not equal to an integer
|
1287
1294
|
def pred
|
1288
1295
|
raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
|
1289
|
-
|
1296
|
+
self.class.new(@scalar.to_i.pred, @numerator, @denominator)
|
1290
1297
|
end
|
1291
1298
|
|
1292
1299
|
# Tries to make a Time object from current unit. Assumes the current unit hold the duration in seconds from the epoch.
|
@@ -1389,7 +1396,7 @@ module RubyUnits
|
|
1389
1396
|
|
1390
1397
|
# automatically coerce objects to units when possible
|
1391
1398
|
# if an object defines a 'to_unit' method, it will be coerced using that method
|
1392
|
-
# @param [Object, #to_unit]
|
1399
|
+
# @param other [Object, #to_unit]
|
1393
1400
|
# @return [Array]
|
1394
1401
|
def coerce(other)
|
1395
1402
|
return [other.to_unit, self] if other.respond_to? :to_unit
|
@@ -1397,7 +1404,7 @@ module RubyUnits
|
|
1397
1404
|
when Unit
|
1398
1405
|
[other, self]
|
1399
1406
|
else
|
1400
|
-
[
|
1407
|
+
[self.class.new(other), self]
|
1401
1408
|
end
|
1402
1409
|
end
|
1403
1410
|
|
@@ -1409,7 +1416,7 @@ module RubyUnits
|
|
1409
1416
|
else
|
1410
1417
|
@@prefix_values.key(10**((Math.log10(base_scalar) / 3.0).floor * 3))
|
1411
1418
|
end
|
1412
|
-
to(
|
1419
|
+
to(self.class.new(@@prefix_map.key(best_prefix) + units(with_prefix: false)))
|
1413
1420
|
end
|
1414
1421
|
|
1415
1422
|
# override hash method so objects with same values are considered equal
|
@@ -1449,11 +1456,11 @@ module RubyUnits
|
|
1449
1456
|
vector = Array.new(SIGNATURE_VECTOR.size, 0)
|
1450
1457
|
# it's possible to have a kind that misses the array... kinds like :counting
|
1451
1458
|
# are more like prefixes, so don't use them to calculate the vector
|
1452
|
-
@numerator.map { |element|
|
1459
|
+
@numerator.map { |element| self.class.definition(element) }.each do |definition|
|
1453
1460
|
index = SIGNATURE_VECTOR.index(definition.kind)
|
1454
1461
|
vector[index] += 1 if index
|
1455
1462
|
end
|
1456
|
-
@denominator.map { |element|
|
1463
|
+
@denominator.map { |element| self.class.definition(element) }.each do |definition|
|
1457
1464
|
index = SIGNATURE_VECTOR.index(definition.kind)
|
1458
1465
|
vector[index] -= 1 if index
|
1459
1466
|
end
|
@@ -1507,7 +1514,7 @@ module RubyUnits
|
|
1507
1514
|
|
1508
1515
|
if defined?(Complex) && unit_string =~ COMPLEX_NUMBER
|
1509
1516
|
real, imaginary, unit_s = unit_string.scan(COMPLEX_REGEX)[0]
|
1510
|
-
result =
|
1517
|
+
result = self.class.new(unit_s || '1') * Complex(real.to_f, imaginary.to_f)
|
1511
1518
|
copy(result)
|
1512
1519
|
return
|
1513
1520
|
end
|
@@ -1516,7 +1523,7 @@ module RubyUnits
|
|
1516
1523
|
sign, proper, numerator, denominator, unit_s = unit_string.scan(RATIONAL_REGEX)[0]
|
1517
1524
|
sign = sign == '-' ? -1 : 1
|
1518
1525
|
rational = sign * (proper.to_i + Rational(numerator.to_i, denominator.to_i))
|
1519
|
-
result =
|
1526
|
+
result = self.class.new(unit_s || '1') * rational
|
1520
1527
|
copy(result)
|
1521
1528
|
return
|
1522
1529
|
end
|
@@ -1542,13 +1549,14 @@ module RubyUnits
|
|
1542
1549
|
# ... and then strip the remaining brackets for x*y*z
|
1543
1550
|
unit_string.gsub!(/[<>]/, '')
|
1544
1551
|
|
1545
|
-
if unit_string =~
|
1552
|
+
if unit_string =~ TIME_REGEX
|
1546
1553
|
hours, minutes, seconds, microseconds = unit_string.scan(TIME_REGEX)[0]
|
1547
|
-
raise ArgumentError,
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1554
|
+
raise ArgumentError,'Invalid Duration' if [hours, minutes, seconds, microseconds].all?(&:nil?)
|
1555
|
+
|
1556
|
+
result = self.class.new("#{hours || 0} h") +
|
1557
|
+
self.class.new("#{minutes || 0} minutes") +
|
1558
|
+
self.class.new("#{seconds || 0} seconds") +
|
1559
|
+
self.class.new("#{microseconds || 0} usec")
|
1552
1560
|
copy(result)
|
1553
1561
|
return
|
1554
1562
|
end
|
@@ -1557,7 +1565,7 @@ module RubyUnits
|
|
1557
1565
|
# feet -- 6'5"
|
1558
1566
|
feet, inches = unit_string.scan(FEET_INCH_REGEX)[0]
|
1559
1567
|
if feet && inches
|
1560
|
-
result =
|
1568
|
+
result = self.class.new("#{feet} ft") + self.class.new("#{inches} inches")
|
1561
1569
|
copy(result)
|
1562
1570
|
return
|
1563
1571
|
end
|
@@ -1565,7 +1573,7 @@ module RubyUnits
|
|
1565
1573
|
# weight -- 8 lbs 12 oz
|
1566
1574
|
pounds, oz = unit_string.scan(LBS_OZ_REGEX)[0]
|
1567
1575
|
if pounds && oz
|
1568
|
-
result =
|
1576
|
+
result = self.class.new("#{pounds} lbs") + self.class.new("#{oz} oz")
|
1569
1577
|
copy(result)
|
1570
1578
|
return
|
1571
1579
|
end
|
@@ -1573,7 +1581,7 @@ module RubyUnits
|
|
1573
1581
|
# stone -- 3 stone 5, 2 stone, 14 stone 3 pounds, etc.
|
1574
1582
|
stone, pounds = unit_string.scan(STONE_LB_REGEX)[0]
|
1575
1583
|
if stone && pounds
|
1576
|
-
result =
|
1584
|
+
result = self.class.new("#{stone} stone") + self.class.new("#{pounds} lbs")
|
1577
1585
|
copy(result)
|
1578
1586
|
return
|
1579
1587
|
end
|
@@ -1581,6 +1589,7 @@ module RubyUnits
|
|
1581
1589
|
# more than one per. I.e., "1 m/s/s"
|
1582
1590
|
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.count('/') > 1
|
1583
1591
|
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string =~ /\s[02-9]/
|
1592
|
+
|
1584
1593
|
@scalar, top, bottom = unit_string.scan(UNIT_STRING_REGEX)[0] # parse the string into parts
|
1585
1594
|
top.scan(TOP_REGEX).each do |item|
|
1586
1595
|
n = item[1].to_i
|
@@ -1613,12 +1622,12 @@ module RubyUnits
|
|
1613
1622
|
|
1614
1623
|
@numerator ||= UNITY_ARRAY
|
1615
1624
|
@denominator ||= UNITY_ARRAY
|
1616
|
-
@numerator = top.scan(
|
1617
|
-
@denominator = bottom.scan(
|
1625
|
+
@numerator = top.scan(self.class.unit_match_regex).delete_if(&:empty?).compact if top
|
1626
|
+
@denominator = bottom.scan(self.class.unit_match_regex).delete_if(&:empty?).compact if bottom
|
1618
1627
|
|
1619
1628
|
# eliminate all known terms from this string. This is a quick check to see if the passed unit
|
1620
1629
|
# contains terms that are not defined.
|
1621
|
-
used = "#{top} #{bottom}".to_s.gsub(
|
1630
|
+
used = "#{top} #{bottom}".to_s.gsub(self.class.unit_match_regex, '').gsub(%r{[\d\*, "'_^\/\$]}, '')
|
1622
1631
|
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") unless used.empty?
|
1623
1632
|
|
1624
1633
|
@numerator = @numerator.map do |item|
|