Class: RubyUnits::Unit
- Includes:
- Comparable
- Defined in:
- lib/ruby_units/unit.rb,
lib/ruby_units/version.rb,
lib/ruby_units/definition.rb
Overview
The accuracy of unit conversions depends on the precision of the conversion factor. If you have more accurate estimates for particular conversion factors, please send them to me and I will incorporate them into the next release. It is also incumbent on the end-user to ensure that the accuracy of any conversions is sufficient for their intended application.
Copyright 2006-2024 While there are a large number of unit specified in the base package, there are also a large number of units that are not included. This package covers nearly all SI, Imperial, and units commonly used in the United States. If your favorite units are not listed here, file an issue on GitHub.
To add or override a unit definition, add a code block like this..
Defined Under Namespace
Classes: Definition
Constant Summary collapse
- UNITY =
"<1>"
- UNITY_ARRAY =
[UNITY].freeze
- SIGN_REGEX =
+, -, or nothing
/(?:[+-])?/.freeze
- INTEGER_DIGITS_REGEX =
regex for matching an integer number but not a fraction
%r{(?<!/)\d+(?!/)}.freeze
- INTEGER_REGEX =
-1, 1, +1, but not 1/2
/(#{SIGN_REGEX}#{INTEGER_DIGITS_REGEX})/.freeze
- UNSIGNED_INTEGER_REGEX =
1, 2, 3, but not -1
/((?<!-)#{INTEGER_DIGITS_REGEX})/.freeze
- DIGITS_REGEX =
0, 1, 2, 3
/\d+/.freeze
- DECIMAL_REGEX =
1, 0.1, .1
/\d*[.]?#{DIGITS_REGEX}/.freeze
- RATIONAL_NUMBER =
Rational number, including improper fractions: 1 2/3, -1 2/3, 5/3, etc.
%r{\(?(?:(?<proper>#{SIGN_REGEX}#{DECIMAL_REGEX})[ -])?(?<numerator>#{SIGN_REGEX}#{DECIMAL_REGEX})/(?<denominator>#{SIGN_REGEX}#{DECIMAL_REGEX})\)?}.freeze
- SCI_NUMBER =
Scientific notation: 1, -1, 1, 1.2, 1.2, -1.2, 123.4E5, +123.4e5,
-123.4E+5, -123.4e-5, etc.
/([+-]?\d*[.]?\d+(?:[Ee][+-]?\d+(?![.]))?)/.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*(?<inches>#{RATIONAL_NUMBER}|#{SCI_NUMBER})\s*(?:"|in|inch(?:es)?)/.freeze
- FEET_INCH_REGEX =
/(?<feet>#{INTEGER_REGEX})\s*#{FEET_INCH_UNITS_REGEX}/.freeze
- 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,]*(?<oz>#{RATIONAL_NUMBER}|#{UNSIGNED_INTEGER_REGEX})\s*(?:ozs?|ounces?)/.freeze
- LBS_OZ_REGEX =
/(?<pounds>#{INTEGER_REGEX})\s*#{LBS_OZ_UNIT_REGEX}/.freeze
- 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,]*(?<pounds>#{RATIONAL_NUMBER}|#{UNSIGNED_INTEGER_REGEX})\s*(?:#|lbs?|pounds?|pound-mass)*/.freeze
- STONE_LB_REGEX =
/(?<stone>#{INTEGER_REGEX})\s*#{STONE_LB_UNIT_REGEX}/.freeze
- TIME_REGEX =
Time formats: 12:34:56,78, (hh:mm:ss,msec) etc.
/(?<hour>\d+):(?<min>\d+):?(?:(?<sec>\d+))?(?:[.](?<msec>\d+))?/.freeze
- COMPLEX_NUMBER =
Complex numbers: 1+2i, 1.0+2.0i, -1-1i, etc.
/(?<real>#{SCI_NUMBER})?(?<imaginary>#{SCI_NUMBER})i\b/.freeze
- ANY_NUMBER =
Any Complex, Rational, or scientific number
/(#{COMPLEX_NUMBER}|#{RATIONAL_NUMBER}|#{SCI_NUMBER})/.freeze
- ANY_NUMBER_REGEX =
/(?:#{ANY_NUMBER})?\s?([^-\d.].*)?/.freeze
- NUMBER_REGEX =
a number followed by a unit
/(?<scalar>#{SCI_NUMBER}*)\s*(?<unit>.+)?/.freeze
- UNIT_STRING_REGEX =
%r{#{SCI_NUMBER}*\s*([^/]*)/*(.+)*}.freeze
- TOP_REGEX =
/([^ *]+)(?:\^|\*\*)([\d-]+)/.freeze
- BOTTOM_REGEX =
/([^* ]+)(?:\^|\*\*)(\d+)/.freeze
- NUMBER_UNIT_REGEX =
/#{SCI_NUMBER}?(.*)/.freeze
- COMPLEX_REGEX =
/#{COMPLEX_NUMBER}\s?(?<unit>.+)?/.freeze
- RATIONAL_REGEX =
/#{RATIONAL_NUMBER}\s?(?<unit>.+)?/.freeze
- 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 =
"4.1.0"
Class Attribute Summary collapse
-
.definitions ⇒ Hash{Symbol=>RubyUnits::Units::Definition}
return a list of all defined units.
- .kinds ⇒ Hash{Integer => Symbol} readonly
- .prefix_map ⇒ Hash{Symbol => String}
-
.prefix_values ⇒ Hash{Symbol => String}
The list of units and their prefixes.
- .unit_map ⇒ Hash{Symbol => String}
- .unit_values ⇒ Hash{Symbol => String}
Instance Attribute Summary collapse
- #base_denominator ⇒ Array
- #base_numerator ⇒ Array
- #base_scalar ⇒ Numeric
- #denominator ⇒ Array
- #numerator ⇒ Array
- #output ⇒ String
- #scalar ⇒ Numeric
- #signature ⇒ Integer
- #unit_name ⇒ String
Class Method Summary collapse
- .base_unit_cache ⇒ RubyUnits::Cache
-
.base_units ⇒ Array
return an array of base units.
-
.cached ⇒ RubyUnits::Cache
Unit cache.
- .clear_cache ⇒ Boolean
-
.define(unit_definition, &block) ⇒ RubyUnits::Unit::Definition
Unpack a unit definition and add it to the array of defined units.
-
.defined?(unit) ⇒ Boolean
determine if a unit is already defined.
-
.definition(unit_name) ⇒ RubyUnits::Unit::Definition?
return the unit definition for a unit.
- .eliminate_terms(q, n, d) ⇒ Hash
-
.inherited(subclass) ⇒ Object
Callback triggered when a subclass is created.
- .parse(input) ⇒ Unit
-
.parse_into_numbers_and_units(string) ⇒ Array(Numeric, String)
Parse a string consisting of a number and a unit string NOTE: This does not properly handle units formatted like ‘12mg/6ml’.
-
.prefix_regex ⇒ String
return a regexp fragment used to match prefixes.
-
.redefine!(name, &_block) {|the| ... } ⇒ RubyUnits::Unit::Definition
Get the definition for a unit and allow it to be redefined.
-
.setup ⇒ Boolean
setup internal arrays and hashes.
-
.temp_regex ⇒ Regexp
Generates (and memoizes) a regexp matching any of the temperature units or their aliases.
-
.undefine!(unit) ⇒ Boolean
Undefine a unit.
-
.unit_match_regex ⇒ Regexp
return a regex used to match units.
-
.unit_regex ⇒ String
return a fragment of a regex to be used for matching units or reconstruct it if hasn’t been used yet.
-
.use_definition(definition) ⇒ Object
inject a definition into the internal array and set it up for use.
Instance Method Summary collapse
-
#%(other) ⇒ Integer
(also: #modulo)
Perform a modulo on a unit, will raise an exception if the units are not compatible.
-
#*(other) ⇒ Unit
Multiply two units.
-
#**(other) ⇒ Unit
Exponentiation.
-
#+(other) ⇒ Unit
Add two units together.
-
#-(other) ⇒ Unit
Subtract two units.
-
#-@ ⇒ Numeric, Unit
negates the scalar of the Unit.
-
#/(other) ⇒ Unit
Divide two units.
-
#<=>(other) ⇒ Integer?
Compare two Unit objects.
-
#==(other) ⇒ Boolean
Compare Units for equality this is necessary mostly for Complex units.
-
#===(other) ⇒ Boolean
(also: #same?, #same_as?)
Compare two units.
-
#=~(other) ⇒ Boolean
(also: #compatible?, #compatible_with?)
Check to see if units are compatible, ignoring the scalar part.
-
#abs ⇒ Numeric, Unit
absolute value of a unit.
- #ago ⇒ Unit
-
#as_json ⇒ String
Returns string formatted for json.
-
#base? ⇒ Boolean
(also: #is_base?)
Is this unit in base form?.
- #before(time_point = ::Time.now) ⇒ Unit (also: #before_now)
-
#best_prefix ⇒ Unit
Returns a new unit that has been scaled to be more in line with typical usage.
-
#ceil(*args) ⇒ Numeric, Unit
ceil of a unit.
-
#coerce(other) ⇒ Array(Unit, Unit)
Automatically coerce objects to [Unit] when possible.
-
#convert_to(other) ⇒ Unit
(also: #>>, #to)
convert to a specified unit string or to the same units as another Unit.
-
#copy(from) ⇒ RubyUnits::Unit
Used to copy one unit to another.
-
#degree? ⇒ Boolean
(also: #is_degree?)
true if a degree unit or equivalent.
-
#divmod(other) ⇒ Array(Integer, Unit)
Divide two units and return quotient and remainder.
-
#eliminate_terms ⇒ RubyUnits::Unit
Creates a new unit from the current one with all common terms eliminated.
- #floor(*args) ⇒ Numeric, Unit
- #from(time_point) ⇒ Time, ... (also: #after, #from_now)
-
#hash ⇒ Object
override hash method so objects with same values are considered equal.
-
#initialize(*options) ⇒ Unit
constructor
Create a new Unit object.
- #inspect(dump = nil) ⇒ String deprecated Deprecated.
-
#inverse ⇒ Unit
returns inverse of Unit (1/unit).
-
#kind ⇒ Symbol
@todo: figure out how to handle :counting units.
-
#power(n) ⇒ Unit
returns the unit raised to the n-th power.
-
#pred ⇒ Unit
returns previous unit in a range.
- #quo(other) ⇒ Unit (also: #fdiv)
-
#remainder(other) ⇒ Unit
Returns the remainder when one unit is divided by another.
-
#root(n) ⇒ Unit
Calculates the n-th root of a unit if n < 0, returns 1/unit^(1/n).
-
#round(*args, **kwargs) ⇒ Numeric, Unit
Round the unit according to the rules of the scalar’s class.
- #since(time_point) ⇒ Unit
-
#succ ⇒ Unit
(also: #next)
returns next unit in a range.
-
#temperature? ⇒ Boolean
(also: #is_temperature?)
true if unit is a ‘temperature’, false if a ‘degree’ or anything else.
-
#temperature_scale ⇒ String
returns the ‘degree’ unit associated with a temperature unit.
-
#to_base ⇒ Unit
(also: #base)
convert to base SI units results of the conversion are cached so subsequent calls to this will be fast.
-
#to_c ⇒ Complex
converts the unit back to a complex if it is unitless.
- #to_date ⇒ Date
-
#to_datetime ⇒ ::DateTime
convert a duration to a DateTime.
-
#to_f ⇒ Float
converts the unit back to a float if it is unitless.
-
#to_i ⇒ Integer
(also: #to_int)
if unitless, returns an int, otherwise raises an error.
-
#to_r ⇒ Rational
if unitless, returns a Rational, otherwise raises an error.
-
#to_s(target_units = nil, precision: 0.0001, format: RubyUnits.configuration.format) ⇒ String
Generate human readable output.
-
#to_time ⇒ Time
(also: #time)
Tries to make a Time object from current unit.
-
#to_unit(other = nil) ⇒ RubyUnits::Unit
(also: #unit)
Convert the unit to a Unit, possibly performing a conversion.
- #truncate(*args) ⇒ Numeric, Unit
-
#unitless? ⇒ Boolean
returns true if no associated units false, even if the units are “unitless” like ‘radians, each, etc’.
-
#units(with_prefix: true, format: nil) ⇒ String
Returns the ‘unit’ part of the Unit object without the scalar.
- #until(time_point) ⇒ Unit
-
#zero? ⇒ Boolean
true if scalar is zero.
Constructor Details
#initialize(*options) ⇒ Unit
Create a new Unit object. Can be initialized using a String, a Hash, an Array, Time, DateTime
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 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 |
# File 'lib/ruby_units/unit.rb', line 490 def initialize(*) @scalar = nil @base_scalar = nil @unit_name = nil @signature = nil @output = {} raise ArgumentError, "Invalid Unit Format" if [0].nil? if .size == 2 # options[0] is the scalar # options[1] is a unit string cached = self.class.cached.get([1]) if cached.nil? initialize("#{[0]} #{[1]}") else copy(cached * [0]) end return end if .size == 3 [1] = [1].join if [1].is_a?(Array) [2] = [2].join if [2].is_a?(Array) cached = self.class.cached.get("#{[1]}/#{[2]}") if cached.nil? initialize("#{[0]} #{[1]}/#{[2]}") else copy(cached) * [0] end return end case [0] when Unit copy([0]) return when Hash @scalar = [0][:scalar] || 1 @numerator = [0][:numerator] || UNITY_ARRAY @denominator = [0][:denominator] || UNITY_ARRAY @signature = [0][:signature] when Array initialize(*[0]) return when Numeric @scalar = [0] @numerator = @denominator = UNITY_ARRAY when Time @scalar = [0].to_f @numerator = ["<second>"] @denominator = UNITY_ARRAY when DateTime, Date @scalar = [0].ajd @numerator = ["<day>"] @denominator = UNITY_ARRAY when /^\s*$/ raise ArgumentError, "No Unit Specified" when String parse([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.negative? unary_unit = units || "" if .first.instance_of?(String) _opt_scalar, opt_units = self.class.parse_into_numbers_and_units([0]) if !(self.class.cached.keys.include?(opt_units) || (opt_units =~ %r{\D/[\d+.]+}) || (opt_units =~ %r{(#{self.class.temp_regex})|(#{STONE_LB_UNIT_REGEX})|(#{LBS_OZ_UNIT_REGEX})|(#{FEET_INCH_UNITS_REGEX})|%|(#{TIME_REGEX})|i\s?(.+)?|±|\+/-})) && (opt_units && !opt_units.empty?) self.class.cached.set(opt_units, scalar == 1 ? self : opt_units.to_unit) end end unless self.class.cached.keys.include?(unary_unit) || (unary_unit =~ self.class.temp_regex) self.class.cached.set(unary_unit, scalar == 1 ? self : unary_unit.to_unit) end [@scalar, @numerator, @denominator, @base_scalar, @signature, @base].each(&:freeze) super() end |
Class Attribute Details
.definitions ⇒ Hash{Symbol=>RubyUnits::Units::Definition}
return a list of all defined units
30 31 32 |
# File 'lib/ruby_units/unit.rb', line 30 def definitions @definitions end |
.kinds ⇒ Hash{Integer => Symbol} (readonly)
45 46 47 |
# File 'lib/ruby_units/unit.rb', line 45 def kinds @kinds end |
.prefix_map ⇒ Hash{Symbol => String}
36 37 38 |
# File 'lib/ruby_units/unit.rb', line 36 def prefix_map @prefix_map end |
.prefix_values ⇒ Hash{Symbol => String}
Returns the list of units and their prefixes.
33 34 35 |
# File 'lib/ruby_units/unit.rb', line 33 def prefix_values @prefix_values end |
.unit_map ⇒ Hash{Symbol => String}
39 40 41 |
# File 'lib/ruby_units/unit.rb', line 39 def unit_map @unit_map end |
.unit_values ⇒ Hash{Symbol => String}
42 43 44 |
# File 'lib/ruby_units/unit.rb', line 42 def unit_values @unit_values end |
Instance Attribute Details
#base_denominator ⇒ Array
448 449 450 |
# File 'lib/ruby_units/unit.rb', line 448 def base_denominator @base_denominator end |
#base_numerator ⇒ Array
445 446 447 |
# File 'lib/ruby_units/unit.rb', line 445 def base_numerator @base_numerator end |
#base_scalar ⇒ Numeric
442 443 444 |
# File 'lib/ruby_units/unit.rb', line 442 def base_scalar @base_scalar end |
#denominator ⇒ Array
436 437 438 |
# File 'lib/ruby_units/unit.rb', line 436 def denominator @denominator end |
#numerator ⇒ Array
433 434 435 |
# File 'lib/ruby_units/unit.rb', line 433 def numerator @numerator end |
#signature ⇒ Integer
439 440 441 |
# File 'lib/ruby_units/unit.rb', line 439 def signature @signature end |
#unit_name ⇒ String
454 455 456 |
# File 'lib/ruby_units/unit.rb', line 454 def unit_name @unit_name end |
Class Method Details
.base_unit_cache ⇒ RubyUnits::Cache
286 287 288 |
# File 'lib/ruby_units/unit.rb', line 286 def self.base_unit_cache @base_unit_cache ||= RubyUnits::Cache.new end |
.base_units ⇒ Array
return an array of base units
340 341 342 |
# File 'lib/ruby_units/unit.rb', line 340 def self.base_units @base_units ||= definitions.dup.select { |_, definition| definition.base? }.keys.map { new(_1) } end |
.cached ⇒ RubyUnits::Cache
Unit cache
273 274 275 |
# File 'lib/ruby_units/unit.rb', line 273 def self.cached @cached ||= RubyUnits::Cache.new end |
.clear_cache ⇒ Boolean
278 279 280 281 282 283 |
# File 'lib/ruby_units/unit.rb', line 278 def self.clear_cache cached.clear base_unit_cache.clear new(1) true end |
.define(unit_definition, &block) ⇒ RubyUnits::Unit::Definition
Unpack a unit definition and add it to the array of defined units
230 231 232 233 234 235 236 237 238 239 |
# File 'lib/ruby_units/unit.rb', line 230 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.is_a?(String) unit_definition = RubyUnits::Unit::Definition.new(unit_definition, &block) end definitions[unit_definition.name] = unit_definition use_definition(unit_definition) unit_definition end |
.defined?(unit) ⇒ Boolean
determine if a unit is already defined
204 205 206 |
# File 'lib/ruby_units/unit.rb', line 204 def self.defined?(unit) definitions.values.any? { _1.aliases.include?(unit) } end |
.definition(unit_name) ⇒ RubyUnits::Unit::Definition?
return the unit definition for a unit
211 212 213 214 |
# File 'lib/ruby_units/unit.rb', line 211 def self.definition(unit_name) unit = unit_name =~ /^<.+>$/ ? unit_name : "<#{unit_name}>" definitions[unit] end |
.eliminate_terms(q, n, d) ⇒ Hash
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
# File 'lib/ruby_units/unit.rb', line 303 def self.eliminate_terms(q, n, d) num = n.dup den = d.dup num.delete(UNITY) den.delete(UNITY) combined = ::Hash.new(0) [[num, 1], [den, -1]].each do |array, increment| array.chunk_while { |elt_before, _| definition(elt_before).prefix? } .to_a .each { combined[_1] += increment } end num = [] den = [] combined.each do |key, value| if value.positive? value.times { num << key } elsif value.negative? value.abs.times { den << key } end end num = UNITY_ARRAY if num.empty? den = UNITY_ARRAY if den.empty? { scalar: q, numerator: num.flatten, denominator: den.flatten } end |
.inherited(subclass) ⇒ Object
Callback triggered when a subclass is created. This properly sets up the internal variables, and copies definitions from the parent class.
174 175 176 177 178 179 |
# File 'lib/ruby_units/unit.rb', line 174 def self.inherited(subclass) super subclass.definitions = definitions.dup subclass.instance_variable_set(:@kinds, @kinds.dup) subclass.setup end |
.parse(input) ⇒ Unit
294 295 296 297 |
# File 'lib/ruby_units/unit.rb', line 294 def self.parse(input) first, second = input.scan(/(.+)\s(?:in|to|as)\s(.+)/i).first second.nil? ? new(first) : new(first).convert_to(second) end |
.parse_into_numbers_and_units(string) ⇒ Array(Numeric, String)
Parse a string consisting of a number and a unit string NOTE: This does not properly handle units formatted like ‘12mg/6ml’
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/ruby_units/unit.rb', line 349 def self.parse_into_numbers_and_units(string) num, unit = string.scan(ANY_NUMBER_REGEX).first [ case num when nil # This happens when no number is passed and we are parsing a pure unit string 1 when COMPLEX_NUMBER num.to_c when RATIONAL_NUMBER # We use this method instead of relying on `to_r` because it does not # handle improper fractions correctly. 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 num.to_f end, unit.to_s.strip ] end |
.prefix_regex ⇒ String
return a regexp fragment used to match prefixes
388 389 390 |
# File 'lib/ruby_units/unit.rb', line 388 def self.prefix_regex @prefix_regex ||= prefix_map.keys.sort_by { [_1.length, _1] }.reverse.join("|") end |
.redefine!(name, &_block) {|the| ... } ⇒ RubyUnits::Unit::Definition
Get the definition for a unit and allow it to be redefined
249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/ruby_units/unit.rb', line 249 def self.redefine!(name, &_block) 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) setup end |
.setup ⇒ Boolean
setup internal arrays and hashes
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/ruby_units/unit.rb', line 183 def self.setup clear_cache self.prefix_values = {} self.prefix_map = {} self.unit_map = {} self.unit_values = {} @unit_regex = nil @unit_match_regex = nil @prefix_regex = nil definitions.each_value do |definition| use_definition(definition) end new(1) true end |
.temp_regex ⇒ Regexp
Generates (and memoizes) a regexp matching any of the temperature units or their aliases.
395 396 397 398 399 400 401 402 403 404 405 |
# File 'lib/ruby_units/unit.rb', line 395 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 = definition(unit) d && d.aliases end.flatten.compact regex_str = aliases.empty? ? "(?!x)x" : aliases.join("|") Regexp.new "(?:#{regex_str})" end end |
.undefine!(unit) ⇒ Boolean
Undefine a unit. Will not raise an exception for unknown units.
265 266 267 268 |
# File 'lib/ruby_units/unit.rb', line 265 def self.undefine!(unit) definitions.delete("<#{unit}>") setup end |
.unit_match_regex ⇒ Regexp
return a regex used to match units
381 382 383 |
# File 'lib/ruby_units/unit.rb', line 381 def self.unit_match_regex @unit_match_regex ||= /(#{prefix_regex})??(#{unit_regex})\b/ end |
.unit_regex ⇒ String
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
375 376 377 |
# File 'lib/ruby_units/unit.rb', line 375 def self.unit_regex @unit_regex ||= unit_map.keys.sort_by { [_1.length, _1] }.reverse.join("|") end |
.use_definition(definition) ⇒ Object
inject a definition into the internal array and set it up for use
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'lib/ruby_units/unit.rb', line 410 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 { prefix_map[_1] = 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 { unit_map[_1] = definition.name } @unit_regex = nil # invalidate the unit regex end end |
Instance Method Details
#%(other) ⇒ Integer Also known as: modulo
Perform a modulo on a unit, will raise an exception if the units are not compatible
1010 1011 1012 1013 1014 |
# File 'lib/ruby_units/unit.rb', line 1010 def %(other) raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless compatible_with?(other) self.class.new(base_scalar % other.to_unit.base_scalar, to_base.units).convert_to(self) end |
#*(other) ⇒ Unit
Multiply two units.
938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 |
# File 'lib/ruby_units/unit.rb', line 938 def *(other) case other when Unit raise ArgumentError, "Cannot multiply by temperatures" if [other, self].any?(&:temperature?) opts = self.class.eliminate_terms(@scalar * other.scalar, @numerator + other.numerator, @denominator + other.denominator) opts[:signature] = @signature + other.signature self.class.new(opts) when Numeric self.class.new(scalar: @scalar * other, numerator: @numerator, denominator: @denominator, signature: @signature) else x, y = coerce(other) x * y end end |
#**(other) ⇒ Unit
Exponentiation. 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
1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 |
# File 'lib/ruby_units/unit.rb', line 1039 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 power(other.numerator).root(other.denominator) when Integer power(other) when Float return self**other.to_i if other == other.to_i valid = (1..9).map { Rational(1, _1) } raise ArgumentError, "Not a n-th root (1..9), use 1/n" unless valid.include? other.abs 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
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 872 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 temperature? self.class.new(scalar: (scalar + other.convert_to(temperature_scale).scalar), numerator: @numerator, denominator: @denominator, signature: @signature) elsif other.temperature? self.class.new(scalar: (other.scalar + convert_to(other.temperature_scale).scalar), numerator: other.numerator, denominator: other.denominator, signature: other.signature) else self.class.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
904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 |
# File 'lib/ruby_units/unit.rb', line 904 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?) self.class.new(scalar: (base_scalar - other.base_scalar), numerator: KELVIN, denominator: UNITY_ARRAY, signature: @signature).convert_to(temperature_scale) elsif temperature? self.class.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 self.class.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 a Unit" else x, y = coerce(other) y - x end end |
#-@ ⇒ Numeric, Unit
negates the scalar of the Unit
1318 1319 1320 1321 1322 |
# File 'lib/ruby_units/unit.rb', line 1318 def -@ return -@scalar if unitless? dup * -1 end |
#/(other) ⇒ Unit
Divide two units. Throws an exception if divisor is 0
960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 |
# File 'lib/ruby_units/unit.rb', line 960 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 = self.class.eliminate_terms(sc, @numerator + other.denominator, @denominator + other.numerator) opts[:signature] = @signature - other.signature self.class.new(opts) when Numeric raise ZeroDivisionError if other.zero? sc = Rational(@scalar, other) sc = sc.numerator if sc.denominator == 1 self.class.new(scalar: sc, numerator: @numerator, denominator: @denominator, signature: @signature) else x, y = coerce(other) y / x end end |
#<=>(other) ⇒ Integer?
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.
777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 |
# File 'lib/ruby_units/unit.rb', line 777 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.
802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 |
# File 'lib/ruby_units/unit.rb', line 802 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
847 848 849 850 851 852 853 854 855 856 857 858 859 |
# File 'lib/ruby_units/unit.rb', line 847 def ===(other) case other when Unit (scalar == other.scalar) && (units == other.units) else begin x, y = coerce(other) x.same_as?(y) rescue ArgumentError false end end end |
#=~(other) ⇒ Boolean Also known as: compatible?, compatible_with?
if you want to do a regexp comparison of the unit string do this … unit.units =~ /regexp/
Check to see if units are compatible, ignoring the scalar part. This check is done by comparing unit signatures for performance reasons. If passed a string, this will create a [Unit] object with the string and then do the comparison.
829 830 831 832 833 834 835 836 |
# File 'lib/ruby_units/unit.rb', line 829 def =~(other) return signature == other.signature if other.is_a?(Unit) x, y = coerce(other) x =~ y rescue ArgumentError # return false when `other` cannot be converted to a [Unit] false end |
#abs ⇒ Numeric, Unit
absolute value of a unit
1326 1327 1328 1329 1330 |
# File 'lib/ruby_units/unit.rb', line 1326 def abs return @scalar.abs if unitless? self.class.new(@scalar.abs, @numerator, @denominator) end |
#as_json ⇒ String
Returns string formatted for json
1264 1265 1266 |
# File 'lib/ruby_units/unit.rb', line 1264 def as_json(*) to_s end |
#base? ⇒ Boolean Also known as: is_base?
Is this unit in base form?
591 592 593 594 595 596 597 598 599 600 |
# File 'lib/ruby_units/unit.rb', line 591 def base? return @base if defined? @base @base = (@numerator + @denominator) .compact .uniq .map { self.class.definition(_1) } .all? { _1.unity? || _1.base? } @base end |
#before(time_point = ::Time.now) ⇒ Unit Also known as: before_now
1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 |
# File 'lib/ruby_units/unit.rb', line 1426 def before(time_point = ::Time.now) case time_point when Time, Date, DateTime (begin time_point - self rescue StandardError time_point.to_datetime - self end) else raise ArgumentError, "Must specify a Time, Date, or DateTime" end end |
#best_prefix ⇒ Unit
Returns a new unit that has been scaled to be more in line with typical usage. This is highly opinionated and not based on any standard. It is intended to be used to make the units more human readable.
Some key points:
-
Units containing ‘kg’ will be returned as is. The prefix in ‘kg’ makes this an odd case.
-
It will use ‘centi` instead of `milli` when the scalar is between 0.01 and 0.001
1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 |
# File 'lib/ruby_units/unit.rb', line 1510 def best_prefix return to_base if scalar.zero? return self if units.include?("kg") best_prefix = if kind == :information self.class.prefix_values.key(2**((::Math.log(base_scalar, 2) / 10.0).floor * 10)) elsif ((1/100r)..(1/10r)).cover?(base_scalar) self.class.prefix_values.key(1/100r) else self.class.prefix_values.key(10**((::Math.log10(base_scalar) / 3.0).floor * 3)) end to(self.class.new(self.class.prefix_map.key(best_prefix) + units(with_prefix: false))) end |
#ceil(*args) ⇒ Numeric, Unit
ceil of a unit
1334 1335 1336 1337 1338 |
# File 'lib/ruby_units/unit.rb', line 1334 def ceil(*args) return @scalar.ceil(*args) if unitless? self.class.new(@scalar.ceil(*args), @numerator, @denominator) end |
#coerce(other) ⇒ Array(Unit, Unit)
Automatically coerce objects to [Unit] when possible. If an object defines a ‘#to_unit’ method, it will be coerced using that method.
1496 1497 1498 1499 1500 |
# File 'lib/ruby_units/unit.rb', line 1496 def coerce(other) return [other.to_unit, self] if other.respond_to?(:to_unit) [self.class.new(other), self] end |
#convert_to(other) ⇒ Unit Also known as: >>, to
If temperature is part of a compound unit, the temperature will be treated as a differential and the units will be scaled appropriately.
When converting units with Integer scalars, the scalar will be converted to a Rational to avoid unexpected behavior caused by Integer division.
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.
1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 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 |
# File 'lib/ruby_units/unit.rb', line 1146 def convert_to(other) return self if other.nil? return self if other.is_a?(TrueClass) return self if other.is_a?(FalseClass) if (other.is_a?(Unit) && other.temperature?) || (other.is_a?(String) && other =~ self.class.temp_regex) raise ArgumentError, "Receiver is not a temperature unit" unless degree? start_unit = units # @type [String] target_unit = case other when Unit other.units when String other else raise ArgumentError, "Unknown target units" end return self if target_unit == start_unit # @type [Numeric] @base_scalar ||= case self.class.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 # @type [Numeric] q = case self.class.unit_map[target_unit] when "<tempC>" @base_scalar - 273.15 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 self.class.new("#{q} #{target_unit}") else # @type [Unit] target = case other when Unit other when String self.class.new(other) else raise ArgumentError, "Unknown target units" end return self if target.units == units raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ target numerator1 = @numerator.map { self.class.prefix_values[_1] || _1 }.map { _1.is_a?(Numeric) ? _1 : self.class.unit_values[_1][:scalar] }.compact denominator1 = @denominator.map { self.class.prefix_values[_1] || _1 }.map { _1.is_a?(Numeric) ? _1 : self.class.unit_values[_1][:scalar] }.compact numerator2 = target.numerator.map { self.class.prefix_values[_1] || _1 }.map { _1.is_a?(Numeric) ? _1 : self.class.unit_values[_1][:scalar] }.compact denominator2 = target.denominator.map { self.class.prefix_values[_1] || _1 }.map { _1.is_a?(Numeric) ? _1 : self.class.unit_values[_1][:scalar] }.compact # If the scalar is an Integer, convert it to a Rational number so that # if the value is scaled during conversion, resolution is not lost due # to integer math # @type [Rational, Numeric] conversion_scalar = @scalar.is_a?(Integer) ? @scalar.to_r : @scalar q = conversion_scalar * (numerator1 + denominator2).reduce(1, :*) / (numerator2 + denominator1).reduce(1, :*) # Convert the scalar to an Integer if the result is equivalent to an # integer q = q.to_i if @scalar.is_a?(Integer) && q.to_i == q self.class.new(scalar: q, numerator: target.numerator, denominator: target.denominator, signature: target.signature) end end |
#copy(from) ⇒ RubyUnits::Unit
Used to copy one unit to another
459 460 461 462 463 464 465 466 467 468 |
# File 'lib/ruby_units/unit.rb', line 459 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 = from.unit_name self end |
#degree? ⇒ Boolean Also known as: is_degree?
true if a degree unit or equivalent.
749 750 751 |
# File 'lib/ruby_units/unit.rb', line 749 def degree? kind == :temperature end |
#divmod(other) ⇒ Array(Integer, Unit)
Divide two units and return quotient and remainder
999 1000 1001 1002 1003 |
# File 'lib/ruby_units/unit.rb', line 999 def divmod(other) raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless compatible_with?(other) [quo(other).to_base.floor, self % other] end |
#eliminate_terms ⇒ RubyUnits::Unit
Creates a new unit from the current one with all common terms eliminated.
334 335 336 |
# File 'lib/ruby_units/unit.rb', line 334 def eliminate_terms self.class.new(self.class.eliminate_terms(@scalar, @numerator, @denominator)) end |
#floor(*args) ⇒ Numeric, Unit
1341 1342 1343 1344 1345 |
# File 'lib/ruby_units/unit.rb', line 1341 def floor(*args) return @scalar.floor(*args) if unitless? self.class.new(@scalar.floor(*args), @numerator, @denominator) end |
#from(time_point) ⇒ Time, ... Also known as: after, from_now
1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 |
# File 'lib/ruby_units/unit.rb', line 1474 def from(time_point) case time_point when Time, DateTime, Date (begin time_point + self rescue StandardError time_point.to_datetime + self end) else raise ArgumentError, "Must specify a Time, Date, or DateTime" end end |
#hash ⇒ Object
override hash method so objects with same values are considered equal
1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 |
# File 'lib/ruby_units/unit.rb', line 1525 def hash [ @scalar, @numerator, @denominator, @base, @signature, @base_scalar, @unit_name ].hash end |
#inspect(dump = nil) ⇒ String
Normally pretty prints the unit, but if you really want to see the guts of it, pass ‘:dump’
732 733 734 735 736 |
# File 'lib/ruby_units/unit.rb', line 732 def inspect(dump = nil) return super() if dump to_s end |
#inverse ⇒ Unit
returns inverse of Unit (1/unit)
1119 1120 1121 |
# File 'lib/ruby_units/unit.rb', line 1119 def inverse self.class.new("1") / self end |
#kind ⇒ Symbol
@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…)
573 574 575 |
# File 'lib/ruby_units/unit.rb', line 573 def kind self.class.kinds[signature] end |
#power(n) ⇒ Unit
returns the unit raised to the n-th power
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 |
# File 'lib/ruby_units/unit.rb', line 1071 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 |
#pred ⇒ Unit
returns previous unit in a range. ‘2 mm’.to_unit.pred #=> ‘1 mm’.to_unit only works when the scalar is an integer
1386 1387 1388 1389 1390 |
# File 'lib/ruby_units/unit.rb', line 1386 def pred raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i self.class.new(@scalar.to_i.pred, @numerator, @denominator) end |
#quo(other) ⇒ Unit Also known as: fdiv
1020 1021 1022 |
# File 'lib/ruby_units/unit.rb', line 1020 def quo(other) self / other end |
#remainder(other) ⇒ Unit
Returns the remainder when one unit is divided by another
988 989 990 991 992 |
# File 'lib/ruby_units/unit.rb', line 988 def remainder(other) raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless compatible_with?(other) self.class.new(base_scalar.remainder(other.to_unit.base_scalar), to_base.units).convert_to(self) end |
#root(n) ⇒ Unit
Calculates the n-th root of a unit if n < 0, returns 1/unit^(1/n)
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 |
# File 'lib/ruby_units/unit.rb', line 1089 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.negative? vec = unit_signature_vector vec = vec.map { _1 % n } raise ArgumentError, "Illegal root" unless vec.max.zero? num = @numerator.dup den = @denominator.dup @numerator.uniq.each do |item| x = num.find_all { _1 == 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 { _1 == item }.size r = ((x / n) * (n - 1)).to_int r.times { den.delete_at(den.index(item)) } end self.class.new(scalar: @scalar**Rational(1, n), numerator: num, denominator: den) end |
#round(*args, **kwargs) ⇒ Numeric, Unit
Round the unit according to the rules of the scalar’s class. Call this with the arguments appropriate for the scalar’s class (e.g., Integer, Rational, etc..). Because unit conversions can often result in Rational scalars (to preserve precision), it may be advisable to use to_s
to format output instead of using round
.
1357 1358 1359 1360 1361 |
# File 'lib/ruby_units/unit.rb', line 1357 def round(*args, **kwargs) return @scalar.round(*args, **kwargs) if unitless? self.class.new(@scalar.round(*args, **kwargs), @numerator, @denominator) end |
#since(time_point) ⇒ Unit
1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 |
# File 'lib/ruby_units/unit.rb', line 1445 def since(time_point) case time_point when Time self.class.new(::Time.now - time_point, "second").convert_to(self) when DateTime, Date self.class.new(::DateTime.now - time_point, "day").convert_to(self) else raise ArgumentError, "Must specify a Time, Date, or DateTime" end end |
#succ ⇒ Unit 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
1374 1375 1376 1377 1378 |
# File 'lib/ruby_units/unit.rb', line 1374 def succ raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i self.class.new(@scalar.to_i.succ, @numerator, @denominator) end |
#temperature? ⇒ Boolean Also known as: is_temperature?
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
741 742 743 |
# File 'lib/ruby_units/unit.rb', line 741 def temperature? degree? && units.match?(self.class.temp_regex) end |
#temperature_scale ⇒ String
returns the ‘degree’ unit associated with a temperature unit
758 759 760 761 762 |
# File 'lib/ruby_units/unit.rb', line 758 def temperature_scale return nil unless temperature? "deg#{self.class.unit_map[units][/temp([CFRK])/, 1]}" end |
#to_base ⇒ Unit Also known as: base
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
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 |
# File 'lib/ruby_units/unit.rb', line 608 def to_base return self if base? if self.class.unit_map[units] =~ /\A<(?:temp|deg)[CRF]>\Z/ @signature = self.class.kinds.key(:temperature) base = if temperature? convert_to("tempK") elsif degree? convert_to("degK") end return base end cached_unit = self.class.base_unit_cache.get(units) return cached_unit * scalar unless cached_unit.nil? num = [] den = [] q = Rational(1) @numerator.compact.each do |num_unit| if self.class.prefix_values[num_unit] q *= self.class.prefix_values[num_unit] else q *= self.class.unit_values[num_unit][:scalar] if self.class.unit_values[num_unit] num << self.class.unit_values[num_unit][:numerator] if self.class.unit_values[num_unit] && self.class.unit_values[num_unit][:numerator] den << self.class.unit_values[num_unit][:denominator] if self.class.unit_values[num_unit] && self.class.unit_values[num_unit][:denominator] end end @denominator.compact.each do |num_unit| if self.class.prefix_values[num_unit] q /= self.class.prefix_values[num_unit] else q /= self.class.unit_values[num_unit][:scalar] if self.class.unit_values[num_unit] den << self.class.unit_values[num_unit][:numerator] if self.class.unit_values[num_unit] && self.class.unit_values[num_unit][:numerator] num << self.class.unit_values[num_unit][:denominator] if self.class.unit_values[num_unit] && self.class.unit_values[num_unit][:denominator] end end num = num.flatten.compact den = den.flatten.compact num = UNITY_ARRAY if num.empty? base = self.class.new(self.class.eliminate_terms(q, num, den)) self.class.base_unit_cache.set(units, base) base * @scalar end |
#to_c ⇒ Complex
converts the unit back to a complex if it is unitless. Otherwise raises an exception
1236 1237 1238 1239 1240 |
# File 'lib/ruby_units/unit.rb', line 1236 def to_c return Complex(@scalar) if unitless? raise "Cannot convert '#{self}' to Complex unless unitless. Use Unit#scalar" end |
#to_date ⇒ Date
1408 1409 1410 |
# File 'lib/ruby_units/unit.rb', line 1408 def to_date Date.new0(convert_to("d").scalar) end |
#to_datetime ⇒ ::DateTime
convert a duration to a DateTime. This will work so long as the duration is the duration from the zero date defined by DateTime
1403 1404 1405 |
# File 'lib/ruby_units/unit.rb', line 1403 def to_datetime DateTime.new!(convert_to("d").scalar) end |
#to_f ⇒ Float
converts the unit back to a float if it is unitless. Otherwise raises an exception
1227 1228 1229 1230 1231 |
# File 'lib/ruby_units/unit.rb', line 1227 def to_f return @scalar.to_f if unitless? raise "Cannot convert '#{self}' to Float unless unitless. Use Unit#scalar" end |
#to_i ⇒ Integer Also known as: to_int
if unitless, returns an int, otherwise raises an error
1245 1246 1247 1248 1249 |
# File 'lib/ruby_units/unit.rb', line 1245 def to_i return @scalar.to_int if unitless? raise "Cannot convert '#{self}' to Integer unless unitless. Use Unit#scalar" end |
#to_r ⇒ Rational
if unitless, returns a Rational, otherwise raises an error
1256 1257 1258 1259 1260 |
# File 'lib/ruby_units/unit.rb', line 1256 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, precision: 0.0001, format: RubyUnits.configuration.format) ⇒ String
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
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 |
# File 'lib/ruby_units/unit.rb', line 675 def to_s(target_units = nil, precision: 0.0001, format: RubyUnits.configuration.format) out = @output[target_units] return out if out separator = RubyUnits.configuration.separator case target_units when :ft feet, inches = convert_to("in").scalar.abs.divmod(12) improper, frac = inches.divmod(1) frac = frac.zero? ? "" : "-#{frac.rationalize(precision)}" out = "#{negative? ? '-' : nil}#{feet}'#{improper}#{frac}\"" when :lbs pounds, ounces = convert_to("oz").scalar.abs.divmod(16) improper, frac = ounces.divmod(1) frac = frac.zero? ? "" : "-#{frac.rationalize(precision)}" out = "#{negative? ? '-' : nil}#{pounds}#{separator}lbs #{improper}#{frac}#{separator}oz" when :stone stone, pounds = convert_to("lbs").scalar.abs.divmod(14) improper, frac = pounds.divmod(1) frac = frac.zero? ? "" : "-#{frac.rationalize(precision)}" out = "#{negative? ? '-' : nil}#{stone}#{separator}stone #{improper}#{frac}#{separator}lbs" 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), format: format) else "#{Regexp.last_match(1) % @scalar}#{separator}#{Regexp.last_match(2) || units(format: format)}".strip end rescue StandardError # 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(format: format) else raise "unhandled case" end else out = case @scalar when Complex "#{@scalar}#{separator}#{units(format: format)}" when Rational "#{@scalar == @scalar.to_i ? @scalar.to_i : @scalar}#{separator}#{units(format: format)}" else "#{'%g' % @scalar}#{separator}#{units(format: format)}" end.strip end @output[target_units] = out out end |
#to_time ⇒ Time 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.
1394 1395 1396 |
# File 'lib/ruby_units/unit.rb', line 1394 def to_time Time.at(self) end |
#to_unit(other = nil) ⇒ RubyUnits::Unit Also known as: unit
Convert the unit to a Unit, possibly performing a conversion. > The ability to pass a Unit to convert to was added in v3.0.0 for > consistency with other uses of #to_unit.
583 584 585 |
# File 'lib/ruby_units/unit.rb', line 583 def to_unit(other = nil) other ? convert_to(other) : self end |
#truncate(*args) ⇒ Numeric, Unit
1364 1365 1366 1367 1368 |
# File 'lib/ruby_units/unit.rb', line 1364 def truncate(*args) return @scalar.truncate(*args) if unitless? self.class.new(@scalar.truncate(*args), @numerator, @denominator) end |
#unitless? ⇒ Boolean
returns true if no associated units false, even if the units are “unitless” like ‘radians, each, etc’
767 768 769 |
# File 'lib/ruby_units/unit.rb', line 767 def unitless? @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY end |
#units(with_prefix: true, format: nil) ⇒ String
Returns the ‘unit’ part of the Unit object without the scalar
1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 |
# File 'lib/ruby_units/unit.rb', line 1274 def units(with_prefix: true, format: nil) 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 { self.class.definition(_1) } definitions.reject!(&:prefix?) unless with_prefix definitions = definitions.chunk_while { |definition, _| definition.prefix? }.to_a output_numerator = definitions.map { _1.map(&:display_name).join } end unless den == UNITY_ARRAY definitions = den.map { self.class.definition(_1) } definitions.reject!(&:prefix?) unless with_prefix definitions = definitions.chunk_while { |definition, _| definition.prefix? }.to_a output_denominator = definitions.map { _1.map(&:display_name).join } end on = output_numerator .uniq .map { [_1, output_numerator.count(_1)] } .map { |element, power| (element.to_s.strip + (power > 1 ? "^#{power}" : "")) } if format == :exponential od = output_denominator .uniq .map { [_1, output_denominator.count(_1)] } .map { |element, power| (element.to_s.strip + (power.positive? ? "^#{-power}" : "")) } (on + od).join("*").strip else od = output_denominator .uniq .map { [_1, output_denominator.count(_1)] } .map { |element, power| (element.to_s.strip + (power > 1 ? "^#{power}" : "")) } "#{on.join('*')}#{od.empty? ? '' : "/#{od.join('*')}"}".strip end end |
#until(time_point) ⇒ Unit
1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 |
# File 'lib/ruby_units/unit.rb', line 1459 def until(time_point) case time_point when Time self.class.new(time_point - ::Time.now, "second").convert_to(self) when DateTime, Date self.class.new(time_point - ::DateTime.now, "day").convert_to(self) else raise ArgumentError, "Must specify a Time, Date, or DateTime" end end |
#zero? ⇒ Boolean
true if scalar is zero
1414 1415 1416 |
# File 'lib/ruby_units/unit.rb', line 1414 def zero? base_scalar.zero? end |