ruby-units 2.4.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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|
|