Class: RubyUnits::Unit

Inherits:
Numeric show all
Includes:
Comparable
Defined in:
lib/ruby_units/unit.rb,
lib/ruby_units/cache.rb,
lib/ruby_units/version.rb,
lib/ruby_units/definition.rb

Defined Under Namespace

Classes: Cache, Definition

Constant Summary collapse

UNITY =
'<1>'.freeze
UNITY_ARRAY =
[UNITY].freeze
FEET_INCH_UNITS_REGEX =

ideally we would like to generate this regex from the alias for a ‘feet’ and ‘inches’, but they aren’t defined at the point in the code where we need this regex.

/(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inch(?:es)?)/
FEET_INCH_REGEX =
/(\d+)\s*#{FEET_INCH_UNITS_REGEX}/
LBS_OZ_UNIT_REGEX =

ideally we would like to generate this regex from the alias for a ‘pound’ and ‘ounce’, but they aren’t defined at the point in the code where we need this regex.

/(?:#|lbs?|pounds?|pound-mass)+[\s,]*(\d+)\s*(?:ozs?|ounces?)/
LBS_OZ_REGEX =
/(\d+)\s*#{LBS_OZ_UNIT_REGEX}/
STONE_LB_UNIT_REGEX =

ideally we would like to generate this regex from the alias for a ‘stone’ and ‘pound’, but they aren’t defined at the point in the code where we need this regex. also note that the plural of ‘stone’ is still ‘stone’, but we accept ‘stones’ anyway.

/(?:sts?|stones?)+[\s,]*(\d+)\s*(?:#|lbs?|pounds?|pound-mass)*/
STONE_LB_REGEX =
/(\d+)\s*#{STONE_LB_UNIT_REGEX}/
TIME_REGEX =
/(\d+)*:(\d+)*:*(\d+)*[:,]*(\d+)*/
SCI_NUMBER =
/([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)/
RATIONAL_NUMBER =
%r{\(?([+-])?(\d+[ -])?(\d+)\/(\d+)\)?}
COMPLEX_NUMBER =
/#{SCI_NUMBER}?#{SCI_NUMBER}i\b/
NUMBER_REGEX =
/#{SCI_NUMBER}*\s*(.+)?/
UNIT_STRING_REGEX =
%r{#{SCI_NUMBER}*\s*([^\/]*)\/*(.+)*}
TOP_REGEX =
/([^ \*]+)(?:\^|\*\*)([\d-]+)/
BOTTOM_REGEX =
/([^* ]+)(?:\^|\*\*)(\d+)/
NUMBER_UNIT_REGEX =
/#{SCI_NUMBER}?(.*)/
COMPLEX_REGEX =
/#{COMPLEX_NUMBER}\s?(.+)?/
RATIONAL_REGEX =
/#{RATIONAL_NUMBER}\s?(.+)?/
KELVIN =
['<kelvin>'].freeze
FAHRENHEIT =
['<fahrenheit>'].freeze
RANKINE =
['<rankine>'].freeze
CELSIUS =
['<celsius>'].freeze
SIGNATURE_VECTOR =
%i[
  length
  time
  temperature
  mass
  current
  substance
  luminosity
  currency
  information
  angle
].freeze
VERSION =
'2.3.2'.freeze
@@definitions =
{}
@@prefix_values =
{}
@@prefix_map =
{}
@@unit_map =
{}
@@unit_values =
{}
@@unit_regex =
nil
@@unit_match_regex =
nil
@@temp_regex =
nil
@@kinds =
{
  -312_078     => :elastance,
  -312_058     => :resistance,
  -312_038     => :inductance,
  -152_040     => :magnetism,
  -152_038     => :magnetism,
  -152_058     => :potential,
  -7997        => :specific_volume,
  -79          => :snap,
  -59          => :jolt,
  -39          => :acceleration,
  -38          => :radiation,
  -20          => :frequency,
  -19          => :speed,
  -18          => :viscosity,
  -17          => :volumetric_flow,
  -1           => :wavenumber,
  0            => :unitless,
  1            => :length,
  2            => :area,
  3            => :volume,
  20           => :time,
  400          => :temperature,
  7941         => :yank,
  7942         => :power,
  7959         => :pressure,
  7962         => :energy,
  7979         => :viscosity,
  7961         => :force,
  7981         => :momentum,
  7982         => :angular_momentum,
  7997         => :density,
  7998         => :area_density,
  8000         => :mass,
  152_020      => :radiation_exposure,
  159_999      => :magnetism,
  160_000      => :current,
  160_020      => :charge,
  312_058      => :conductance,
  312_078      => :capacitance,
  3_199_980    => :activity,
  3_199_997    => :molar_concentration,
  3_200_000    => :substance,
  63_999_998   => :illuminance,
  64_000_000   => :luminous_power,
  1_280_000_000 => :currency,
  25_600_000_000  => :information,
  511_999_999_980 => :angular_velocity,
  512_000_000_000 => :angle
}.freeze
@@cached_units =
{}
@@base_unit_cache =
{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*options) ⇒ Unit

Create a new Unit object. Can be initialized using a String, a Hash, an Array, Time, DateTime

Examples:

Valid options include:

"5.6 kg*m/s^2"
"5.6 kg*m*s^-2"
"5.6 kilogram*meter*second^-2"
"2.2 kPa"
"37 degC"
"1"  -- creates a unitless constant with value 1
"GPa"  -- creates a unit with scalar 1 with units 'GPa'
"6'4\"""  -- recognized as 6 feet + 4 inches
"8 lbs 8 oz" -- recognized as 8 lbs + 8 ounces
[1, 'kg']
{scalar: 1, numerator: 'kg'}

Parameters:

Raises:

  • (ArgumentError)

    if absolute value of a temperature is less than absolute zero

  • (ArgumentError)

    if no unit is specified

  • (ArgumentError)

    if an invalid unit is specified



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
# File 'lib/ruby_units/unit.rb', line 465

def initialize(*options)
  @scalar      = nil
  @base_scalar = nil
  @unit_name   = nil
  @signature   = nil
  @output      = {}
  raise ArgumentError, 'Invalid Unit Format' if options[0].nil?
  if options.size == 2
    # options[0] is the scalar
    # options[1] is a unit string
    begin
      cached = @@cached_units[options[1]] * options[0]
      copy(cached)
    rescue
      initialize("#{options[0]} #{(begin
                                     options[1].units
                                   rescue
                                     options[1]
                                   end)}")
    end
    return
  end
  if options.size == 3
    options[1] = options[1].join if options[1].is_a?(Array)
    options[2] = options[2].join if options[2].is_a?(Array)
    begin
      cached = @@cached_units["#{options[1]}/#{options[2]}"] * options[0]
      copy(cached)
    rescue
      initialize("#{options[0]} #{options[1]}/#{options[2]}")
    end
    return
  end

  case options[0]
  when Unit
    copy(options[0])
    return
  when Hash
    @scalar      = (options[0][:scalar] || 1)
    @numerator   = options[0][:numerator] || UNITY_ARRAY
    @denominator = options[0][:denominator] || UNITY_ARRAY
    @signature   = options[0][:signature]
  when Array
    initialize(*options[0])
    return
  when Numeric
    @scalar    = options[0]
    @numerator = @denominator = UNITY_ARRAY
  when Time
    @scalar      = options[0].to_f
    @numerator   = ['<second>']
    @denominator = UNITY_ARRAY
  when DateTime, Date
    @scalar      = options[0].ajd
    @numerator   = ['<day>']
    @denominator = UNITY_ARRAY
  when /^\s*$/
    raise ArgumentError, 'No Unit Specified'
  when String
    parse(options[0])
  else
    raise ArgumentError, 'Invalid Unit Format'
  end
  update_base_scalar
  raise ArgumentError, 'Temperatures must not be less than absolute zero' if temperature? && base_scalar < 0
  unary_unit = units || ''
  if options.first.instance_of?(String)
    _opt_scalar, opt_units = RubyUnits::Unit.parse_into_numbers_and_units(options[0])
    unless  @@cached_units.keys.include?(opt_units) ||
            (opt_units =~ %r{\D/[\d+\.]+}) ||
            (opt_units =~ %r{(#{RubyUnits::Unit.temp_regex})|(#{STONE_LB_UNIT_REGEX})|(#{LBS_OZ_UNIT_REGEX})|(#{FEET_INCH_UNITS_REGEX})|%|(#{TIME_REGEX})|i\s?(.+)?|&plusmn;|\+\/-})
      @@cached_units[opt_units] = (scalar == 1 ? self : opt_units.to_unit) if opt_units && !opt_units.empty?
    end
  end
  unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /#{RubyUnits::Unit.temp_regex}/)
    @@cached_units[unary_unit] = (scalar == 1 ? self : unary_unit.to_unit)
  end
  [@scalar, @numerator, @denominator, @base_scalar, @signature, @base].each(&:freeze)
  self
end

Instance Attribute Details

#base_denominatorArray

Returns:



419
420
421
# File 'lib/ruby_units/unit.rb', line 419

def base_denominator
  @base_denominator
end

#base_numeratorArray

Returns:



416
417
418
# File 'lib/ruby_units/unit.rb', line 416

def base_numerator
  @base_numerator
end

#base_scalarNumeric

Returns:



413
414
415
# File 'lib/ruby_units/unit.rb', line 413

def base_scalar
  @base_scalar
end

#denominatorArray

Returns:



407
408
409
# File 'lib/ruby_units/unit.rb', line 407

def denominator
  @denominator
end

#numeratorArray

Returns:



404
405
406
# File 'lib/ruby_units/unit.rb', line 404

def numerator
  @numerator
end

#outputString

Returns:



422
423
424
# File 'lib/ruby_units/unit.rb', line 422

def output
  @output
end

#scalarNumeric

Returns:



401
402
403
# File 'lib/ruby_units/unit.rb', line 401

def scalar
  @scalar
end

#signatureInteger

Returns:

  • (Integer)


410
411
412
# File 'lib/ruby_units/unit.rb', line 410

def signature
  @signature
end

#unit_nameString

Returns:



425
426
427
# File 'lib/ruby_units/unit.rb', line 425

def unit_name
  @unit_name
end

Class Method Details

.base_unit_cacheHash

Returns:

  • (Hash)


233
234
235
# File 'lib/ruby_units/unit.rb', line 233

def self.base_unit_cache
  @@base_unit_cache
end

.base_unitsArray

return an array of base units

Returns:



300
301
302
# File 'lib/ruby_units/unit.rb', line 300

def self.base_units
  @@base_units ||= @@definitions.dup.delete_if { |_, defn| !defn.base? }.keys.map { |u| RubyUnits::Unit.new(u) }
end

.cachedHash

Returns:

  • (Hash)


220
221
222
# File 'lib/ruby_units/unit.rb', line 220

def self.cached
  @@cached_units
end

.clear_cachetrue

Returns:

  • (true)


225
226
227
228
229
230
# File 'lib/ruby_units/unit.rb', line 225

def self.clear_cache
  @@cached_units    = {}
  @@base_unit_cache = {}
  RubyUnits::Unit.new(1)
  true
end

.define(unit_definition, &block) ⇒ RubyUnits::Unit::Definition

Unpack a unit definition and add it to the array of defined units

Examples:

Block form

RubyUnits::Unit.define('foobar') do |foobar|
  foobar.definition = RubyUnits::Unit.new("1 baz")
end

RubyUnits::Unit::Definition form

unit_definition = RubyUnits::Unit::Definition.new("foobar") {|foobar| foobar.definition = RubyUnits::Unit.new("1 baz")}
RubyUnits::Unit.define(unit_definition)

Parameters:

Returns:

Raises:

  • (ArgumentError)

    when passed a non-string if using the block form



185
186
187
188
189
190
191
192
193
# File 'lib/ruby_units/unit.rb', line 185

def self.define(unit_definition, &block)
  if block_given?
    raise ArgumentError, 'When using the block form of RubyUnits::Unit.define, pass the name of the unit' unless unit_definition.instance_of?(String)
    unit_definition = RubyUnits::Unit::Definition.new(unit_definition, &block)
  end
  RubyUnits::Unit.definitions[unit_definition.name] = unit_definition
  RubyUnits::Unit.use_definition(unit_definition)
  unit_definition
end

.defined?(unit) ⇒ Boolean

determine if a unit is already defined

Parameters:

Returns:

  • (Boolean)


153
154
155
# File 'lib/ruby_units/unit.rb', line 153

def self.defined?(unit)
  definitions.values.any? { |d| d.aliases.include?(unit) }
end

.definition(unit_name) ⇒ RubyUnits::Unit::Definition?

return the unit definition for a unit

Parameters:

Returns:



160
161
162
163
# File 'lib/ruby_units/unit.rb', line 160

def self.definition(unit_name)
  unit = unit_name =~ /^<.+>$/ ? unit_name : "<#{unit_name}>"
  @@definitions[unit]
end

.definitionsArray

return a list of all defined units

Returns:



167
168
169
# File 'lib/ruby_units/unit.rb', line 167

def self.definitions
  @@definitions
end

.eliminate_terms(q, n, d) ⇒ Hash

Parameters:

Returns:

  • (Hash)


250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/ruby_units/unit.rb', line 250

def self.eliminate_terms(q, n, d)
  num = n.dup
  den = d.dup

  num.delete_if { |v| v == UNITY }
  den.delete_if { |v| v == UNITY }
  combined = Hash.new(0)

  i = 0
  loop do
    break if i > num.size
    if @@prefix_values.key? num[i]
      k = [num[i], num[i + 1]]
      i += 2
    else
      k = num[i]
      i += 1
    end
    combined[k] += 1 unless k.nil? || k == UNITY
  end

  j = 0
  loop do
    break if j > den.size
    if @@prefix_values.key? den[j]
      k = [den[j], den[j + 1]]
      j += 2
    else
      k = den[j]
      j += 1
    end
    combined[k] -= 1 unless k.nil? || k == UNITY
  end

  num = []
  den = []
  combined.each do |key, value|
    if value >= 0
      value.times { num << key }
    elsif value < 0
      value.abs.times { den << key }
    end
  end
  num = UNITY_ARRAY if num.empty?
  den = UNITY_ARRAY if den.empty?
  { scalar: q, numerator: num.flatten.compact, denominator: den.flatten.compact }
end

.parse(input) ⇒ Unit

Examples:

parse strings

"1 minute in seconds"

Parameters:

Returns:



241
242
243
244
# File 'lib/ruby_units/unit.rb', line 241

def self.parse(input)
  first, second = input.scan(/(.+)\s(?:in|to|as)\s(.+)/i).first
  second.nil? ? RubyUnits::Unit.new(first) : RubyUnits::Unit.new(first).convert_to(second)
end

.parse_into_numbers_and_units(string) ⇒ Array

parse a string consisting of a number and a unit string NOTE: This does not properly handle units formatted like ‘12mg/6ml’

Parameters:

Returns:

  • (Array)

    consisting of [Numeric, “unit”]



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/ruby_units/unit.rb', line 308

def self.parse_into_numbers_and_units(string)
  # scientific notation.... 123.234E22, -123.456e-10
  sci       = /[+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*/
  # rational numbers.... -1/3, 1/5, 20/100, -6 1/2, -6-1/2
  rational  = %r{\(?[+-]?(?:\d+[ -])?\d+\/\d+\)?}
  # complex numbers... -1.2+3i, +1.2-3.3i
  complex   = /#{sci}{2,2}i/
  anynumber = /(?:(#{complex}|#{rational}|#{sci}))?\s?([^-\d\.].*)?/

  num, unit = string.scan(anynumber).first

  [
    case num
    when NilClass
      1
    when complex
      if num.respond_to?(:to_c)
        num.to_c
      else
        #:nocov_19:
        Complex(*num.scan(/(#{sci})(#{sci})i/).flatten.map(&:to_i))
        #:nocov_19:
      end
    when rational
      # if it has whitespace, it will be of the form '6 1/2'
      if num =~ RATIONAL_NUMBER
        sign = Regexp.last_match(1) == '-' ? -1 : 1
        n = Regexp.last_match(2).to_i
        f = Rational(Regexp.last_match(3).to_i, Regexp.last_match(4).to_i)
        sign * (n + f)
      else
        Rational(*num.split('/').map(&:to_i))
      end
    else
      num.to_f
    end,
    unit.to_s.strip
  ]
end

.prefix_regexString

return a regexp fragment used to match prefixes

Returns:



364
365
366
# File 'lib/ruby_units/unit.rb', line 364

def self.prefix_regex
  @@prefix_regex ||= @@prefix_map.keys.sort_by { |prefix| [prefix.length, prefix] }.reverse.join('|')
end

.redefine!(name) {|RubyUnits::Unit::Definition| ... } ⇒ RubyUnits::Unit::Definition

Get the definition for a unit and allow it to be redefined

Parameters:

  • name (String)

    Name of unit to redefine

  • block (Block)

Yields:

Returns:

Raises:

  • (ArgumentError)

    if a block is not given



201
202
203
204
205
206
207
208
209
# File 'lib/ruby_units/unit.rb', line 201

def self.redefine!(name)
  raise ArgumentError, 'A block is required to redefine a unit' unless block_given?
  unit_definition = definition(name)
  raise(ArgumentError, "'#{name}' Unit not recognized") unless unit_definition
  yield unit_definition
  @@definitions.delete("<#{name}>")
  define(unit_definition)
  RubyUnits::Unit.setup
end

.setuptrue

setup internal arrays and hashes

Returns:

  • (true)


132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/ruby_units/unit.rb', line 132

def self.setup
  clear_cache
  @@prefix_values    = {}
  @@prefix_map       = {}
  @@unit_values      = {}
  @@unit_map         = {}
  @@unit_regex       = nil
  @@unit_match_regex = nil
  @@prefix_regex     = nil

  @@definitions.each do |_name, definition|
    use_definition(definition)
  end

  RubyUnits::Unit.new(1)
  true
end

.temp_regexObject



368
369
370
371
372
373
374
375
376
377
378
# File 'lib/ruby_units/unit.rb', line 368

def self.temp_regex
  @@temp_regex ||= begin
    temp_units = %w[tempK tempC tempF tempR degK degC degF degR]
    aliases = temp_units.map do |unit|
      d = RubyUnits::Unit.definition(unit)
      d && d.aliases
    end.flatten.compact
    regex_str = aliases.empty? ? '(?!x)x' : aliases.join('|')
    Regexp.new "(?:#{regex_str})"
  end
end

.undefine!(unit) ⇒ true

Undefine a unit. Will not raise an exception for unknown units.

Parameters:

  • name (String)

    of unit to undefine

Returns:

  • (true)


214
215
216
217
# File 'lib/ruby_units/unit.rb', line 214

def self.undefine!(unit)
  @@definitions.delete("<#{unit}>")
  RubyUnits::Unit.setup
end

.unit_match_regexRegExp

return a regex used to match units

Returns:

  • (RegExp)


357
358
359
# File 'lib/ruby_units/unit.rb', line 357

def self.unit_match_regex
  @@unit_match_regex ||= /(#{RubyUnits::Unit.prefix_regex})??(#{RubyUnits::Unit.unit_regex})\b/
end

.unit_regexString

return a fragment of a regex to be used for matching units or reconstruct it if hasn’t been used yet. Unit names are reverse sorted by length so the regexp matcher will prefer longer and more specific names

Returns:



351
352
353
# File 'lib/ruby_units/unit.rb', line 351

def self.unit_regex
  @@unit_regex ||= @@unit_map.keys.sort_by { |unit_name| [unit_name.length, unit_name] }.reverse.join('|')
end

.use_definition(definition) ⇒ Object

inject a definition into the internal array and set it up for use



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/ruby_units/unit.rb', line 381

def self.use_definition(definition)
  @@unit_match_regex = nil # invalidate the unit match regex
  @@temp_regex       = nil # invalidate the temp regex
  if definition.prefix?
    @@prefix_values[definition.name] = definition.scalar
    definition.aliases.each { |alias_name| @@prefix_map[alias_name] = definition.name }
    @@prefix_regex = nil # invalidate the prefix regex
  else
    @@unit_values[definition.name]          = {}
    @@unit_values[definition.name][:scalar] = definition.scalar
    @@unit_values[definition.name][:numerator] = definition.numerator if definition.numerator
    @@unit_values[definition.name][:denominator] = definition.denominator if definition.denominator
    definition.aliases.each { |alias_name| @@unit_map[alias_name] = definition.name }
    @@unit_regex = nil # invalidate the unit regex
  end
end

Instance Method Details

#%(other) ⇒ Integer

perform a modulo on a unit, will raise an exception if the units are not compatible

Parameters:

  • other (Object)

Returns:

  • (Integer)


958
959
960
# File 'lib/ruby_units/unit.rb', line 958

def %(other)
  divmod(other).last
end

#*(other) ⇒ Unit

Multiply two units.

Parameters:

Returns:

Raises:

  • (ArgumentError)

    when attempting to multiply two temperatures



902
903
904
905
906
907
908
909
910
911
912
913
914
915
# File 'lib/ruby_units/unit.rb', line 902

def *(other)
  case other
  when Unit
    raise ArgumentError, 'Cannot multiply by temperatures' if [other, self].any?(&:temperature?)
    opts = RubyUnits::Unit.eliminate_terms(@scalar * other.scalar, @numerator + other.numerator, @denominator + other.denominator)
    opts[:signature] = @signature + other.signature
    RubyUnits::Unit.new(opts)
  when Numeric
    RubyUnits::Unit.new(scalar: @scalar * other, numerator: @numerator, denominator: @denominator, signature: @signature)
  else
    x, y = coerce(other)
    x * y
  end
end

#**(other) ⇒ Unit

Exponentiate. Only takes integer powers. Note that anything raised to the power of 0 results in a Unit object with a scalar of 1, and no units. Throws an exception if exponent is not an integer. Ideally this routine should accept a float for the exponent It should then convert the float to a rational and raise the unit by the numerator and root it by the denominator but, sadly, floats can’t be converted to rationals.

For now, if a rational is passed in, it will be used, otherwise we are stuck with integers and certain floats < 1

Parameters:

Returns:

Raises:

  • (ArgumentError)

    when raising a temperature to a power

  • (ArgumentError)

    when n not in the set integers from (1..9)

  • (ArgumentError)

    when attempting to raise to a complex number

  • (ArgumentError)

    when an invalid exponent is passed



976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
# File 'lib/ruby_units/unit.rb', line 976

def **(other)
  raise ArgumentError, 'Cannot raise a temperature to a power' if temperature?
  if other.is_a?(Numeric)
    return inverse if other == -1
    return self if other == 1
    return 1 if other.zero?
  end
  case other
  when Rational
    return power(other.numerator).root(other.denominator)
  when Integer
    return power(other)
  when Float
    return self**other.to_i if other == other.to_i
    valid = (1..9).map { |n| Rational(1, n) }
    raise ArgumentError, 'Not a n-th root (1..9), use 1/n' unless valid.include? other.abs
    return root(Rational(1, other).to_int)
  when Complex
    raise ArgumentError, 'exponentiation of complex numbers is not supported.'
  else
    raise ArgumentError, 'Invalid Exponent'
  end
end

#+(other) ⇒ Unit

Add two units together. Result is same units as receiver and scalar and base_scalar are updated appropriately throws an exception if the units are not compatible. It is possible to add Time objects to units of time

Parameters:

  • other (Object)

Returns:

Raises:

  • (ArgumentError)

    when two temperatures are added

  • (ArgumentError)

    when units are not compatible

  • (ArgumentError)

    when adding a fixed time or date to a time span



835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
# File 'lib/ruby_units/unit.rb', line 835

def +(other)
  case other
  when Unit
    if zero?
      other.dup
    elsif self =~ other
      raise ArgumentError, 'Cannot add two temperatures' if [self, other].all?(&:temperature?)
      if [self, other].any?(&:temperature?)
        if temperature?
          RubyUnits::Unit.new(scalar: (scalar + other.convert_to(temperature_scale).scalar), numerator: @numerator, denominator: @denominator, signature: @signature)
        else
          RubyUnits::Unit.new(scalar: (other.scalar + convert_to(other.temperature_scale).scalar), numerator: other.numerator, denominator: other.denominator, signature: other.signature)
        end
      else
        RubyUnits::Unit.new(scalar: (base_scalar + other.base_scalar), numerator: base.numerator, denominator: base.denominator, signature: @signature).convert_to(self)
      end
    else
      raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
    end
  when Date, Time
    raise ArgumentError, 'Date and Time objects represent fixed points in time and cannot be added to a Unit'
  else
    x, y = coerce(other)
    y + x
  end
end

#-(other) ⇒ Unit

Subtract two units. Result is same units as receiver and scalar and base_scalar are updated appropriately

Parameters:

Returns:

Raises:

  • (ArgumentError)

    when subtracting a temperature from a degree

  • (ArgumentError)

    when units are not compatible

  • (ArgumentError)

    when subtracting a fixed time from a time span



868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
# File 'lib/ruby_units/unit.rb', line 868

def -(other)
  case other
  when Unit
    if zero?
      if other.zero?
        other.dup * -1 # preserve Units class
      else
        -other.dup
      end
    elsif self =~ other
      if [self, other].all?(&:temperature?)
        RubyUnits::Unit.new(scalar: (base_scalar - other.base_scalar), numerator: KELVIN, denominator: UNITY_ARRAY, signature: @signature).convert_to(temperature_scale)
      elsif temperature?
        RubyUnits::Unit.new(scalar: (base_scalar - other.base_scalar), numerator: ['<tempK>'], denominator: UNITY_ARRAY, signature: @signature).convert_to(self)
      elsif other.temperature?
        raise ArgumentError, 'Cannot subtract a temperature from a differential degree unit'
      else
        RubyUnits::Unit.new(scalar: (base_scalar - other.base_scalar), numerator: base.numerator, denominator: base.denominator, signature: @signature).convert_to(self)
      end
    else
      raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
    end
  when Time
    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'
  else
    x, y = coerce(other)
    y - x
  end
end

#-@Numeric, Unit

negates the scalar of the Unit

Returns:



1234
1235
1236
1237
# File 'lib/ruby_units/unit.rb', line 1234

def -@
  return -@scalar if unitless?
  dup * -1
end

#/(other) ⇒ Unit

Divide two units. Throws an exception if divisor is 0

Parameters:

Returns:

Raises:

  • (ZeroDivisionError)

    if divisor is zero

  • (ArgumentError)

    if attempting to divide a temperature by another temperature



923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
# File 'lib/ruby_units/unit.rb', line 923

def /(other)
  case other
  when Unit
    raise ZeroDivisionError if other.zero?
    raise ArgumentError, 'Cannot divide with temperatures' if [other, self].any?(&:temperature?)
    sc = Rational(@scalar, other.scalar)
    sc = sc.numerator if sc.denominator == 1
    opts = RubyUnits::Unit.eliminate_terms(sc, @numerator + other.denominator, @denominator + other.numerator)
    opts[:signature] = @signature - other.signature
    RubyUnits::Unit.new(opts)
  when Numeric
    raise ZeroDivisionError if other.zero?
    sc = Rational(@scalar, other)
    sc = sc.numerator if sc.denominator == 1
    RubyUnits::Unit.new(scalar: sc, numerator: @numerator, denominator: @denominator, signature: @signature)
  else
    x, y = coerce(other)
    y / x
  end
end

#<=>(other) ⇒ -1|0|1|nil

Compare two Unit objects. Throws an exception if they are not of compatible types. Comparisons are done based on the value of the unit in base SI units.

Parameters:

  • other (Object)

Returns:

  • (-1|0|1|nil)

Raises:

  • (NoMethodError)

    when other does not define <=>

  • (ArgumentError)

    when units are not compatible



739
740
741
742
743
744
745
746
747
748
749
750
751
752
# File 'lib/ruby_units/unit.rb', line 739

def <=>(other)
  raise NoMethodError, "undefined method `<=>' for #{base_scalar.inspect}" unless base_scalar.respond_to?(:<=>)
  if other.nil?
    base_scalar <=> nil
  elsif !temperature? && other.respond_to?(:zero?) && other.zero?
    base_scalar <=> 0
  elsif other.instance_of?(Unit)
    raise ArgumentError, "Incompatible Units ('#{units}' not compatible with '#{other.units}')" unless self =~ other
    base_scalar <=> other.base_scalar
  else
    x, y = coerce(other)
    y <=> x
  end
end

#==(other) ⇒ Boolean

Compare Units for equality this is necessary mostly for Complex units. Complex units do not have a <=> operator so we define this one here so that we can properly check complex units for equality. Units of incompatible types are not equal, except when they are both zero and neither is a temperature Equality checks can be tricky since round off errors may make essentially equivalent units appear to be different.

Parameters:

  • other (Object)

Returns:

  • (Boolean)


762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
# File 'lib/ruby_units/unit.rb', line 762

def ==(other)
  if other.respond_to?(:zero?) && other.zero?
    zero?
  elsif other.instance_of?(Unit)
    return false unless self =~ other
    base_scalar == other.base_scalar
  else
    begin
      x, y = coerce(other)
      x == y
    rescue ArgumentError # return false when object cannot be coerced
      false
    end
  end
end

#===(other) ⇒ Boolean Also known as: same?, same_as?

Compare two units. Returns true if quantities and units match

Examples:

RubyUnits::Unit.new("100 cm") === RubyUnits::Unit.new("100 cm")   # => true
RubyUnits::Unit.new("100 cm") === RubyUnits::Unit.new("1 m")      # => false

Parameters:

  • other (Object)

Returns:

  • (Boolean)


810
811
812
813
814
815
816
817
818
819
820
821
822
# File 'lib/ruby_units/unit.rb', line 810

def ===(other)
  case other
  when Unit
    (scalar == other.scalar) && (units == other.units)
  else
    begin
      x, y = coerce(other)
      return x === y
    rescue ArgumentError
      return false
    end
  end
end

#=~(other) ⇒ Boolean Also known as: compatible?, compatible_with?

Note:

if you want to do a regexp comparison of the unit string do this … unit.units =~ /regexp/

check to see if units are compatible, but not the scalar part this check is done by comparing signatures for performance reasons if passed a string, it will create a unit object with the string and then do the comparison

Examples:

this permits a syntax like:

unit =~ "mm"

Parameters:

  • other (Object)

Returns:

  • (Boolean)


787
788
789
790
791
792
793
794
795
796
797
798
799
# File 'lib/ruby_units/unit.rb', line 787

def =~(other)
  case other
  when Unit
    signature == other.signature
  else
    begin
      x, y = coerce(other)
      return x =~ y
    rescue ArgumentError
      return false
    end
  end
end

#absNumeric, Unit

absolute value of a unit

Returns:



1241
1242
1243
1244
# File 'lib/ruby_units/unit.rb', line 1241

def abs
  return @scalar.abs if unitless?
  RubyUnits::Unit.new(@scalar.abs, @numerator, @denominator)
end

#agoUnit

Examples:

‘5 min’.to_unit.ago

Returns:



1319
1320
1321
# File 'lib/ruby_units/unit.rb', line 1319

def ago
  before
end

#as_jsonString

Returns string formatted for json

Returns:



1168
1169
1170
# File 'lib/ruby_units/unit.rb', line 1168

def as_json(*)
  to_s
end

#base?Boolean Also known as: is_base?

Is this unit in base form?

Returns:

  • (Boolean)


563
564
565
566
567
568
569
570
571
# File 'lib/ruby_units/unit.rb', line 563

def base?
  return @base if defined? @base
  @base = (@numerator + @denominator)
          .compact
          .uniq
          .map { |unit| RubyUnits::Unit.definition(unit) }
          .all? { |element| element.unity? || element.base? }
  @base
end

#before(time_point = ::Time.now) ⇒ Unit Also known as: before_now

Examples:

‘5 min’.before(time)

Returns:



1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
# File 'lib/ruby_units/unit.rb', line 1325

def before(time_point = ::Time.now)
  case time_point
  when Time, Date, DateTime
    return (begin
              time_point - self
            rescue
              time_point.to_datetime - self
            end)
  else
    raise ArgumentError, 'Must specify a Time, Date, or DateTime'
  end
end

#best_prefixObject

returns a new unit that has been scaled to be more in line with typical usage.



1404
1405
1406
1407
1408
1409
1410
1411
1412
# File 'lib/ruby_units/unit.rb', line 1404

def best_prefix
  return to_base if scalar.zero?
  best_prefix = if kind == :information
                  @@prefix_values.key(2**((Math.log(base_scalar, 2) / 10.0).floor * 10))
                else
                  @@prefix_values.key(10**((Math.log10(base_scalar) / 3.0).floor * 3))
                end
  to(RubyUnits::Unit.new(@@prefix_map.key(best_prefix) + units(with_prefix: false)))
end

#ceilNumeric, Unit

ceil of a unit

Returns:



1248
1249
1250
1251
# File 'lib/ruby_units/unit.rb', line 1248

def ceil
  return @scalar.ceil if unitless?
  RubyUnits::Unit.new(@scalar.ceil, @numerator, @denominator)
end

#coerce(other) ⇒ Array

automatically coerce objects to units when possible if an object defines a ‘to_unit’ method, it will be coerced using that method

Parameters:

Returns:



1393
1394
1395
1396
1397
1398
1399
1400
1401
# File 'lib/ruby_units/unit.rb', line 1393

def coerce(other)
  return [other.to_unit, self] if other.respond_to? :to_unit
  case other
  when Unit
    [other, self]
  else
    [RubyUnits::Unit.new(other), self]
  end
end

#convert_to(other) ⇒ Unit Also known as: >>, to

Note:

If temperature is part of a compound unit, the temperature will be treated as a differential and the units will be scaled appropriately.

convert to a specified unit string or to the same units as another Unit

unit.convert_to "kg"   will covert to kilograms
unit1.convert_to unit2 converts to same units as unit2 object

To convert a Unit object to match another Unit object, use:

unit1 >>= unit2

Special handling for temperature conversions is supported. If the Unit object is converted from one temperature unit to another, the proper temperature offsets will be used. Supports Kelvin, Celsius, Fahrenheit, and Rankine scales.

Parameters:

  • other (Object)

Returns:

Raises:

  • (ArgumentError)

    when attempting to convert a degree to a temperature

  • (ArgumentError)

    when target unit is unknown

  • (ArgumentError)

    when target unit is incompatible



1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
# File 'lib/ruby_units/unit.rb', line 1074

def convert_to(other)
  return self if other.nil?
  return self if TrueClass === other
  return self if FalseClass === other
  if (Unit === other && other.temperature?) || (String === other && other =~ /temp[CFRK]/)
    raise ArgumentError, 'Receiver is not a temperature unit' unless degree?
    start_unit = units
    target_unit = begin
                    other.units
                  rescue
                    other
                  end
    @base_scalar ||= case @@unit_map[start_unit]
                     when '<tempC>'
                       @scalar + 273.15
                     when '<tempK>'
                       @scalar
                     when '<tempF>'
                       (@scalar + 459.67).to_r * Rational(5, 9)
                     when '<tempR>'
                       @scalar.to_r * Rational(5, 9)
                     end
    q = case @@unit_map[target_unit]
        when '<tempC>'
          @base_scalar - 273.15r
        when '<tempK>'
          @base_scalar
        when '<tempF>'
          @base_scalar.to_r * Rational(9, 5) - 459.67r
        when '<tempR>'
          @base_scalar.to_r * Rational(9, 5)
        end
    return RubyUnits::Unit.new("#{q} #{target_unit}")
  else
    case other
    when Unit
      return self if other.units == units
      target = other
    when String
      target = RubyUnits::Unit.new(other)
    else
      raise ArgumentError, 'Unknown target units'
    end
    raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ target
    numerator1   = @numerator.map { |x| @@prefix_values[x] ? @@prefix_values[x] : x }.map { |i| i.is_a?(Numeric) ? i : @@unit_values[i][:scalar] }.compact
    denominator1 = @denominator.map { |x| @@prefix_values[x] ? @@prefix_values[x] : x }.map { |i| i.is_a?(Numeric) ? i : @@unit_values[i][:scalar] }.compact
    numerator2   = target.numerator.map { |x| @@prefix_values[x] ? @@prefix_values[x] : x }.map { |x| x.is_a?(Numeric) ? x : @@unit_values[x][:scalar] }.compact
    denominator2 = target.denominator.map { |x| @@prefix_values[x] ? @@prefix_values[x] : x }.map { |x| x.is_a?(Numeric) ? x : @@unit_values[x][:scalar] }.compact

    q = @scalar * ((numerator1 + denominator2).inject(1) { |acc, elem| acc * elem }) /
        ((numerator2 + denominator1).inject(1) { |acc, elem| acc * elem })
    return RubyUnits::Unit.new(scalar: q, numerator: target.numerator, denominator: target.denominator, signature: target.signature)
  end
end

#copy(from) ⇒ Unit

Used to copy one unit to another

Parameters:

  • from (Unit)

    Unit to copy definition from

Returns:



430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/ruby_units/unit.rb', line 430

def copy(from)
  @scalar      = from.scalar
  @numerator   = from.numerator
  @denominator = from.denominator
  @base = from.base?
  @signature   = from.signature
  @base_scalar = from.base_scalar
  @unit_name = begin
                 from.unit_name
               rescue
                 nil
               end
  self
end

#degree?Boolean Also known as: is_degree?

true if a degree unit or equivalent.

Returns:

  • (Boolean)


712
713
714
# File 'lib/ruby_units/unit.rb', line 712

def degree?
  kind == :temperature
end

#divmod(other) ⇒ Array

divide two units and return quotient and remainder when both units are in the same units we just use divmod on the raw scalars otherwise we use the scalar of the base unit which will be a float

Parameters:

  • other (Object)

Returns:

Raises:

  • (ArgumentError)


949
950
951
952
953
# File 'lib/ruby_units/unit.rb', line 949

def divmod(other)
  raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ other
  return scalar.divmod(other.scalar) if units == other.units
  to_base.scalar.divmod(other.to_base.scalar)
end

#floorNumeric, Unit

Returns:



1254
1255
1256
1257
# File 'lib/ruby_units/unit.rb', line 1254

def floor
  return @scalar.floor if unitless?
  RubyUnits::Unit.new(@scalar.floor, @numerator, @denominator)
end

#from(time_point) ⇒ Time, ... Also known as: after, from_now

Examples:

‘5 min’.from(time)

Parameters:

Returns:

Raises:

  • (ArgumentError)

    when passed argument is not a Time, Date, or DateTime



1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
# File 'lib/ruby_units/unit.rb', line 1373

def from(time_point)
  case time_point
  when Time, DateTime, Date
    (begin
       time_point + self
     rescue
       time_point.to_datetime + self
     end)
  else
    raise ArgumentError, 'Must specify a Time, Date, or DateTime'
  end
end

#hashObject

override hash method so objects with same values are considered equal



1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
# File 'lib/ruby_units/unit.rb', line 1415

def hash
  [
    @scalar,
    @numerator,
    @denominator,
    @base,
    @signature,
    @base_scalar,
    @unit_name
  ].hash
end

#inspect(dump = nil) ⇒ String

Deprecated.

Normally pretty prints the unit, but if you really want to see the guts of it, pass ‘:dump’

Returns:



696
697
698
699
# File 'lib/ruby_units/unit.rb', line 696

def inspect(dump = nil)
  return super() if dump
  to_s
end

#inverseUnit

returns inverse of Unit (1/unit)

Returns:



1051
1052
1053
# File 'lib/ruby_units/unit.rb', line 1051

def inverse
  RubyUnits::Unit.new('1') / self
end

#kindSymbol

@todo: figure out how to handle :counting units. This method should probably return :counting instead of :unitless for ‘each’ return the kind of the unit (:mass, :length, etc…)

Returns:

  • (Symbol)


550
551
552
# File 'lib/ruby_units/unit.rb', line 550

def kind
  @@kinds[signature]
end

#power(n) ⇒ Unit

returns the unit raised to the n-th power

Parameters:

  • n (Integer)

Returns:

Raises:

  • (ArgumentError)

    when attempting to raise a temperature to a power

  • (ArgumentError)

    when n is not an integer



1005
1006
1007
1008
1009
1010
1011
1012
1013
# File 'lib/ruby_units/unit.rb', line 1005

def power(n)
  raise ArgumentError, 'Cannot raise a temperature to a power' if temperature?
  raise ArgumentError, 'Exponent must an Integer' unless n.is_a?(Integer)
  return inverse if n == -1
  return 1 if n.zero?
  return self if n == 1
  return (1..(n - 1).to_i).inject(self) { |acc, _elem| acc * self } if n >= 0
  (1..-(n - 1).to_i).inject(self) { |acc, _elem| acc / self }
end

#predUnit

returns previous unit in a range. ‘2 mm’.to_unit.pred #=> ‘1 mm’.to_unit only works when the scalar is an integer

Returns:

Raises:

  • (ArgumentError)

    when scalar is not equal to an integer



1286
1287
1288
1289
# File 'lib/ruby_units/unit.rb', line 1286

def pred
  raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
  RubyUnits::Unit.new(@scalar.to_i.pred, @numerator, @denominator)
end

#root(n) ⇒ Unit

Calculates the n-th root of a unit if n < 0, returns 1/unit^(1/n)

Parameters:

  • n (Integer)

Returns:

Raises:

  • (ArgumentError)

    when attemptint to take the root of a temperature

  • (ArgumentError)

    when n is not an integer

  • (ArgumentError)

    when n is 0



1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
# File 'lib/ruby_units/unit.rb', line 1022

def root(n)
  raise ArgumentError, 'Cannot take the root of a temperature' if temperature?
  raise ArgumentError, 'Exponent must an Integer' unless n.is_a?(Integer)
  raise ArgumentError, '0th root undefined' if n.zero?
  return self if n == 1
  return root(n.abs).inverse if n < 0

  vec = unit_signature_vector
  vec = vec.map { |x| x % n }
  raise ArgumentError, 'Illegal root' unless vec.max.zero?
  num = @numerator.dup
  den = @denominator.dup

  @numerator.uniq.each do |item|
    x = num.find_all { |i| i == item }.size
    r = ((x / n) * (n - 1)).to_int
    r.times { num.delete_at(num.index(item)) }
  end

  @denominator.uniq.each do |item|
    x = den.find_all { |i| i == item }.size
    r = ((x / n) * (n - 1)).to_int
    r.times { den.delete_at(den.index(item)) }
  end
  RubyUnits::Unit.new(scalar: @scalar**Rational(1, n), numerator: num, denominator: den)
end

#round(ndigits = 0) ⇒ Numeric, Unit

Returns:



1260
1261
1262
1263
# File 'lib/ruby_units/unit.rb', line 1260

def round(ndigits = 0)
  return @scalar.round(ndigits) if unitless?
  RubyUnits::Unit.new(@scalar.round(ndigits), @numerator, @denominator)
end

#since(time_point) ⇒ Unit

Examples:

‘min’.since(time)

Parameters:

Returns:

Raises:

  • (ArgumentError)

    when time point is not a Time, Date, or DateTime



1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
# File 'lib/ruby_units/unit.rb', line 1344

def since(time_point)
  case time_point
  when Time
    (Time.now - time_point).to_unit('s').convert_to(self)
  when DateTime, Date
    (DateTime.now - time_point).to_unit('d').convert_to(self)
  else
    raise ArgumentError, 'Must specify a Time, Date, or DateTime'
  end
end

#succUnit Also known as: next

returns next unit in a range. ‘1 mm’.to_unit.succ #=> ‘2 mm’.to_unit only works when the scalar is an integer

Returns:

Raises:

  • (ArgumentError)

    when scalar is not equal to an integer



1275
1276
1277
1278
# File 'lib/ruby_units/unit.rb', line 1275

def succ
  raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
  RubyUnits::Unit.new(@scalar.to_i.succ, @numerator, @denominator)
end

#temperature?Boolean Also known as: is_temperature?

TODO:

use unit definition to determine if it’s a temperature instead of a regex

true if unit is a ‘temperature’, false if a ‘degree’ or anything else

Returns:

  • (Boolean)


704
705
706
# File 'lib/ruby_units/unit.rb', line 704

def temperature?
  degree? && !(@@unit_map[units] =~ /temp[CFRK]/).nil?
end

#temperature_scaleString

returns the ‘degree’ unit associated with a temperature unit

Examples:

‘100 tempC’.to_unit.temperature_scale #=> ‘degC’

Returns:

  • (String)

    possible values: degC, degF, degR, or degK



721
722
723
724
# File 'lib/ruby_units/unit.rb', line 721

def temperature_scale
  return nil unless temperature?
  "deg#{@@unit_map[units][/temp([CFRK])/, 1]}"
end

#to_baseUnit Also known as: base

TODO:

this is brittle as it depends on the display_name of a unit, which can be changed

convert to base SI units results of the conversion are cached so subsequent calls to this will be fast

Returns:



579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
# File 'lib/ruby_units/unit.rb', line 579

def to_base
  return self if base?
  if @@unit_map[units] =~ /\A<(?:temp|deg)[CRF]>\Z/
    @signature = @@kinds.key(:temperature)
    base = if temperature?
             convert_to('tempK')
           elsif degree?
             convert_to('degK')
           end
    return base
  end

  cached = (begin
              (@@base_unit_cache[units] * scalar)
            rescue
              nil
            end)
  return cached if cached

  num = []
  den = []
  q   = Rational(1)
  @numerator.compact.each do |num_unit|
    if @@prefix_values[num_unit]
      q *= @@prefix_values[num_unit]
    else
      q *= @@unit_values[num_unit][:scalar] if @@unit_values[num_unit]
      num << @@unit_values[num_unit][:numerator] if @@unit_values[num_unit] && @@unit_values[num_unit][:numerator]
      den << @@unit_values[num_unit][:denominator] if @@unit_values[num_unit] && @@unit_values[num_unit][:denominator]
    end
  end
  @denominator.compact.each do |num_unit|
    if @@prefix_values[num_unit]
      q /= @@prefix_values[num_unit]
    else
      q /= @@unit_values[num_unit][:scalar] if @@unit_values[num_unit]
      den << @@unit_values[num_unit][:numerator] if @@unit_values[num_unit] && @@unit_values[num_unit][:numerator]
      num << @@unit_values[num_unit][:denominator] if @@unit_values[num_unit] && @@unit_values[num_unit][:denominator]
    end
  end

  num = num.flatten.compact
  den = den.flatten.compact
  num = UNITY_ARRAY if num.empty?
  base = RubyUnits::Unit.new(RubyUnits::Unit.eliminate_terms(q, num, den))
  @@base_unit_cache[units] = base
  base * @scalar
end

#to_cComplex

converts the unit back to a complex if it is unitless. Otherwise raises an exception

Returns:

  • (Complex)

Raises:

  • (RuntimeError)

    when not unitless



1143
1144
1145
1146
# File 'lib/ruby_units/unit.rb', line 1143

def to_c
  return Complex(@scalar) if unitless?
  raise "Cannot convert '#{self}' to Complex unless unitless.  Use Unit#scalar"
end

#to_dateDate

Returns:



1307
1308
1309
# File 'lib/ruby_units/unit.rb', line 1307

def to_date
  Date.new0(convert_to('d').scalar)
end

#to_datetimeDateTime

convert a duration to a DateTime. This will work so long as the duration is the duration from the zero date defined by DateTime

Returns:

  • (DateTime)


1302
1303
1304
# File 'lib/ruby_units/unit.rb', line 1302

def to_datetime
  DateTime.new!(convert_to('d').scalar)
end

#to_fFloat

converts the unit back to a float if it is unitless. Otherwise raises an exception

Returns:

  • (Float)

Raises:

  • (RuntimeError)

    when not unitless



1135
1136
1137
1138
# File 'lib/ruby_units/unit.rb', line 1135

def to_f
  return @scalar.to_f if unitless?
  raise "Cannot convert '#{self}' to Float unless unitless.  Use Unit#scalar"
end

#to_iInteger Also known as: to_int

if unitless, returns an int, otherwise raises an error

Returns:

  • (Integer)

Raises:

  • (RuntimeError)

    when not unitless



1151
1152
1153
1154
# File 'lib/ruby_units/unit.rb', line 1151

def to_i
  return @scalar.to_int if unitless?
  raise "Cannot convert '#{self}' to Integer unless unitless.  Use Unit#scalar"
end

#to_rRational

if unitless, returns a Rational, otherwise raises an error

Returns:

  • (Rational)

Raises:

  • (RuntimeError)

    when not unitless



1161
1162
1163
1164
# File 'lib/ruby_units/unit.rb', line 1161

def to_r
  return @scalar.to_r if unitless?
  raise "Cannot convert '#{self}' to Rational unless unitless.  Use Unit#scalar"
end

#to_s(target_units = nil) ⇒ String

Note:

Rational scalars that are equal to an integer will be represented as integers (i.e, 6/1 => 6, 4/2 => 2, etc..)

Generate human readable output. If the name of a unit is passed, the unit will first be converted to the target unit before output. some named conversions are available

You can also pass a standard format string (i.e., ‘%0.2f’) or a strftime format string.

output is cached so subsequent calls for the same format will be fast

Examples:

unit.to_s(:ft) - outputs in feet and inches (e.g., 6'4")
unit.to_s(:lbs) - outputs in pounds and ounces (e.g, 8 lbs, 8 oz)

Parameters:

  • target_units (Symbol) (defaults to: nil)

Returns:



646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
# File 'lib/ruby_units/unit.rb', line 646

def to_s(target_units = nil)
  out = @output[target_units]
  return out if out
  separator = RubyUnits.configuration.separator
  case target_units
  when :ft
    inches = convert_to('in').scalar.to_int
    out    = "#{(inches / 12).truncate}\'#{(inches % 12).round}\""
  when :lbs
    ounces = convert_to('oz').scalar.to_int
    out    = "#{(ounces / 16).truncate}#{separator}lbs, #{(ounces % 16).round}#{separator}oz"
  when :stone
    pounds = convert_to('lbs').scalar.to_int
    out = "#{(pounds / 14).truncate}#{separator}stone, #{(pounds % 14).round}#{separator}lb"
  when String
    out = case target_units.strip
          when /\A\s*\Z/ # whitespace only
            ''
          when /(%[\-+\.\w#]+)\s*(.+)*/ # format string like '%0.2f in'
            begin
              if Regexp.last_match(2) # unit specified, need to convert
                convert_to(Regexp.last_match(2)).to_s(Regexp.last_match(1))
              else
                "#{Regexp.last_match(1) % @scalar}#{separator}#{Regexp.last_match(2) || units}".strip
              end
            rescue # parse it like a strftime format string
              (DateTime.new(0) + self).strftime(target_units)
            end
          when /(\S+)/ # unit only 'mm' or '1/mm'
            convert_to(Regexp.last_match(1)).to_s
          else
            raise 'unhandled case'
          end
  else
    out = case @scalar
          when Complex
            "#{@scalar}#{separator}#{units}"
          when Rational
            "#{@scalar == @scalar.to_i ? @scalar.to_i : @scalar}#{separator}#{units}"
          else
            "#{'%g' % @scalar}#{separator}#{units}"
          end.strip
  end
  @output[target_units] = out
  out
end

#to_timeTime Also known as: time

Tries to make a Time object from current unit. Assumes the current unit hold the duration in seconds from the epoch.

Returns:



1293
1294
1295
# File 'lib/ruby_units/unit.rb', line 1293

def to_time
  Time.at(self)
end

#to_unitUnit Also known as: unit

Returns:



555
556
557
# File 'lib/ruby_units/unit.rb', line 555

def to_unit
  self
end

#truncateNumeric, Unit

Returns:



1266
1267
1268
1269
# File 'lib/ruby_units/unit.rb', line 1266

def truncate
  return @scalar.truncate if unitless?
  RubyUnits::Unit.new(@scalar.truncate, @numerator, @denominator)
end

#unitless?Boolean

returns true if no associated units false, even if the units are “unitless” like ‘radians, each, etc’

Returns:

  • (Boolean)


729
730
731
# File 'lib/ruby_units/unit.rb', line 729

def unitless?
  (@numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY)
end

#units(with_prefix: true) ⇒ String

returns the ‘unit’ part of the Unit object without the scalar

Returns:



1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
# File 'lib/ruby_units/unit.rb', line 1174

def units(with_prefix: true)
  return '' if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY
  output_numerator   = ['1']
  output_denominator = []
  num                = @numerator.clone.compact
  den                = @denominator.clone.compact

  unless num == UNITY_ARRAY
    definitions = num.map { |element| RubyUnits::Unit.definition(element) }
    definitions.reject!(&:prefix?) unless with_prefix
    # there is a bug in jruby 9.1.6.0's implementation of chunk_while
    # see https://github.com/jruby/jruby/issues/4410
    # TODO: fix this after jruby fixes their bug.
    definitions = if definitions.respond_to?(:chunk_while) && RUBY_ENGINE != 'jruby'
                    definitions.chunk_while { |defn, _| defn.prefix? }.to_a
                  else # chunk_while is new to ruby 2.3+, so fallback to less efficient methods for older ruby
                    result = []
                    enumerator = definitions.to_enum
                    loop do
                      first = enumerator.next
                      result << (first.prefix? ? [first, enumerator.next] : [first])
                    end
                    result
                  end
    output_numerator = definitions.map { |element| element.map(&:display_name).join }
  end

  unless den == UNITY_ARRAY
    definitions = den.map { |element| RubyUnits::Unit.definition(element) }
    definitions.reject!(&:prefix?) unless with_prefix
    # there is a bug in jruby 9.1.6.0's implementation of chunk_while
    # see https://github.com/jruby/jruby/issues/4410
    # TODO: fix this after jruby fixes their bug.
    definitions = if definitions.respond_to?(:chunk_while) && RUBY_ENGINE != 'jruby'
                    definitions.chunk_while { |defn, _| defn.prefix? }.to_a
                  else # chunk_while is new to ruby 2.3+, so fallback to less efficient methods for older ruby
                    result = []
                    enumerator = definitions.to_enum
                    loop do
                      first = enumerator.next
                      result << (first.prefix? ? [first, enumerator.next] : [first])
                    end
                    result
                  end
    output_denominator = definitions.map { |element| element.map(&:display_name).join }
  end

  on  = output_numerator
        .uniq
        .map { |x| [x, output_numerator.count(x)] }
        .map { |element, power| (element.to_s.strip + (power > 1 ? "^#{power}" : '')) }
  od  = output_denominator
        .uniq
        .map { |x| [x, output_denominator.count(x)] }
        .map { |element, power| (element.to_s.strip + (power > 1 ? "^#{power}" : '')) }
  "#{on.join('*')}#{od.empty? ? '' : '/' + od.join('*')}".strip
end

#until(time_point) ⇒ Unit

Examples:

‘min’.until(time)

Parameters:

Returns:



1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
# File 'lib/ruby_units/unit.rb', line 1358

def until(time_point)
  case time_point
  when Time
    (time_point - Time.now).to_unit('s').convert_to(self)
  when DateTime, Date
    (time_point - DateTime.now).to_unit('d').convert_to(self)
  else
    raise ArgumentError, 'Must specify a Time, Date, or DateTime'
  end
end

#zero?Boolean

true if scalar is zero

Returns:

  • (Boolean)


1313
1314
1315
# File 'lib/ruby_units/unit.rb', line 1313

def zero?
  base_scalar.zero?
end