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.
@@ -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|