ruby-units 2.4.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/codeql-analysis.yml +3 -3
- data/.solargraph.yml +1 -1
- data/.tool-versions +3 -0
- data/CHANGELOG.txt +3 -1
- data/Gemfile.lock +11 -10
- data/lib/ruby_units/array.rb +17 -7
- data/lib/ruby_units/cache.rb +27 -14
- data/lib/ruby_units/configuration.rb +8 -8
- data/lib/ruby_units/date.rb +46 -53
- data/lib/ruby_units/math.rb +136 -113
- data/lib/ruby_units/numeric.rb +21 -5
- data/lib/ruby_units/string.rb +34 -21
- data/lib/ruby_units/time.rb +76 -69
- data/lib/ruby_units/unit.rb +193 -172
- data/lib/ruby_units/version.rb +1 -1
- metadata +3 -2
data/lib/ruby_units/unit.rb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
require 'date'
|
2
|
-
# Copyright 2006-2015
|
3
|
-
# @author Kevin C. Olbrich, Ph.D.
|
4
|
-
# @see https://github.com/olbrich/ruby-units
|
5
|
-
#
|
6
|
-
# @note The accuracy of unit conversions depends on the precision of the conversion factor.
|
7
|
-
# If you have more accurate estimates for particular conversion factors, please send them
|
8
|
-
# to me and I will incorporate them into the next release. It is also incumbent on the end-user
|
9
|
-
# to ensure that the accuracy of any conversions is sufficient for their intended application.
|
10
|
-
#
|
11
|
-
# While there are a large number of unit specified in the base package,
|
12
|
-
# there are also a large number of units that are not included.
|
13
|
-
# This package covers nearly all SI, Imperial, and units commonly used
|
14
|
-
# in the United States. If your favorite units are not listed here, file an issue on github.
|
15
|
-
#
|
16
|
-
# To add or override a unit definition, add a code block like this..
|
17
|
-
# @example Define a new unit
|
18
|
-
# RubyUnits::Unit.define("foobar") do |unit|
|
19
|
-
# unit.aliases = %w{foo fb foo-bar}
|
20
|
-
# unit.definition = RubyUnits::Unit.new("1 baz")
|
21
|
-
# end
|
22
|
-
#
|
23
2
|
module RubyUnits
|
24
|
-
|
3
|
+
# Copyright 2006-2022
|
4
|
+
# @author Kevin C. Olbrich, Ph.D.
|
5
|
+
# @see https://github.com/olbrich/ruby-units
|
6
|
+
#
|
7
|
+
# @note The accuracy of unit conversions depends on the precision of the conversion factor.
|
8
|
+
# If you have more accurate estimates for particular conversion factors, please send them
|
9
|
+
# to me and I will incorporate them into the next release. It is also incumbent on the end-user
|
10
|
+
# to ensure that the accuracy of any conversions is sufficient for their intended application.
|
11
|
+
#
|
12
|
+
# While there are a large number of unit specified in the base package,
|
13
|
+
# there are also a large number of units that are not included.
|
14
|
+
# This package covers nearly all SI, Imperial, and units commonly used
|
15
|
+
# in the United States. If your favorite units are not listed here, file an issue on GitHub.
|
16
|
+
#
|
17
|
+
# To add or override a unit definition, add a code block like this..
|
18
|
+
# @example Define a new unit
|
19
|
+
# RubyUnits::Unit.define("foobar") do |unit|
|
20
|
+
# unit.aliases = %w{foo fb foo-bar}
|
21
|
+
# unit.definition = RubyUnits::Unit.new("1 baz")
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
class Unit < ::Numeric
|
25
25
|
@@definitions = {}
|
26
26
|
@@prefix_values = {}
|
27
27
|
@@prefix_map = {}
|
@@ -53,15 +53,15 @@ module RubyUnits
|
|
53
53
|
# -123.4E+5, -123.4e-5, etc.
|
54
54
|
SCI_NUMBER = /([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)/.freeze
|
55
55
|
# Rational number, including improper fractions: 1 2/3, -1 2/3, 5/3, etc.
|
56
|
-
RATIONAL_NUMBER = %r{\(?([+-])?(\d+[ -])?(\d+)
|
56
|
+
RATIONAL_NUMBER = %r{\(?([+-])?(\d+[ -])?(\d+)/(\d+)\)?}.freeze
|
57
57
|
# Complex numbers: 1+2i, 1.0+2.0i, -1-1i, etc.
|
58
58
|
COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/.freeze
|
59
59
|
# Any Complex, Rational, or scientific number
|
60
60
|
ANY_NUMBER = /(#{COMPLEX_NUMBER}|#{RATIONAL_NUMBER}|#{SCI_NUMBER})/.freeze
|
61
61
|
ANY_NUMBER_REGEX = /(?:#{ANY_NUMBER})?\s?([^-\d.].*)?/.freeze
|
62
62
|
NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/.freeze
|
63
|
-
UNIT_STRING_REGEX = %r{#{SCI_NUMBER}*\s*([
|
64
|
-
TOP_REGEX = /([^
|
63
|
+
UNIT_STRING_REGEX = %r{#{SCI_NUMBER}*\s*([^/]*)/*(.+)*}.freeze
|
64
|
+
TOP_REGEX = /([^ *]+)(?:\^|\*\*)([\d-]+)/.freeze
|
65
65
|
BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/.freeze
|
66
66
|
NUMBER_UNIT_REGEX = /#{SCI_NUMBER}?(.*)/.freeze
|
67
67
|
COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/.freeze
|
@@ -84,62 +84,60 @@ module RubyUnits
|
|
84
84
|
angle
|
85
85
|
].freeze
|
86
86
|
@@kinds = {
|
87
|
-
-312_078
|
88
|
-
-312_058
|
89
|
-
-312_038
|
90
|
-
-152_040
|
91
|
-
-152_038
|
92
|
-
-152_058
|
93
|
-
-7997
|
94
|
-
-79
|
95
|
-
-59
|
96
|
-
-39
|
97
|
-
-38
|
98
|
-
-20
|
99
|
-
-19
|
100
|
-
-18
|
101
|
-
-17
|
102
|
-
-1
|
103
|
-
0
|
104
|
-
1
|
105
|
-
2
|
106
|
-
3
|
107
|
-
20
|
108
|
-
400
|
109
|
-
7941
|
110
|
-
7942
|
111
|
-
7959
|
112
|
-
7962
|
113
|
-
7979
|
114
|
-
7961
|
115
|
-
7981
|
116
|
-
7982
|
117
|
-
7997
|
118
|
-
7998
|
119
|
-
8000
|
120
|
-
152_020
|
121
|
-
159_999
|
122
|
-
160_000
|
123
|
-
160_020
|
124
|
-
312_058
|
125
|
-
312_078
|
126
|
-
3_199_980
|
127
|
-
3_199_997
|
128
|
-
3_200_000
|
129
|
-
63_999_998
|
130
|
-
64_000_000
|
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,
|
131
131
|
1_280_000_000 => :currency,
|
132
|
-
25_600_000_000
|
132
|
+
25_600_000_000 => :information,
|
133
133
|
511_999_999_980 => :angular_velocity,
|
134
134
|
512_000_000_000 => :angle
|
135
135
|
}.freeze
|
136
|
-
@@cached_units = {}
|
137
|
-
@@base_unit_cache = {}
|
138
136
|
|
139
137
|
# Class Methods
|
140
138
|
|
141
139
|
# setup internal arrays and hashes
|
142
|
-
# @return [
|
140
|
+
# @return [Boolean]
|
143
141
|
def self.setup
|
144
142
|
clear_cache
|
145
143
|
@@prefix_values = {}
|
@@ -180,7 +178,7 @@ module RubyUnits
|
|
180
178
|
end
|
181
179
|
|
182
180
|
# @param [RubyUnits::Unit::Definition, String] unit_definition
|
183
|
-
# @param [
|
181
|
+
# @param [Proc] block
|
184
182
|
# @return [RubyUnits::Unit::Definition]
|
185
183
|
# @raise [ArgumentError] when passed a non-string if using the block form
|
186
184
|
# Unpack a unit definition and add it to the array of defined units
|
@@ -195,7 +193,8 @@ module RubyUnits
|
|
195
193
|
# RubyUnits::Unit.define(unit_definition)
|
196
194
|
def self.define(unit_definition, &block)
|
197
195
|
if block_given?
|
198
|
-
raise ArgumentError, 'When using the block form of RubyUnits::Unit.define, pass the name of the unit' unless unit_definition.
|
196
|
+
raise ArgumentError, 'When using the block form of RubyUnits::Unit.define, pass the name of the unit' unless unit_definition.is_a?(String)
|
197
|
+
|
199
198
|
unit_definition = RubyUnits::Unit::Definition.new(unit_definition, &block)
|
200
199
|
end
|
201
200
|
definitions[unit_definition.name] = unit_definition
|
@@ -206,7 +205,7 @@ module RubyUnits
|
|
206
205
|
# Get the definition for a unit and allow it to be redefined
|
207
206
|
#
|
208
207
|
# @param [String] name Name of unit to redefine
|
209
|
-
# @param [
|
208
|
+
# @param [Proc] _block
|
210
209
|
# @raise [ArgumentError] if a block is not given
|
211
210
|
# @yieldparam [RubyUnits::Unit::Definition] the definition of the unit being
|
212
211
|
# redefined
|
@@ -232,22 +231,24 @@ module RubyUnits
|
|
232
231
|
setup
|
233
232
|
end
|
234
233
|
|
235
|
-
#
|
234
|
+
# Unit cache
|
235
|
+
#
|
236
|
+
# @return [RubyUnits::Cache]
|
236
237
|
def self.cached
|
237
|
-
|
238
|
+
@cached ||= RubyUnits::Cache.new
|
238
239
|
end
|
239
240
|
|
240
|
-
# @return [
|
241
|
+
# @return [Boolean]
|
241
242
|
def self.clear_cache
|
242
|
-
|
243
|
-
|
243
|
+
cached.clear
|
244
|
+
base_unit_cache.clear
|
244
245
|
new(1)
|
245
246
|
true
|
246
247
|
end
|
247
248
|
|
248
|
-
# @return [
|
249
|
+
# @return [RubyUnits::Cache]
|
249
250
|
def self.base_unit_cache
|
250
|
-
|
251
|
+
@base_unit_cache ||= RubyUnits::Cache.new
|
251
252
|
end
|
252
253
|
|
253
254
|
# @example parse strings
|
@@ -269,7 +270,7 @@ module RubyUnits
|
|
269
270
|
num.delete(UNITY)
|
270
271
|
den.delete(UNITY)
|
271
272
|
|
272
|
-
combined = Hash.new(0)
|
273
|
+
combined = ::Hash.new(0)
|
273
274
|
|
274
275
|
[[num, 1], [den, -1]].each do |array, increment|
|
275
276
|
array.chunk_while { |elt_before, _| definition(elt_before).prefix? }
|
@@ -301,11 +302,12 @@ module RubyUnits
|
|
301
302
|
# return an array of base units
|
302
303
|
# @return [Array]
|
303
304
|
def self.base_units
|
304
|
-
@@base_units ||= @@definitions.dup.
|
305
|
+
@@base_units ||= @@definitions.dup.select { |_, definition| definition.base? }.keys.map { |u| new(u) }
|
305
306
|
end
|
306
307
|
|
307
|
-
#
|
308
|
+
# Parse a string consisting of a number and a unit string
|
308
309
|
# NOTE: This does not properly handle units formatted like '12mg/6ml'
|
310
|
+
#
|
309
311
|
# @param [String] string
|
310
312
|
# @return [Array(Numeric, String)] consisting of [number, "unit"]
|
311
313
|
def self.parse_into_numbers_and_units(string)
|
@@ -339,7 +341,7 @@ module RubyUnits
|
|
339
341
|
end
|
340
342
|
|
341
343
|
# return a regex used to match units
|
342
|
-
# @return [
|
344
|
+
# @return [Regexp]
|
343
345
|
def self.unit_match_regex
|
344
346
|
@@unit_match_regex ||= /(#{prefix_regex})??(#{unit_regex})\b/
|
345
347
|
end
|
@@ -353,7 +355,7 @@ module RubyUnits
|
|
353
355
|
|
354
356
|
# Generates (and memoizes) a regexp matching any of the temperature units or their aliases.
|
355
357
|
#
|
356
|
-
# @return [
|
358
|
+
# @return [Regexp]
|
357
359
|
def self.temp_regex
|
358
360
|
@@temp_regex ||= begin
|
359
361
|
temp_units = %w[tempK tempC tempF tempR degK degC degF degR]
|
@@ -416,20 +418,16 @@ module RubyUnits
|
|
416
418
|
attr_accessor :unit_name
|
417
419
|
|
418
420
|
# Used to copy one unit to another
|
419
|
-
# @param [Unit]
|
420
|
-
# @return [Unit]
|
421
|
+
# @param from [RubyUnits::Unit] Unit to copy definition from
|
422
|
+
# @return [RubyUnits::Unit]
|
421
423
|
def copy(from)
|
422
|
-
@scalar
|
423
|
-
@numerator
|
424
|
+
@scalar = from.scalar
|
425
|
+
@numerator = from.numerator
|
424
426
|
@denominator = from.denominator
|
425
427
|
@base = from.base?
|
426
|
-
@signature
|
428
|
+
@signature = from.signature
|
427
429
|
@base_scalar = from.base_scalar
|
428
|
-
@unit_name =
|
429
|
-
from.unit_name
|
430
|
-
rescue
|
431
|
-
nil
|
432
|
-
end
|
430
|
+
@unit_name = from.unit_name
|
433
431
|
self
|
434
432
|
end
|
435
433
|
|
@@ -464,26 +462,22 @@ module RubyUnits
|
|
464
462
|
if options.size == 2
|
465
463
|
# options[0] is the scalar
|
466
464
|
# options[1] is a unit string
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
options[1].units
|
473
|
-
rescue
|
474
|
-
options[1]
|
475
|
-
end)}")
|
465
|
+
cached = self.class.cached.get(options[1])
|
466
|
+
if cached.nil?
|
467
|
+
initialize("#{options[0]} #{options[1]}")
|
468
|
+
else
|
469
|
+
copy(cached * options[0])
|
476
470
|
end
|
477
471
|
return
|
478
472
|
end
|
479
473
|
if options.size == 3
|
480
474
|
options[1] = options[1].join if options[1].is_a?(Array)
|
481
475
|
options[2] = options[2].join if options[2].is_a?(Array)
|
482
|
-
|
483
|
-
|
484
|
-
copy(cached)
|
485
|
-
rescue
|
476
|
+
cached = self.class.cached.get("#{options[1]}/#{options[2]}")
|
477
|
+
if cached.nil?
|
486
478
|
initialize("#{options[0]} #{options[1]}/#{options[2]}")
|
479
|
+
else
|
480
|
+
copy(cached) * options[0]
|
487
481
|
end
|
488
482
|
return
|
489
483
|
end
|
@@ -524,17 +518,17 @@ module RubyUnits
|
|
524
518
|
unary_unit = units || ''
|
525
519
|
if options.first.instance_of?(String)
|
526
520
|
_opt_scalar, opt_units = self.class.parse_into_numbers_and_units(options[0])
|
527
|
-
|
521
|
+
if !(self.class.cached.keys.include?(opt_units) ||
|
528
522
|
(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?(.+)?|±
|
530
|
-
|
523
|
+
(opt_units =~ %r{(#{self.class.temp_regex})|(#{STONE_LB_UNIT_REGEX})|(#{LBS_OZ_UNIT_REGEX})|(#{FEET_INCH_UNITS_REGEX})|%|(#{TIME_REGEX})|i\s?(.+)?|±|\+/-})) && (opt_units && !opt_units.empty?)
|
524
|
+
self.class.cached.set(opt_units, scalar == 1 ? self : opt_units.to_unit)
|
531
525
|
end
|
532
526
|
end
|
533
|
-
unless
|
534
|
-
|
527
|
+
unless self.class.cached.keys.include?(unary_unit) || (unary_unit =~ self.class.temp_regex)
|
528
|
+
self.class.cached.set(unary_unit, scalar == 1 ? self : unary_unit.to_unit)
|
535
529
|
end
|
536
530
|
[@scalar, @numerator, @denominator, @base_scalar, @signature, @base].each(&:freeze)
|
537
|
-
|
531
|
+
super()
|
538
532
|
end
|
539
533
|
|
540
534
|
# @todo: figure out how to handle :counting units. This method should probably return :counting instead of :unitless for 'each'
|
@@ -544,9 +538,14 @@ module RubyUnits
|
|
544
538
|
@@kinds[signature]
|
545
539
|
end
|
546
540
|
|
547
|
-
#
|
548
|
-
|
549
|
-
|
541
|
+
# Convert the unit to a Unit, possibly performing a conversion.
|
542
|
+
# > The ability to pass a Unit to convert to was added in v3.0.0 for
|
543
|
+
# > consistency with other uses of #to_unit.
|
544
|
+
#
|
545
|
+
# @param other [RubyUnits::Unit, String] unit to convert to
|
546
|
+
# @return [RubyUnits::Unit]
|
547
|
+
def to_unit(other = nil)
|
548
|
+
other ? convert_to(other) : self
|
550
549
|
end
|
551
550
|
|
552
551
|
alias unit to_unit
|
@@ -555,6 +554,7 @@ module RubyUnits
|
|
555
554
|
# @return [Boolean]
|
556
555
|
def base?
|
557
556
|
return @base if defined? @base
|
557
|
+
|
558
558
|
@base = (@numerator + @denominator)
|
559
559
|
.compact
|
560
560
|
.uniq
|
@@ -571,6 +571,7 @@ module RubyUnits
|
|
571
571
|
# @todo this is brittle as it depends on the display_name of a unit, which can be changed
|
572
572
|
def to_base
|
573
573
|
return self if base?
|
574
|
+
|
574
575
|
if @@unit_map[units] =~ /\A<(?:temp|deg)[CRF]>\Z/
|
575
576
|
@signature = @@kinds.key(:temperature)
|
576
577
|
base = if temperature?
|
@@ -581,12 +582,8 @@ module RubyUnits
|
|
581
582
|
return base
|
582
583
|
end
|
583
584
|
|
584
|
-
|
585
|
-
|
586
|
-
rescue
|
587
|
-
nil
|
588
|
-
end)
|
589
|
-
return cached if cached
|
585
|
+
cached_unit = self.class.base_unit_cache.get(units)
|
586
|
+
return cached_unit * scalar unless cached_unit.nil?
|
590
587
|
|
591
588
|
num = []
|
592
589
|
den = []
|
@@ -614,7 +611,7 @@ module RubyUnits
|
|
614
611
|
den = den.flatten.compact
|
615
612
|
num = UNITY_ARRAY if num.empty?
|
616
613
|
base = self.class.new(self.class.eliminate_terms(q, num, den))
|
617
|
-
|
614
|
+
self.class.base_unit_cache.set(units, base)
|
618
615
|
base * @scalar
|
619
616
|
end
|
620
617
|
|
@@ -639,6 +636,7 @@ module RubyUnits
|
|
639
636
|
def to_s(target_units = nil)
|
640
637
|
out = @output[target_units]
|
641
638
|
return out if out
|
639
|
+
|
642
640
|
separator = RubyUnits.configuration.separator
|
643
641
|
case target_units
|
644
642
|
when :ft
|
@@ -654,14 +652,14 @@ module RubyUnits
|
|
654
652
|
out = case target_units.strip
|
655
653
|
when /\A\s*\Z/ # whitespace only
|
656
654
|
''
|
657
|
-
when /(%[
|
655
|
+
when /(%[\-+.\w#]+)\s*(.+)*/ # format string like '%0.2f in'
|
658
656
|
begin
|
659
657
|
if Regexp.last_match(2) # unit specified, need to convert
|
660
658
|
convert_to(Regexp.last_match(2)).to_s(Regexp.last_match(1))
|
661
659
|
else
|
662
660
|
"#{Regexp.last_match(1) % @scalar}#{separator}#{Regexp.last_match(2) || units}".strip
|
663
661
|
end
|
664
|
-
rescue # parse it like a strftime format string
|
662
|
+
rescue StandardError # parse it like a strftime format string
|
665
663
|
(DateTime.new(0) + self).strftime(target_units)
|
666
664
|
end
|
667
665
|
when /(\S+)/ # unit only 'mm' or '1/mm'
|
@@ -688,6 +686,7 @@ module RubyUnits
|
|
688
686
|
# @return [String]
|
689
687
|
def inspect(dump = nil)
|
690
688
|
return super() if dump
|
689
|
+
|
691
690
|
to_s
|
692
691
|
end
|
693
692
|
|
@@ -695,7 +694,7 @@ module RubyUnits
|
|
695
694
|
# @return [Boolean]
|
696
695
|
# @todo use unit definition to determine if it's a temperature instead of a regex
|
697
696
|
def temperature?
|
698
|
-
degree? &&
|
697
|
+
degree? && units.match?(self.class.temp_regex)
|
699
698
|
end
|
700
699
|
|
701
700
|
alias is_temperature? temperature?
|
@@ -713,6 +712,7 @@ module RubyUnits
|
|
713
712
|
# @return [String] possible values: degC, degF, degR, or degK
|
714
713
|
def temperature_scale
|
715
714
|
return nil unless temperature?
|
715
|
+
|
716
716
|
"deg#{@@unit_map[units][/temp([CFRK])/, 1]}"
|
717
717
|
end
|
718
718
|
|
@@ -726,17 +726,19 @@ module RubyUnits
|
|
726
726
|
# Compare two Unit objects. Throws an exception if they are not of compatible types.
|
727
727
|
# Comparisons are done based on the value of the unit in base SI units.
|
728
728
|
# @param [Object] other
|
729
|
-
# @return [
|
729
|
+
# @return [Integer,nil]
|
730
730
|
# @raise [NoMethodError] when other does not define <=>
|
731
731
|
# @raise [ArgumentError] when units are not compatible
|
732
732
|
def <=>(other)
|
733
733
|
raise NoMethodError, "undefined method `<=>' for #{base_scalar.inspect}" unless base_scalar.respond_to?(:<=>)
|
734
|
+
|
734
735
|
if other.nil?
|
735
736
|
base_scalar <=> nil
|
736
737
|
elsif !temperature? && other.respond_to?(:zero?) && other.zero?
|
737
738
|
base_scalar <=> 0
|
738
739
|
elsif other.instance_of?(Unit)
|
739
740
|
raise ArgumentError, "Incompatible Units ('#{units}' not compatible with '#{other.units}')" unless self =~ other
|
741
|
+
|
740
742
|
base_scalar <=> other.base_scalar
|
741
743
|
else
|
742
744
|
x, y = coerce(other)
|
@@ -757,6 +759,7 @@ module RubyUnits
|
|
757
759
|
zero?
|
758
760
|
elsif other.instance_of?(Unit)
|
759
761
|
return false unless self =~ other
|
762
|
+
|
760
763
|
base_scalar == other.base_scalar
|
761
764
|
else
|
762
765
|
begin
|
@@ -784,9 +787,9 @@ module RubyUnits
|
|
784
787
|
else
|
785
788
|
begin
|
786
789
|
x, y = coerce(other)
|
787
|
-
|
790
|
+
x =~ y
|
788
791
|
rescue ArgumentError
|
789
|
-
|
792
|
+
false
|
790
793
|
end
|
791
794
|
end
|
792
795
|
end
|
@@ -807,9 +810,9 @@ module RubyUnits
|
|
807
810
|
else
|
808
811
|
begin
|
809
812
|
x, y = coerce(other)
|
810
|
-
|
813
|
+
x === y
|
811
814
|
rescue ArgumentError
|
812
|
-
|
815
|
+
false
|
813
816
|
end
|
814
817
|
end
|
815
818
|
end
|
@@ -832,6 +835,7 @@ module RubyUnits
|
|
832
835
|
other.dup
|
833
836
|
elsif self =~ other
|
834
837
|
raise ArgumentError, 'Cannot add two temperatures' if [self, other].all?(&:temperature?)
|
838
|
+
|
835
839
|
if [self, other].any?(&:temperature?)
|
836
840
|
if temperature?
|
837
841
|
self.class.new(scalar: (scalar + other.convert_to(temperature_scale).scalar), numerator: @numerator, denominator: @denominator, signature: @signature)
|
@@ -881,7 +885,7 @@ module RubyUnits
|
|
881
885
|
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
|
882
886
|
end
|
883
887
|
when Time
|
884
|
-
raise ArgumentError, 'Date and Time objects represent fixed points in time and cannot be subtracted from
|
888
|
+
raise ArgumentError, 'Date and Time objects represent fixed points in time and cannot be subtracted from a Unit'
|
885
889
|
else
|
886
890
|
x, y = coerce(other)
|
887
891
|
y - x
|
@@ -896,6 +900,7 @@ module RubyUnits
|
|
896
900
|
case other
|
897
901
|
when Unit
|
898
902
|
raise ArgumentError, 'Cannot multiply by temperatures' if [other, self].any?(&:temperature?)
|
903
|
+
|
899
904
|
opts = self.class.eliminate_terms(@scalar * other.scalar, @numerator + other.numerator, @denominator + other.denominator)
|
900
905
|
opts[:signature] = @signature + other.signature
|
901
906
|
self.class.new(opts)
|
@@ -918,6 +923,7 @@ module RubyUnits
|
|
918
923
|
when Unit
|
919
924
|
raise ZeroDivisionError if other.zero?
|
920
925
|
raise ArgumentError, 'Cannot divide with temperatures' if [other, self].any?(&:temperature?)
|
926
|
+
|
921
927
|
sc = Rational(@scalar, other.scalar)
|
922
928
|
sc = sc.numerator if sc.denominator == 1
|
923
929
|
opts = self.class.eliminate_terms(sc, @numerator + other.denominator, @denominator + other.numerator)
|
@@ -925,6 +931,7 @@ module RubyUnits
|
|
925
931
|
self.class.new(opts)
|
926
932
|
when Numeric
|
927
933
|
raise ZeroDivisionError if other.zero?
|
934
|
+
|
928
935
|
sc = Rational(@scalar, other)
|
929
936
|
sc = sc.numerator if sc.denominator == 1
|
930
937
|
self.class.new(scalar: sc, numerator: @numerator, denominator: @denominator, signature: @signature)
|
@@ -942,6 +949,7 @@ module RubyUnits
|
|
942
949
|
def divmod(other)
|
943
950
|
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ other
|
944
951
|
return scalar.divmod(other.scalar) if units == other.units
|
952
|
+
|
945
953
|
to_base.scalar.divmod(other.to_base.scalar)
|
946
954
|
end
|
947
955
|
|
@@ -952,7 +960,7 @@ module RubyUnits
|
|
952
960
|
divmod(other).last
|
953
961
|
end
|
954
962
|
|
955
|
-
#
|
963
|
+
# Exponentiation. Only takes integer powers.
|
956
964
|
# Note that anything raised to the power of 0 results in a Unit object with a scalar of 1, and no units.
|
957
965
|
# Throws an exception if exponent is not an integer.
|
958
966
|
# Ideally this routine should accept a float for the exponent
|
@@ -968,6 +976,7 @@ module RubyUnits
|
|
968
976
|
# @raise [ArgumentError] when an invalid exponent is passed
|
969
977
|
def **(other)
|
970
978
|
raise ArgumentError, 'Cannot raise a temperature to a power' if temperature?
|
979
|
+
|
971
980
|
if other.is_a?(Numeric)
|
972
981
|
return inverse if other == -1
|
973
982
|
return self if other == 1
|
@@ -975,14 +984,16 @@ module RubyUnits
|
|
975
984
|
end
|
976
985
|
case other
|
977
986
|
when Rational
|
978
|
-
|
987
|
+
power(other.numerator).root(other.denominator)
|
979
988
|
when Integer
|
980
|
-
|
989
|
+
power(other)
|
981
990
|
when Float
|
982
991
|
return self**other.to_i if other == other.to_i
|
992
|
+
|
983
993
|
valid = (1..9).map { |n| Rational(1, n) }
|
984
994
|
raise ArgumentError, 'Not a n-th root (1..9), use 1/n' unless valid.include? other.abs
|
985
|
-
|
995
|
+
|
996
|
+
root(Rational(1, other).to_int)
|
986
997
|
when Complex
|
987
998
|
raise ArgumentError, 'exponentiation of complex numbers is not supported.'
|
988
999
|
else
|
@@ -1002,6 +1013,7 @@ module RubyUnits
|
|
1002
1013
|
return 1 if n.zero?
|
1003
1014
|
return self if n == 1
|
1004
1015
|
return (1..(n - 1).to_i).inject(self) { |acc, _elem| acc * self } if n >= 0
|
1016
|
+
|
1005
1017
|
(1..-(n - 1).to_i).inject(self) { |acc, _elem| acc / self }
|
1006
1018
|
end
|
1007
1019
|
|
@@ -1009,7 +1021,7 @@ module RubyUnits
|
|
1009
1021
|
# if n < 0, returns 1/unit^(1/n)
|
1010
1022
|
# @param [Integer] n
|
1011
1023
|
# @return [Unit]
|
1012
|
-
# @raise [ArgumentError] when
|
1024
|
+
# @raise [ArgumentError] when attempting to take the root of a temperature
|
1013
1025
|
# @raise [ArgumentError] when n is not an integer
|
1014
1026
|
# @raise [ArgumentError] when n is 0
|
1015
1027
|
def root(n)
|
@@ -1022,6 +1034,7 @@ module RubyUnits
|
|
1022
1034
|
vec = unit_signature_vector
|
1023
1035
|
vec = vec.map { |x| x % n }
|
1024
1036
|
raise ArgumentError, 'Illegal root' unless vec.max.zero?
|
1037
|
+
|
1025
1038
|
num = @numerator.dup
|
1026
1039
|
den = @denominator.dup
|
1027
1040
|
|
@@ -1110,7 +1123,7 @@ module RubyUnits
|
|
1110
1123
|
when '<tempR>'
|
1111
1124
|
@base_scalar.to_r * Rational(9, 5)
|
1112
1125
|
end
|
1113
|
-
|
1126
|
+
self.class.new("#{q} #{target_unit}")
|
1114
1127
|
else
|
1115
1128
|
# @type [Unit]
|
1116
1129
|
target = case other
|
@@ -1138,7 +1151,6 @@ module RubyUnits
|
|
1138
1151
|
q = conversion_scalar * (numerator1 + denominator2).reduce(1, :*) / (numerator2 + denominator1).reduce(1, :*)
|
1139
1152
|
# Convert the scalar to an Integer if the result is equivalent to an
|
1140
1153
|
# integer
|
1141
|
-
|
1142
1154
|
q = q.to_i if @scalar.is_a?(Integer) && q.to_i == q
|
1143
1155
|
self.class.new(scalar: q, numerator: target.numerator, denominator: target.denominator, signature: target.signature)
|
1144
1156
|
end
|
@@ -1152,6 +1164,7 @@ module RubyUnits
|
|
1152
1164
|
# @raise [RuntimeError] when not unitless
|
1153
1165
|
def to_f
|
1154
1166
|
return @scalar.to_f if unitless?
|
1167
|
+
|
1155
1168
|
raise "Cannot convert '#{self}' to Float unless unitless. Use Unit#scalar"
|
1156
1169
|
end
|
1157
1170
|
|
@@ -1160,6 +1173,7 @@ module RubyUnits
|
|
1160
1173
|
# @raise [RuntimeError] when not unitless
|
1161
1174
|
def to_c
|
1162
1175
|
return Complex(@scalar) if unitless?
|
1176
|
+
|
1163
1177
|
raise "Cannot convert '#{self}' to Complex unless unitless. Use Unit#scalar"
|
1164
1178
|
end
|
1165
1179
|
|
@@ -1168,6 +1182,7 @@ module RubyUnits
|
|
1168
1182
|
# @raise [RuntimeError] when not unitless
|
1169
1183
|
def to_i
|
1170
1184
|
return @scalar.to_int if unitless?
|
1185
|
+
|
1171
1186
|
raise "Cannot convert '#{self}' to Integer unless unitless. Use Unit#scalar"
|
1172
1187
|
end
|
1173
1188
|
|
@@ -1178,6 +1193,7 @@ module RubyUnits
|
|
1178
1193
|
# @raise [RuntimeError] when not unitless
|
1179
1194
|
def to_r
|
1180
1195
|
return @scalar.to_r if unitless?
|
1196
|
+
|
1181
1197
|
raise "Cannot convert '#{self}' to Rational unless unitless. Use Unit#scalar"
|
1182
1198
|
end
|
1183
1199
|
|
@@ -1202,14 +1218,14 @@ module RubyUnits
|
|
1202
1218
|
unless num == UNITY_ARRAY
|
1203
1219
|
definitions = num.map { |element| self.class.definition(element) }
|
1204
1220
|
definitions.reject!(&:prefix?) unless with_prefix
|
1205
|
-
definitions = definitions.chunk_while { |
|
1221
|
+
definitions = definitions.chunk_while { |definition, _| definition.prefix? }.to_a
|
1206
1222
|
output_numerator = definitions.map { |element| element.map(&:display_name).join }
|
1207
1223
|
end
|
1208
1224
|
|
1209
1225
|
unless den == UNITY_ARRAY
|
1210
1226
|
definitions = den.map { |element| self.class.definition(element) }
|
1211
1227
|
definitions.reject!(&:prefix?) unless with_prefix
|
1212
|
-
definitions = definitions.chunk_while { |
|
1228
|
+
definitions = definitions.chunk_while { |definition, _| definition.prefix? }.to_a
|
1213
1229
|
output_denominator = definitions.map { |element| element.map(&:display_name).join }
|
1214
1230
|
end
|
1215
1231
|
|
@@ -1228,6 +1244,7 @@ module RubyUnits
|
|
1228
1244
|
# @return [Numeric,Unit]
|
1229
1245
|
def -@
|
1230
1246
|
return -@scalar if unitless?
|
1247
|
+
|
1231
1248
|
dup * -1
|
1232
1249
|
end
|
1233
1250
|
|
@@ -1235,6 +1252,7 @@ module RubyUnits
|
|
1235
1252
|
# @return [Numeric,Unit]
|
1236
1253
|
def abs
|
1237
1254
|
return @scalar.abs if unitless?
|
1255
|
+
|
1238
1256
|
self.class.new(@scalar.abs, @numerator, @denominator)
|
1239
1257
|
end
|
1240
1258
|
|
@@ -1282,6 +1300,7 @@ module RubyUnits
|
|
1282
1300
|
# @raise [ArgumentError] when scalar is not equal to an integer
|
1283
1301
|
def succ
|
1284
1302
|
raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
|
1303
|
+
|
1285
1304
|
self.class.new(@scalar.to_i.succ, @numerator, @denominator)
|
1286
1305
|
end
|
1287
1306
|
|
@@ -1293,6 +1312,7 @@ module RubyUnits
|
|
1293
1312
|
# @raise [ArgumentError] when scalar is not equal to an integer
|
1294
1313
|
def pred
|
1295
1314
|
raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
|
1315
|
+
|
1296
1316
|
self.class.new(@scalar.to_i.pred, @numerator, @denominator)
|
1297
1317
|
end
|
1298
1318
|
|
@@ -1306,7 +1326,7 @@ module RubyUnits
|
|
1306
1326
|
|
1307
1327
|
# convert a duration to a DateTime. This will work so long as the duration is the duration from the zero date
|
1308
1328
|
# defined by DateTime
|
1309
|
-
# @return [DateTime]
|
1329
|
+
# @return [::DateTime]
|
1310
1330
|
def to_datetime
|
1311
1331
|
DateTime.new!(convert_to('d').scalar)
|
1312
1332
|
end
|
@@ -1333,11 +1353,11 @@ module RubyUnits
|
|
1333
1353
|
def before(time_point = ::Time.now)
|
1334
1354
|
case time_point
|
1335
1355
|
when Time, Date, DateTime
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1356
|
+
(begin
|
1357
|
+
time_point - self
|
1358
|
+
rescue StandardError
|
1359
|
+
time_point.to_datetime - self
|
1360
|
+
end)
|
1341
1361
|
else
|
1342
1362
|
raise ArgumentError, 'Must specify a Time, Date, or DateTime'
|
1343
1363
|
end
|
@@ -1352,9 +1372,9 @@ module RubyUnits
|
|
1352
1372
|
def since(time_point)
|
1353
1373
|
case time_point
|
1354
1374
|
when Time
|
1355
|
-
(Time.now - time_point
|
1375
|
+
self.class.new(::Time.now - time_point, 'second').convert_to(self)
|
1356
1376
|
when DateTime, Date
|
1357
|
-
(DateTime.now - time_point
|
1377
|
+
self.class.new(::DateTime.now - time_point, 'day').convert_to(self)
|
1358
1378
|
else
|
1359
1379
|
raise ArgumentError, 'Must specify a Time, Date, or DateTime'
|
1360
1380
|
end
|
@@ -1366,9 +1386,9 @@ module RubyUnits
|
|
1366
1386
|
def until(time_point)
|
1367
1387
|
case time_point
|
1368
1388
|
when Time
|
1369
|
-
(time_point - Time.now
|
1389
|
+
self.class.new(time_point - ::Time.now, 'second').convert_to(self)
|
1370
1390
|
when DateTime, Date
|
1371
|
-
(time_point - DateTime.now
|
1391
|
+
self.class.new(time_point - ::DateTime.now, 'day').convert_to(self)
|
1372
1392
|
else
|
1373
1393
|
raise ArgumentError, 'Must specify a Time, Date, or DateTime'
|
1374
1394
|
end
|
@@ -1382,10 +1402,10 @@ module RubyUnits
|
|
1382
1402
|
case time_point
|
1383
1403
|
when Time, DateTime, Date
|
1384
1404
|
(begin
|
1385
|
-
|
1386
|
-
|
1387
|
-
|
1388
|
-
|
1405
|
+
time_point + self
|
1406
|
+
rescue StandardError
|
1407
|
+
time_point.to_datetime + self
|
1408
|
+
end)
|
1389
1409
|
else
|
1390
1410
|
raise ArgumentError, 'Must specify a Time, Date, or DateTime'
|
1391
1411
|
end
|
@@ -1400,6 +1420,7 @@ module RubyUnits
|
|
1400
1420
|
# @return [Array]
|
1401
1421
|
def coerce(other)
|
1402
1422
|
return [other.to_unit, self] if other.respond_to? :to_unit
|
1423
|
+
|
1403
1424
|
case other
|
1404
1425
|
when Unit
|
1405
1426
|
[other, self]
|
@@ -1411,10 +1432,11 @@ module RubyUnits
|
|
1411
1432
|
# returns a new unit that has been scaled to be more in line with typical usage.
|
1412
1433
|
def best_prefix
|
1413
1434
|
return to_base if scalar.zero?
|
1435
|
+
|
1414
1436
|
best_prefix = if kind == :information
|
1415
|
-
@@prefix_values.key(2**((Math.log(base_scalar, 2) / 10.0).floor * 10))
|
1437
|
+
@@prefix_values.key(2**((::Math.log(base_scalar, 2) / 10.0).floor * 10))
|
1416
1438
|
else
|
1417
|
-
@@prefix_values.key(10**((Math.log10(base_scalar) / 3.0).floor * 3))
|
1439
|
+
@@prefix_values.key(10**((::Math.log10(base_scalar) / 3.0).floor * 3))
|
1418
1440
|
end
|
1419
1441
|
to(self.class.new(@@prefix_map.key(best_prefix) + units(with_prefix: false)))
|
1420
1442
|
end
|
@@ -1453,7 +1475,7 @@ module RubyUnits
|
|
1453
1475
|
# @raise [ArgumentError] when exponent associated with a unit is > 20 or < -20
|
1454
1476
|
def unit_signature_vector
|
1455
1477
|
return to_base.unit_signature_vector unless base?
|
1456
|
-
vector = Array.new(SIGNATURE_VECTOR.size, 0)
|
1478
|
+
vector = ::Array.new(SIGNATURE_VECTOR.size, 0)
|
1457
1479
|
# it's possible to have a kind that misses the array... kinds like :counting
|
1458
1480
|
# are more like prefixes, so don't use them to calculate the vector
|
1459
1481
|
@numerator.map { |element| self.class.definition(element) }.each do |definition|
|
@@ -1465,6 +1487,7 @@ module RubyUnits
|
|
1465
1487
|
vector[index] -= 1 if index
|
1466
1488
|
end
|
1467
1489
|
raise ArgumentError, 'Power out of range (-20 < net power of a unit < 20)' if vector.any? { |x| x.abs >= 20 }
|
1490
|
+
|
1468
1491
|
vector
|
1469
1492
|
end
|
1470
1493
|
|
@@ -1486,8 +1509,9 @@ module RubyUnits
|
|
1486
1509
|
# @return [Array]
|
1487
1510
|
def unit_signature
|
1488
1511
|
return @signature unless @signature.nil?
|
1512
|
+
|
1489
1513
|
vector = unit_signature_vector
|
1490
|
-
vector.each_with_index { |item, index| vector[index] = item * 20**index }
|
1514
|
+
vector.each_with_index { |item, index| vector[index] = item * (20**index) }
|
1491
1515
|
@signature = vector.inject(0) { |acc, elem| acc + elem }
|
1492
1516
|
@signature
|
1493
1517
|
end
|
@@ -1503,7 +1527,7 @@ module RubyUnits
|
|
1503
1527
|
# "GPa" -- creates a unit with scalar 1 with units 'GPa'
|
1504
1528
|
# 6'4" -- recognized as 6 feet + 4 inches
|
1505
1529
|
# 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
|
1506
|
-
# @return [nil
|
1530
|
+
# @return [nil,RubyUnits::Unit]
|
1507
1531
|
# @todo This should either be a separate class or at least a class method
|
1508
1532
|
def parse(passed_unit_string = '0')
|
1509
1533
|
unit_string = passed_unit_string.dup
|
@@ -1529,13 +1553,10 @@ module RubyUnits
|
|
1529
1553
|
end
|
1530
1554
|
|
1531
1555
|
unit_string =~ NUMBER_REGEX
|
1532
|
-
unit =
|
1533
|
-
mult =
|
1534
|
-
(Regexp.last_match(1).empty? ? 1.0 : Regexp.last_match(1).to_f)
|
1535
|
-
rescue
|
1536
|
-
1.0
|
1537
|
-
end
|
1556
|
+
unit = self.class.cached.get(Regexp.last_match(2))
|
1557
|
+
mult = Regexp.last_match(1).nil? ? 1.0 : Regexp.last_match(1).to_f
|
1538
1558
|
mult = mult.to_int if mult.to_int == mult
|
1559
|
+
|
1539
1560
|
if unit
|
1540
1561
|
copy(unit)
|
1541
1562
|
@scalar *= mult
|
@@ -1551,7 +1572,7 @@ module RubyUnits
|
|
1551
1572
|
|
1552
1573
|
if unit_string =~ TIME_REGEX
|
1553
1574
|
hours, minutes, seconds, microseconds = unit_string.scan(TIME_REGEX)[0]
|
1554
|
-
raise ArgumentError,'Invalid Duration' if [hours, minutes, seconds, microseconds].all?(&:nil?)
|
1575
|
+
raise ArgumentError, 'Invalid Duration' if [hours, minutes, seconds, microseconds].all?(&:nil?)
|
1555
1576
|
|
1556
1577
|
result = self.class.new("#{hours || 0} h") +
|
1557
1578
|
self.class.new("#{minutes || 0} minutes") +
|
@@ -1627,7 +1648,7 @@ module RubyUnits
|
|
1627
1648
|
|
1628
1649
|
# eliminate all known terms from this string. This is a quick check to see if the passed unit
|
1629
1650
|
# contains terms that are not defined.
|
1630
|
-
used = "#{top} #{bottom}".to_s.gsub(self.class.unit_match_regex, '').gsub(%r{[\d
|
1651
|
+
used = "#{top} #{bottom}".to_s.gsub(self.class.unit_match_regex, '').gsub(%r{[\d*, "'_^/$]}, '')
|
1631
1652
|
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") unless used.empty?
|
1632
1653
|
|
1633
1654
|
@numerator = @numerator.map do |item|
|