ruby-units 2.3.0 → 2.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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|
|