Class: RubyUnits::Unit
- 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
Modules: Version Classes: Cache, Definition
Constant Summary collapse
- VERSION =
Unit::Version::STRING
- UNITY =
'<1>'
- UNITY_ARRAY =
[UNITY]
- 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}/
- TIME_REGEX =
/(\d+)*:(\d+)*:*(\d+)*[:,]*(\d+)*/
- SCI_NUMBER =
%r{([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)}
- RATIONAL_NUMBER =
/\(?([+-])?(\d+[ -])?(\d+)\/(\d+)\)?/
- COMPLEX_NUMBER =
/#{SCI_NUMBER}?#{SCI_NUMBER}i\b/
- NUMBER_REGEX =
/#{SCI_NUMBER}*\s*(.+)?/
- UNIT_STRING_REGEX =
/#{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>']
- FAHRENHEIT =
['<fahrenheit>']
- RANKINE =
['<rankine>']
- CELSIUS =
['<celsius>']
- SIGNATURE_VECTOR =
[ :length, :time, :temperature, :mass, :current, :substance, :luminosity, :currency, :information, :angle ]
- @@definitions =
{}
- @@PREFIX_VALUES =
{}
- @@PREFIX_MAP =
{}
- @@UNIT_MAP =
{}
- @@UNIT_VALUES =
{}
- @@UNIT_REGEX =
nil
- @@UNIT_MATCH_REGEX =
nil
- @@TEMP_REGEX =
nil
- @@KINDS =
{ -312078 => :elastance, -312058 => :resistance, -312038 => :inductance, -152040 => :magnetism, -152038 => :magnetism, -152058 => :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, 152020 => :radiation_exposure, 159999 => :magnetism, 160000 => :current, 160020 => :charge, 312058 => :conductance, 312078 => :capacitance, 3199980 => :activity, 3199997 => :molar_concentration, 3200000 => :substance, 63999998 => :illuminance, 64000000 => :luminous_power, 1280000000 => :currency, 25600000000 => :information, 511999999980 => :angular_velocity, 512000000000 => :angle }
- @@cached_units =
{}
- @@base_unit_cache =
{}
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 ⇒ Hash
- .cached ⇒ Hash
- .clear_cache ⇒ true
-
.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) ⇒ RubyUnits::Unit::Definition?
return the unit definition for a unit.
-
.definitions ⇒ Array
return a list of all defined units.
- .parse(input) ⇒ Unit
-
.redefine!(name) {|RubyUnits::Unit::Definition| ... } ⇒ RubyUnits::Unit::Definition
Get the definition for a unit and allow it to be redefined.
-
.setup ⇒ true
setup internal arrays and hashes.
-
.undefine!(unit) ⇒ true
Undefine a unit.
Instance Method Summary collapse
-
#%(other) ⇒ Integer
perform a modulo on a unit, will raise an exception if the units are not compatible.
-
#*(other) ⇒ Unit
Multiply two units.
-
#**(other) ⇒ Unit
Exponentiate.
-
#+(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) ⇒ -1|0|1|nil
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, 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.
-
#abs ⇒ Numeric, Unit
absolute value of a unit.
- #ago ⇒ Unit
-
#as_json(*args) ⇒ String
Returns string formatted for json.
- #before(time_point = ::Time.now) ⇒ Unit (also: #before_now)
-
#best_prefix ⇒ Object
returns a new unit that has been scaled to be more in line with typical usage.
-
#ceil ⇒ Numeric, Unit
ceil of a unit.
-
#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.
-
#convert_to(other) ⇒ Unit
(also: #>>, #to)
convert to a specified unit string or to the same units as another Unit.
-
#copy(from) ⇒ Unit
Used to copy one unit to another.
-
#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.
- #floor ⇒ Numeric, Unit
- #from(time_point) ⇒ Time, ... (also: #after, #from_now)
-
#initialize(*options) ⇒ Unit
constructor
Create a new Unit object.
- #inspect(dump = nil) ⇒ String deprecated Deprecated.
-
#inverse ⇒ Unit
returns inverse of Unit (1/unit).
-
#is_base? ⇒ Boolean
(also: #base?)
Is this unit in base form?.
-
#is_degree? ⇒ Boolean
(also: #degree?)
true if a degree unit or equivalent.
-
#is_temperature? ⇒ Boolean
(also: #temperature?)
true if unit is a ‘temperature’, false if a ‘degree’ or anything else.
-
#kind ⇒ Symbol
@todo: figure out how to handle :counting units.
-
#kind_of?(klass) ⇒ Boolean
needed to make complex units play nice – otherwise not detected as a complex_generic.
-
#power(n) ⇒ Unit
returns the unit raised to the n-th power.
-
#pred ⇒ Unit
returns previous unit in a range.
-
#root(n) ⇒ Unit
Calculates the n-th root of a unit if n < 0, returns 1/unit^(1/n).
- #round(ndigits = 0) ⇒ Numeric, Unit
- #since(time_point) ⇒ Unit
-
#succ ⇒ Unit
(also: #next)
returns next unit in a range.
-
#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) ⇒ String
Generate human readable output.
-
#to_time ⇒ Time
(also: #time)
Tries to make a Time object from current unit.
- #to_unit ⇒ Unit (also: #unit)
- #truncate ⇒ 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) ⇒ 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
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 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 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/ruby_units/unit.rb', line 284 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 begin cached = @@cached_units[[1]] * [0] copy(cached) rescue initialize("#{[0]} #{([1].units rescue [1])}") end return end if .size == 3 [1] = [1].join if [1].kind_of?(Array) [2] = [2].join if [2].kind_of?(Array) begin cached = @@cached_units["#{[1]}/#{[2]}"] * [0] copy(cached) rescue initialize("#{[0]} #{[1]}/#{[2]}") 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 self.update_base_scalar raise ArgumentError, "Temperatures must not be less than absolute zero" if self.is_temperature? && self.base_scalar < 0 unary_unit = self.units || "" if .first.instance_of?(String) opt_scalar, opt_units = RubyUnits::Unit.parse_into_numbers_and_units([0]) unless @@cached_units.keys.include?(opt_units) || (opt_units =~ %r{\D/[\d+\.]+}) || (opt_units =~ /(#{RubyUnits::Unit.temp_regex})|(#{LBS_OZ_UNIT_REGEX})|(#{FEET_INCH_UNITS_REGEX})|%|(#{TIME_REGEX})|i\s?(.+)?|±|\+\/-/) @@cached_units[opt_units] = (self.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}/) then @@cached_units[unary_unit] = (self.scalar == 1 ? self : unary_unit.to_unit) end [@scalar, @numerator, @denominator, @base_scalar, @signature, @is_base].each { |x| x.freeze } return self end |
Instance Attribute Details
#base_denominator ⇒ Array
235 236 237 |
# File 'lib/ruby_units/unit.rb', line 235 def base_denominator @base_denominator end |
#base_numerator ⇒ Array
232 233 234 |
# File 'lib/ruby_units/unit.rb', line 232 def base_numerator @base_numerator end |
#base_scalar ⇒ Numeric
229 230 231 |
# File 'lib/ruby_units/unit.rb', line 229 def base_scalar @base_scalar end |
#denominator ⇒ Array
223 224 225 |
# File 'lib/ruby_units/unit.rb', line 223 def denominator @denominator end |
#numerator ⇒ Array
220 221 222 |
# File 'lib/ruby_units/unit.rb', line 220 def numerator @numerator end |
#signature ⇒ Integer
226 227 228 |
# File 'lib/ruby_units/unit.rb', line 226 def signature @signature end |
#unit_name ⇒ String
241 242 243 |
# File 'lib/ruby_units/unit.rb', line 241 def unit_name @unit_name end |
Class Method Details
.base_unit_cache ⇒ Hash
386 387 388 |
# File 'lib/ruby_units/unit.rb', line 386 def self.base_unit_cache return @@base_unit_cache end |
.cached ⇒ Hash
371 372 373 |
# File 'lib/ruby_units/unit.rb', line 371 def self.cached return @@cached_units end |
.clear_cache ⇒ true
377 378 379 380 381 382 |
# File 'lib/ruby_units/unit.rb', line 377 def self.clear_cache @@cached_units = {} @@base_unit_cache = {} RubyUnits::Unit.new(1) return true end |
.define(unit_definition, &block) ⇒ RubyUnits::Unit::Definition
Unpack a unit definition and add it to the array of defined units
181 182 183 184 185 186 187 188 189 |
# File 'lib/ruby_units/unit.rb', line 181 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) return unit_definition end |
.defined?(unit) ⇒ Boolean
determine if a unit is already defined
149 150 151 |
# File 'lib/ruby_units/unit.rb', line 149 def self.defined?(unit) self.definitions.values.any? { |d| d.aliases.include?(unit) } end |
.definition(_unit) ⇒ RubyUnits::Unit::Definition?
return the unit definition for a unit
156 157 158 159 |
# File 'lib/ruby_units/unit.rb', line 156 def self.definition(_unit) unit = (_unit =~ /^<.+>$/) ? _unit : "<#{_unit}>" return @@definitions[unit] end |
.definitions ⇒ Array
return a list of all defined units
163 164 165 |
# File 'lib/ruby_units/unit.rb', line 163 def self.definitions return @@definitions end |
.parse(input) ⇒ Unit
394 395 396 397 |
# File 'lib/ruby_units/unit.rb', line 394 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 |
.redefine!(name) {|RubyUnits::Unit::Definition| ... } ⇒ RubyUnits::Unit::Definition
Get the definition for a unit and allow it to be redefined
197 198 199 200 201 202 203 204 |
# File 'lib/ruby_units/unit.rb', line 197 def self.redefine!(name) fail ArgumentError, 'A block is required to redefine a unit' unless block_given? unit_definition = definition(name) yield unit_definition @@definitions.delete("<#{name}>") define(unit_definition) RubyUnits::Unit.setup end |
.setup ⇒ true
setup internal arrays and hashes
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/ruby_units/unit.rb', line 127 def self.setup self.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| self.use_definition(definition) end RubyUnits::Unit.new(1) return true end |
Instance Method Details
#%(other) ⇒ Integer
perform a modulo on a unit, will raise an exception if the units are not compatible
801 802 803 |
# File 'lib/ruby_units/unit.rb', line 801 def %(other) return self.divmod(other).last end |
#*(other) ⇒ Unit
Multiply two units.
746 747 748 749 750 751 752 753 754 755 756 757 758 759 |
# File 'lib/ruby_units/unit.rb', line 746 def *(other) case other when Unit raise ArgumentError, "Cannot multiply by temperatures" if [other, self].any? { |x| x.is_temperature? } opts = RubyUnits::Unit.eliminate_terms(@scalar*other.scalar, @numerator + other.numerator, @denominator + other.denominator) opts.merge!(:signature => @signature + other.signature) return RubyUnits::Unit.new(opts) when Numeric return RubyUnits::Unit.new(:scalar => @scalar*other, :numerator => @numerator, :denominator => @denominator, :signature => @signature) else x, y = coerce(other) return 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
819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 |
# File 'lib/ruby_units/unit.rb', line 819 def **(other) raise ArgumentError, "Cannot raise a temperature to a power" if self.is_temperature? if other.kind_of?(Numeric) return self.inverse if other == -1 return self if other == 1 return 1 if other.zero? end case other when Rational return self.power(other.numerator).root(other.denominator) when Integer return self.power(other) when Float return self**(other.to_i) if other == other.to_i valid = (1..9).map { |x| 1/x } raise ArgumentError, "Not a n-th root (1..9), use 1/n" unless valid.include? other.abs return self.root((1/other).to_int) when Complex raise ArgumentError, "exponentiation of complex numbers is not yet 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
674 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 |
# File 'lib/ruby_units/unit.rb', line 674 def +(other) case other when Unit case when self.zero? other.dup when self =~ other raise ArgumentError, "Cannot add two temperatures" if ([self, other].all? { |x| x.is_temperature? }) if [self, other].any? { |x| x.is_temperature? } if self.is_temperature? RubyUnits::Unit.new(:scalar => (self.scalar + other.convert_to(self.temperature_scale).scalar), :numerator => @numerator, :denominator => @denominator, :signature => @signature) else RubyUnits::Unit.new(:scalar => (other.scalar + self.convert_to(other.temperature_scale).scalar), :numerator => other.numerator, :denominator => other.denominator, :signature => other.signature) end else @q ||= ((@@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar) rescue (self.units.to_unit.to_base.scalar)) RubyUnits::Unit.new(:scalar => (self.base_scalar + other.base_scalar)*@q, :numerator => @numerator, :denominator => @denominator, :signature => @signature) 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
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 |
# File 'lib/ruby_units/unit.rb', line 709 def -(other) case other when Unit case when self.zero? if other.zero? other.dup * -1 # preserve Units class else -other.dup end when self =~ other case when [self, other].all? { |x| x.is_temperature? } RubyUnits::Unit.new(:scalar => (self.base_scalar - other.base_scalar), :numerator => KELVIN, :denominator => UNITY_ARRAY, :signature => @signature).convert_to(self.temperature_scale) when self.is_temperature? RubyUnits::Unit.new(:scalar => (self.base_scalar - other.base_scalar), :numerator => ['<tempK>'], :denominator => UNITY_ARRAY, :signature => @signature).convert_to(self) when other.is_temperature? raise ArgumentError, "Cannot subtract a temperature from a differential degree unit" else @q ||= ((@@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar) rescue (self.units.to_unit.scalar/self.units.to_unit.to_base.scalar)) RubyUnits::Unit.new(:scalar => (self.base_scalar - other.base_scalar)*@q, :numerator => @numerator, :denominator => @denominator, :signature => @signature) 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) return y-x end end |
#-@ ⇒ Numeric, Unit
negates the scalar of the Unit
1066 1067 1068 1069 |
# File 'lib/ruby_units/unit.rb', line 1066 def -@ return -@scalar if self.unitless? return (self.dup * -1) end |
#/(other) ⇒ Unit
Divide two units. Throws an exception if divisor is 0
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 |
# File 'lib/ruby_units/unit.rb', line 767 def /(other) case other when Unit raise ZeroDivisionError if other.zero? raise ArgumentError, "Cannot divide with temperatures" if [other, self].any? { |x| x.is_temperature? } opts = RubyUnits::Unit.eliminate_terms(@scalar/other.scalar, @numerator + other.denominator, @denominator + other.numerator) opts.merge!(:signature => @signature - other.signature) return RubyUnits::Unit.new(opts) when Numeric raise ZeroDivisionError if other.zero? return RubyUnits::Unit.new(:scalar => @scalar/other, :numerator => @numerator, :denominator => @denominator, :signature => @signature) else x, y = coerce(other) return 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.
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 |
# File 'lib/ruby_units/unit.rb', line 575 def <=>(other) case when !self.base_scalar.respond_to?(:<=>) raise NoMethodError, "undefined method `<=>' for #{self.base_scalar.inspect}" when other.nil? return self.base_scalar <=> nil when !self.is_temperature? && other.respond_to?(:zero?) && other.zero? return self.base_scalar <=> 0 when other.instance_of?(Unit) raise ArgumentError, "Incompatible Units ('#{self.units}' not compatible with '#{other.units}')" unless self =~ other return self.base_scalar <=> other.base_scalar else x, y = coerce(other) return 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.
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 |
# File 'lib/ruby_units/unit.rb', line 600 def ==(other) case when other.respond_to?(:zero?) && other.zero? return self.zero? when other.instance_of?(Unit) return false unless self =~ other return self.base_scalar == other.base_scalar else begin x, y = coerce(other) return x == y rescue ArgumentError # return false when object cannot be coerced return false end end end |
#===(other) ⇒ Boolean Also known as: same?, same_as?
Compare two units. Returns true if quantities and units match
649 650 651 652 653 654 655 656 657 658 659 660 661 |
# File 'lib/ruby_units/unit.rb', line 649 def ===(other) case other when Unit (self.scalar == other.scalar) && (self.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?
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
626 627 628 629 630 631 632 633 634 635 636 637 638 |
# File 'lib/ruby_units/unit.rb', line 626 def =~(other) case other when Unit self.signature == other.signature else begin x, y = coerce(other) return x =~ y rescue ArgumentError return false end end end |
#abs ⇒ Numeric, Unit
absolute value of a unit
1073 1074 1075 1076 |
# File 'lib/ruby_units/unit.rb', line 1073 def abs return @scalar.abs if self.unitless? return RubyUnits::Unit.new(@scalar.abs, @numerator, @denominator) end |
#ago ⇒ Unit
1151 1152 1153 |
# File 'lib/ruby_units/unit.rb', line 1151 def ago return self.before end |
#as_json(*args) ⇒ String
Returns string formatted for json
1013 1014 1015 |
# File 'lib/ruby_units/unit.rb', line 1013 def as_json(*args) to_s end |
#before(time_point = ::Time.now) ⇒ Unit Also known as: before_now
1157 1158 1159 1160 1161 1162 1163 1164 |
# File 'lib/ruby_units/unit.rb', line 1157 def before(time_point = ::Time.now) case time_point when Time, Date, DateTime return (time_point - self rescue time_point.to_datetime - self) else raise ArgumentError, "Must specify a Time, Date, or DateTime" end end |
#best_prefix ⇒ Object
returns a new unit that has been scaled to be more in line with typical usage.
1230 1231 1232 1233 1234 1235 1236 1237 1238 |
# File 'lib/ruby_units/unit.rb', line 1230 def best_prefix return self.to_base if self.scalar == 0 _best_prefix = if (self.kind == :information) @@PREFIX_VALUES.key(2**((Math.log(self.base_scalar,2) / 10.0).floor * 10)) else @@PREFIX_VALUES.key(10**((Math.log10(self.base_scalar) / 3.0).floor * 3)) end self.to(RubyUnits::Unit.new(@@PREFIX_MAP.key(_best_prefix)+self.units(false))) end |
#ceil ⇒ Numeric, Unit
ceil of a unit
1080 1081 1082 1083 |
# File 'lib/ruby_units/unit.rb', line 1080 def ceil return @scalar.ceil if self.unitless? return 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
1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 |
# File 'lib/ruby_units/unit.rb', line 1217 def coerce(other) if other.respond_to? :to_unit return [other.to_unit, self] end case other when Unit return [other, self] else return [RubyUnits::Unit.new(other), self] end 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.
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.
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 |
# File 'lib/ruby_units/unit.rb', line 921 def convert_to(other) return self if other.nil? return self if TrueClass === other return self if FalseClass === other if (Unit === other && other.is_temperature?) || (String === other && other =~ /temp[CFRK]/) raise ArgumentError, "Receiver is not a temperature unit" unless self.degree? start_unit = self.units target_unit = other.units rescue other unless @base_scalar @base_scalar = case @@UNIT_MAP[start_unit] when '<tempC>' @scalar + 273.15 when '<tempK>' @scalar when '<tempF>' (@scalar+459.67)*Rational(5, 9) when '<tempR>' @scalar*Rational(5, 9) end end q= case @@UNIT_MAP[target_unit] when '<tempC>' @base_scalar - 273.15 when '<tempK>' @base_scalar when '<tempF>' @base_scalar * Rational(9, 5) - 459.67 when '<tempR>' @base_scalar * Rational(9, 5) end return RubyUnits::Unit.new("#{q} #{target_unit}") else case other when Unit return self if other.units == self.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.kind_of?(Numeric) ? i : @@UNIT_VALUES[i][:scalar] }.compact _denominator1 = @denominator.map { |x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x }.map { |i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[i][:scalar] }.compact _numerator2 = target.numerator.map { |x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x }.map { |x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[x][:scalar] }.compact _denominator2 = target.denominator.map { |x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x }.map { |x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[x][:scalar] }.compact q = @scalar * ((_numerator1 + _denominator2).inject(1) { |product, n| product*n }) / ((_numerator2 + _denominator1).inject(1) { |product, n| product*n }) 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
253 254 255 256 257 258 259 260 261 262 |
# File 'lib/ruby_units/unit.rb', line 253 def copy(from) @scalar = from.scalar @numerator = from.numerator @denominator = from.denominator @is_base = from.is_base? @signature = from.signature @base_scalar = from.base_scalar @unit_name = from.unit_name rescue nil return self 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
789 790 791 792 793 794 795 796 |
# File 'lib/ruby_units/unit.rb', line 789 def divmod(other) raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ other if self.units == other.units return self.scalar.divmod(other.scalar) else return self.to_base.scalar.divmod(other.to_base.scalar) end end |
#floor ⇒ Numeric, Unit
1086 1087 1088 1089 |
# File 'lib/ruby_units/unit.rb', line 1086 def floor return @scalar.floor if self.unitless? return RubyUnits::Unit.new(@scalar.floor, @numerator, @denominator) end |
#from(time_point) ⇒ Time, ... Also known as: after, from_now
1201 1202 1203 1204 1205 1206 1207 1208 |
# File 'lib/ruby_units/unit.rb', line 1201 def from(time_point) case time_point when Time, DateTime, Date return (time_point + self rescue time_point.to_datetime + self) else raise ArgumentError, "Must specify a Time, Date, or DateTime" end end |
#inspect(dump = nil) ⇒ String
Normally pretty prints the unit, but if you really want to see the guts of it, pass ‘:dump’
532 533 534 535 |
# File 'lib/ruby_units/unit.rb', line 532 def inspect(dump = nil) return super() if dump to_s end |
#inverse ⇒ Unit
returns inverse of Unit (1/unit)
898 899 900 |
# File 'lib/ruby_units/unit.rb', line 898 def inverse return RubyUnits::Unit.new("1") / self end |
#is_base? ⇒ Boolean Also known as: base?
Is this unit in base form?
408 409 410 411 412 413 414 |
# File 'lib/ruby_units/unit.rb', line 408 def is_base? return @is_base if defined? @is_base @is_base = (@numerator + @denominator).compact.uniq. map { |unit| RubyUnits::Unit.definition(unit) }. all? { |element| element.unity? || element.base? } return @is_base end |
#is_degree? ⇒ Boolean Also known as: degree?
true if a degree unit or equivalent.
548 549 550 |
# File 'lib/ruby_units/unit.rb', line 548 def is_degree? return self.kind == :temperature end |
#is_temperature? ⇒ Boolean Also known as: 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
540 541 542 |
# File 'lib/ruby_units/unit.rb', line 540 def is_temperature? return self.is_degree? && (!(@@UNIT_MAP[self.units] =~ /temp[CFRK]/).nil?) 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…)
365 366 367 |
# File 'lib/ruby_units/unit.rb', line 365 def kind return @@KINDS[self.signature] end |
#kind_of?(klass) ⇒ Boolean
needed to make complex units play nice – otherwise not detected as a complex_generic
246 247 248 |
# File 'lib/ruby_units/unit.rb', line 246 def kind_of?(klass) self.scalar.kind_of?(klass) end |
#power(n) ⇒ Unit
returns the unit raised to the n-th power
848 849 850 851 852 853 854 855 856 857 858 859 |
# File 'lib/ruby_units/unit.rb', line 848 def power(n) raise ArgumentError, "Cannot raise a temperature to a power" if self.is_temperature? raise ArgumentError, "Exponent must an Integer" unless n.kind_of?(Integer) return self.inverse if n == -1 return 1 if n.zero? return self if n == 1 if n > 0 then return (1..(n-1).to_i).inject(self) { |product, x| product * self } else return (1..-(n-1).to_i).inject(self) { |product, x| product / self } end 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
1118 1119 1120 1121 |
# File 'lib/ruby_units/unit.rb', line 1118 def pred raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i return 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)
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 |
# File 'lib/ruby_units/unit.rb', line 868 def root(n) raise ArgumentError, "Cannot take the root of a temperature" if self.is_temperature? raise ArgumentError, "Exponent must an Integer" unless n.kind_of?(Integer) raise ArgumentError, "0th root undefined" if n.zero? return self if n == 1 return self.root(n.abs).inverse if n < 0 vec = self.unit_signature_vector vec =vec.map { |x| x % n } raise ArgumentError, "Illegal root" unless vec.max == 0 num = @numerator.dup den = @denominator.dup for item in @numerator.uniq do x = num.find_all { |i| i==item }.size r = ((x/n)*(n-1)).to_int r.times { |y| num.delete_at(num.index(item)) } end for item in @denominator.uniq do x = den.find_all { |i| i==item }.size r = ((x/n)*(n-1)).to_int r.times { |y| den.delete_at(den.index(item)) } end q = @scalar < 0 ? (-1)**Rational(1, n) * (@scalar.abs)**Rational(1, n) : @scalar**Rational(1, n) return RubyUnits::Unit.new(:scalar => q, :numerator => num, :denominator => den) end |
#round(ndigits = 0) ⇒ Numeric, Unit
1092 1093 1094 1095 |
# File 'lib/ruby_units/unit.rb', line 1092 def round(ndigits = 0) return @scalar.round(ndigits) if self.unitless? return RubyUnits::Unit.new(@scalar.round(ndigits), @numerator, @denominator) end |
#since(time_point) ⇒ Unit
1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 |
# File 'lib/ruby_units/unit.rb', line 1172 def since(time_point) case time_point when Time return (Time.now - time_point).to_unit('s').convert_to(self) when DateTime, Date return (DateTime.now - time_point).to_unit('d').convert_to(self) else fail 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
1107 1108 1109 1110 |
# File 'lib/ruby_units/unit.rb', line 1107 def succ raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i return RubyUnits::Unit.new(@scalar.to_i.succ, @numerator, @denominator) end |
#temperature_scale ⇒ String
returns the ‘degree’ unit associated with a temperature unit
557 558 559 560 |
# File 'lib/ruby_units/unit.rb', line 557 def temperature_scale return nil unless self.is_temperature? return "deg#{@@UNIT_MAP[self.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
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 |
# File 'lib/ruby_units/unit.rb', line 422 def to_base return self if self.is_base? if @@UNIT_MAP[self.units] =~ /\A<(?:temp|deg)[CRF]>\Z/ @signature = @@KINDS.key(:temperature) base = case when self.is_temperature? self.convert_to('tempK') when self.is_degree? self.convert_to('degK') end return base end cached = ((@@base_unit_cache[self.units] * self.scalar) rescue nil) return cached if cached num = [] den = [] q = 1 for unit in @numerator.compact do if @@PREFIX_VALUES[unit] q *= @@PREFIX_VALUES[unit] else q *= @@UNIT_VALUES[unit][:scalar] if @@UNIT_VALUES[unit] num << @@UNIT_VALUES[unit][:numerator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:numerator] den << @@UNIT_VALUES[unit][:denominator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:denominator] end end for unit in @denominator.compact do if @@PREFIX_VALUES[unit] q /= @@PREFIX_VALUES[unit] else q /= @@UNIT_VALUES[unit][:scalar] if @@UNIT_VALUES[unit] den << @@UNIT_VALUES[unit][:numerator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:numerator] num << @@UNIT_VALUES[unit][:denominator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[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[self.units]=base return base * @scalar end |
#to_c ⇒ Complex
converts the unit back to a complex if it is unitless. Otherwise raises an exception
988 989 990 991 |
# File 'lib/ruby_units/unit.rb', line 988 def to_c return Complex(@scalar) if self.unitless? raise RuntimeError, "Cannot convert '#{self.to_s}' to Complex unless unitless. Use Unit#scalar" end |
#to_date ⇒ Date
1139 1140 1141 |
# File 'lib/ruby_units/unit.rb', line 1139 def to_date return Date.new0(self.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
1134 1135 1136 |
# File 'lib/ruby_units/unit.rb', line 1134 def to_datetime return DateTime.new!(self.convert_to('d').scalar) end |
#to_f ⇒ Float
converts the unit back to a float if it is unitless. Otherwise raises an exception
980 981 982 983 |
# File 'lib/ruby_units/unit.rb', line 980 def to_f return @scalar.to_f if self.unitless? raise RuntimeError, "Cannot convert '#{self.to_s}' 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
996 997 998 999 |
# File 'lib/ruby_units/unit.rb', line 996 def to_i return @scalar.to_int if self.unitless? raise RuntimeError, "Cannot convert '#{self.to_s}' to Integer unless unitless. Use Unit#scalar" end |
#to_r ⇒ Rational
if unitless, returns a Rational, otherwise raises an error
1006 1007 1008 1009 |
# File 'lib/ruby_units/unit.rb', line 1006 def to_r return @scalar.to_r if self.unitless? raise RuntimeError, "Cannot convert '#{self.to_s}' to Rational unless unitless. Use Unit#scalar" end |
#to_s(target_units = nil) ⇒ String
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
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 |
# File 'lib/ruby_units/unit.rb', line 485 def to_s(target_units=nil) out = @output[target_units] if out return out else case target_units when :ft inches = self.convert_to("in").scalar.to_int out = "#{(inches / 12).truncate}\'#{(inches % 12).round}\"" when :lbs ounces = self.convert_to("oz").scalar.to_int out = "#{(ounces / 16).truncate} lbs, #{(ounces % 16).round} oz" when String out = case target_units.strip when /\A\s*\Z/ # whitespace only '' when /(%[\-+\.\w#]+)\s*(.+)*/ #format string like '%0.2f in' begin if $2 #unit specified, need to convert self.convert_to($2).to_s($1) else "#{$1 % @scalar} #{$2 || self.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' self.convert_to($1).to_s else raise "unhandled case" end else out = case @scalar when Rational, Complex "#{@scalar} #{self.units}" else "#{'%g' % @scalar} #{self.units}" end.strip end @output[target_units] = out return out end 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.
1125 1126 1127 |
# File 'lib/ruby_units/unit.rb', line 1125 def to_time return Time.at(self) end |
#to_unit ⇒ Unit Also known as: unit
400 401 402 |
# File 'lib/ruby_units/unit.rb', line 400 def to_unit self end |
#truncate ⇒ Numeric, Unit
1098 1099 1100 1101 |
# File 'lib/ruby_units/unit.rb', line 1098 def truncate return @scalar.truncate if self.unitless? return 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’
565 566 567 |
# File 'lib/ruby_units/unit.rb', line 565 def unitless? return(@numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY) end |
#units(with_prefix = true) ⇒ String
returns the ‘unit’ part of the Unit object without the scalar
1019 1020 1021 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 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 |
# File 'lib/ruby_units/unit.rb', line 1019 def units(with_prefix = true) return "" if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY output_numerator = [] output_denominator = [] num = @numerator.clone.compact den = @denominator.clone.compact if @numerator == UNITY_ARRAY output_numerator << "1" else while defn = RubyUnits::Unit.definition(num.shift) do if defn && defn.prefix? if with_prefix output_numerator << (defn.display_name + RubyUnits::Unit.definition(num.shift).display_name) end else output_numerator << defn.display_name end end end if @denominator == UNITY_ARRAY output_denominator = [] else while defn = RubyUnits::Unit.definition(den.shift) do if defn && defn.prefix? if with_prefix output_denominator << (defn.display_name + RubyUnits::Unit.definition(den.shift).display_name) end else output_denominator << defn.display_name end end end on = output_numerator.uniq. map { |x| [x, output_numerator.count(x)] }. map { |element, power| ("#{element}".strip + (power > 1 ? "^#{power}" : '')) } od = output_denominator.uniq. map { |x| [x, output_denominator.count(x)] }. map { |element, power| ("#{element}".strip + (power > 1 ? "^#{power}" : '')) } out = "#{on.join('*')}#{od.empty? ? '' : '/' + od.join('*')}".strip return out end |
#until(time_point) ⇒ Unit
1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 |
# File 'lib/ruby_units/unit.rb', line 1186 def until(time_point) case time_point when Time return (time_point - Time.now).to_unit('s').convert_to(self) when DateTime, Date return (time_point - DateTime.now).to_unit('d').convert_to(self) else fail ArgumentError, 'Must specify a Time, Date, or DateTime' end end |
#zero? ⇒ Boolean
true if scalar is zero
1145 1146 1147 |
# File 'lib/ruby_units/unit.rb', line 1145 def zero? return self.base_scalar.zero? end |