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.
@@ -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
- class Unit < Numeric
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+)\/(\d+)\)?}.freeze
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*([^\/]*)\/*(.+)*}.freeze
64
- TOP_REGEX = /([^ \*]+)(?:\^|\*\*)([\d-]+)/.freeze
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 => :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,
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 => :information,
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 [true]
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 [Block] block
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.instance_of?(String)
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 [Block] _block
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
- # @return [Hash]
234
+ # Unit cache
235
+ #
236
+ # @return [RubyUnits::Cache]
236
237
  def self.cached
237
- @@cached_units
238
+ @cached ||= RubyUnits::Cache.new
238
239
  end
239
240
 
240
- # @return [true]
241
+ # @return [Boolean]
241
242
  def self.clear_cache
242
- @@cached_units = {}
243
- @@base_unit_cache = {}
243
+ cached.clear
244
+ base_unit_cache.clear
244
245
  new(1)
245
246
  true
246
247
  end
247
248
 
248
- # @return [Hash]
249
+ # @return [RubyUnits::Cache]
249
250
  def self.base_unit_cache
250
- @@base_unit_cache
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.delete_if { |_, defn| !defn.base? }.keys.map { |u| new(u) }
305
+ @@base_units ||= @@definitions.dup.select { |_, definition| definition.base? }.keys.map { |u| new(u) }
305
306
  end
306
307
 
307
- # parse a string consisting of a number and a unit string
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 [RegExp]
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 [RegExp]
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] from Unit to copy definition from
420
- # @return [Unit]
421
+ # @param from [RubyUnits::Unit] Unit to copy definition from
422
+ # @return [RubyUnits::Unit]
421
423
  def copy(from)
422
- @scalar = from.scalar
423
- @numerator = from.numerator
424
+ @scalar = from.scalar
425
+ @numerator = from.numerator
424
426
  @denominator = from.denominator
425
427
  @base = from.base?
426
- @signature = from.signature
428
+ @signature = from.signature
427
429
  @base_scalar = from.base_scalar
428
- @unit_name = begin
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
- begin
468
- cached = @@cached_units[options[1]] * options[0]
469
- copy(cached)
470
- rescue
471
- initialize("#{options[0]} #{(begin
472
- options[1].units
473
- rescue
474
- options[1]
475
- end)}")
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
- begin
483
- cached = @@cached_units["#{options[1]}/#{options[2]}"] * options[0]
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
- unless @@cached_units.keys.include?(opt_units) ||
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?(.+)?|&plusmn;|\+\/-})
530
- @@cached_units[opt_units] = (scalar == 1 ? self : opt_units.to_unit) if opt_units && !opt_units.empty?
523
+ (opt_units =~ %r{(#{self.class.temp_regex})|(#{STONE_LB_UNIT_REGEX})|(#{LBS_OZ_UNIT_REGEX})|(#{FEET_INCH_UNITS_REGEX})|%|(#{TIME_REGEX})|i\s?(.+)?|&plusmn;|\+/-})) && (opt_units && !opt_units.empty?)
524
+ self.class.cached.set(opt_units, scalar == 1 ? self : opt_units.to_unit)
531
525
  end
532
526
  end
533
- unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /#{self.class.temp_regex}/)
534
- @@cached_units[unary_unit] = (scalar == 1 ? self : unary_unit.to_unit)
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
- self
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
- # @return [Unit]
548
- def to_unit
549
- self
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
- cached = (begin
585
- (@@base_unit_cache[units] * scalar)
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
- @@base_unit_cache[units] = base
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 /(%[\-+\.\w#]+)\s*(.+)*/ # format string like '%0.2f in'
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? && !(@@unit_map[units] =~ /temp[CFRK]/).nil?
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 [-1|0|1|nil]
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
- return x =~ y
790
+ x =~ y
788
791
  rescue ArgumentError
789
- return false
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
- return x === y
813
+ x === y
811
814
  rescue ArgumentError
812
- return false
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 to a Unit, which can only represent time spans'
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
- # Exponentiate. Only takes integer powers.
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
- return power(other.numerator).root(other.denominator)
987
+ power(other.numerator).root(other.denominator)
979
988
  when Integer
980
- return power(other)
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
- return root(Rational(1, other).to_int)
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 attemptint to take the root of a temperature
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
- return self.class.new("#{q} #{target_unit}")
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 { |defn, _| defn.prefix? }.to_a
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 { |defn, _| defn.prefix? }.to_a
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
- return (begin
1337
- time_point - self
1338
- rescue
1339
- time_point.to_datetime - self
1340
- end)
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).to_unit('s').convert_to(self)
1375
+ self.class.new(::Time.now - time_point, 'second').convert_to(self)
1356
1376
  when DateTime, Date
1357
- (DateTime.now - time_point).to_unit('d').convert_to(self)
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).to_unit('s').convert_to(self)
1389
+ self.class.new(time_point - ::Time.now, 'second').convert_to(self)
1370
1390
  when DateTime, Date
1371
- (time_point - DateTime.now).to_unit('d').convert_to(self)
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
- time_point + self
1386
- rescue
1387
- time_point.to_datetime + self
1388
- end)
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 | Unit]
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 = @@cached_units[Regexp.last_match(2)]
1533
- mult = begin
1534
- (Regexp.last_match(1).empty? ? 1.0 : Regexp.last_match(1).to_f)
1535
- rescue
1536
- 1.0
1537
- end
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|