Class: 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_REGEX =
/(\d+)\s*(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inches)/- TIME_REGEX =
/(\d+)*:(\d+)*:*(\d+)*[:,]*(\d+)*/- LBS_OZ_REGEX =
/(\d+)\s*(?:#|lbs|pounds|pound-mass)+[\s,]*(\d+)\s*(?:oz|ounces)/- SCI_NUMBER =
%r{([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)}- RATIONAL_NUMBER =
/\(?([+-]?\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+)/- UNCERTAIN_REGEX =
/#{SCI_NUMBER}\s*\+\/-\s*#{SCI_NUMBER}\s(.+)/- 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, :memory, :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 => :resistance, 312078 => :capacitance, 3199980 => :activity, 3199997 => :molar_concentration, 3200000 => :substance, 63999998 => :illuminance, 64000000 => :luminous_power, 1280000000 => :currency, 25600000000 => :memory, 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) ⇒ 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) ⇒ Unit::Definition?
return the unit definition for a unit.
-
.definitions ⇒ Array
return a list of all defined units.
- .parse(input) ⇒ Unit
-
.redefine!(name, &block) {|Unit::Definition| ... } ⇒ 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)
-
#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(option = 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)
-
#to_yaml(opts = {}) ⇒ String
basically a copy of the basic to_yaml.
-
#to_yaml_properties ⇒ Array
a list of properties to emit to yaml.
- #truncate ⇒ Numeric, Unit
-
#unitless? ⇒ Boolean
returns true if no associated units false, even if the units are “unitless” like ‘radians, each, etc’.
-
#units ⇒ 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
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 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 |
# File 'lib/ruby_units/unit.rb', line 310 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 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 = Unit.parse_into_numbers_and_units([0]) unless @@cached_units.keys.include?(opt_units) || (opt_units =~ /(#{Unit.temp_regex})|(pounds|lbs[ ,]\d+ ounces|oz)|('\d+")|(ft|feet[ ,]\d+ in|inch|inches)|%|(#{TIME_REGEX})|i\s?(.+)?|±|\+\/-/) @@cached_units[opt_units] = (self.scalar == 1 ? self : opt_units.unit) if opt_units && !opt_units.empty? end end unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /#{Unit.temp_regex}/) then @@cached_units[unary_unit] = (self.scalar == 1 ? self : unary_unit.unit) end [@scalar, @numerator, @denominator, @base_scalar, @signature, @is_base].each {|x| x.freeze} return self end |
Instance Attribute Details
#base_denominator ⇒ Array
236 237 238 |
# File 'lib/ruby_units/unit.rb', line 236 def base_denominator @base_denominator end |
#base_numerator ⇒ Array
233 234 235 |
# File 'lib/ruby_units/unit.rb', line 233 def base_numerator @base_numerator end |
#base_scalar ⇒ Numeric
230 231 232 |
# File 'lib/ruby_units/unit.rb', line 230 def base_scalar @base_scalar end |
#denominator ⇒ Array
224 225 226 |
# File 'lib/ruby_units/unit.rb', line 224 def denominator @denominator end |
#numerator ⇒ Array
221 222 223 |
# File 'lib/ruby_units/unit.rb', line 221 def numerator @numerator end |
#signature ⇒ Integer
227 228 229 |
# File 'lib/ruby_units/unit.rb', line 227 def signature @signature end |
#unit_name ⇒ String
242 243 244 |
# File 'lib/ruby_units/unit.rb', line 242 def unit_name @unit_name end |
Class Method Details
.base_unit_cache ⇒ Hash
407 408 409 |
# File 'lib/ruby_units/unit.rb', line 407 def self.base_unit_cache return @@base_unit_cache end |
.cached ⇒ Hash
392 393 394 |
# File 'lib/ruby_units/unit.rb', line 392 def self.cached return @@cached_units end |
.clear_cache ⇒ true
398 399 400 401 402 403 |
# File 'lib/ruby_units/unit.rb', line 398 def self.clear_cache @@cached_units = {} @@base_unit_cache = {} Unit.new(1) return true end |
.define(unit_definition, &block) ⇒ Unit::Definition
Unpack a unit definition and add it to the array of defined units
184 185 186 187 188 189 190 191 192 |
# File 'lib/ruby_units/unit.rb', line 184 def self.define(unit_definition, &block) if block_given? raise ArgumentError, "When using the block form of Unit.define, pass the name of the unit" unless unit_definition.instance_of?(String) unit_definition = Unit::Definition.new(unit_definition, &block) end Unit.definitions[unit_definition.name] = unit_definition Unit.use_definition(unit_definition) return unit_definition end |
.defined?(unit) ⇒ Boolean
determine if a unit is already defined
152 153 154 |
# File 'lib/ruby_units/unit.rb', line 152 def self.defined?(unit) return @@UNIT_VALUES.keys.include?("<#{unit}>") end |
.definition(_unit) ⇒ Unit::Definition?
return the unit definition for a unit
159 160 161 162 |
# File 'lib/ruby_units/unit.rb', line 159 def self.definition(_unit) unit = (_unit =~ /^<.+>$/) ? _unit : "<#{_unit}>" return @@definitions[unit] end |
.definitions ⇒ Array
return a list of all defined units
166 167 168 |
# File 'lib/ruby_units/unit.rb', line 166 def self.definitions return @@definitions end |
.parse(input) ⇒ Unit
415 416 417 418 |
# File 'lib/ruby_units/unit.rb', line 415 def self.parse(input) first, second = input.scan(/(.+)\s(?:in|to|as)\s(.+)/i).first return second.nil? ? first.unit : first.unit.convert_to(second) end |
.redefine!(name, &block) {|Unit::Definition| ... } ⇒ Unit::Definition
Get the definition for a unit and allow it to be redefined
200 201 202 203 204 205 |
# File 'lib/ruby_units/unit.rb', line 200 def self.redefine!(name, &block) raise ArgumentError, "A block is required to redefine a unit" unless block_given? unit_definition = self.definition(name) yield unit_definition self.define(unit_definition) end |
.setup ⇒ true
setup internal arrays and hashes
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/ruby_units/unit.rb', line 130 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 Unit.new(1) return true end |
.undefine!(unit) ⇒ true
Undefine a unit. Will not raise an exception for unknown units.
210 211 212 213 |
# File 'lib/ruby_units/unit.rb', line 210 def self.undefine!(unit) @@definitions.delete("<#{unit}>") Unit.setup end |
Instance Method Details
#%(other) ⇒ Integer
perform a modulo on a unit, will raise an exception if the units are not compatible
819 820 821 |
# File 'lib/ruby_units/unit.rb', line 819 def %(other) return self.divmod(other).last end |
#*(other) ⇒ Unit
Multiply two units.
764 765 766 767 768 769 770 771 772 773 774 775 776 777 |
# File 'lib/ruby_units/unit.rb', line 764 def *(other) case other when Unit raise ArgumentError, "Cannot multiply by temperatures" if [other,self].any? {|x| x.is_temperature?} opts = Unit.eliminate_terms(@scalar*other.scalar, @numerator + other.numerator ,@denominator + other.denominator) opts.merge!(:signature => @signature + other.signature) return Unit.new(opts) when Numeric return 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
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 |
# File 'lib/ruby_units/unit.rb', line 837 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
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 |
# File 'lib/ruby_units/unit.rb', line 696 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? Unit.new(:scalar => (self.scalar + other.convert_to(self.temperature_scale).scalar), :numerator => @numerator, :denominator=>@denominator, :signature => @signature) else 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.unit.to_base.scalar)) 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
731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 |
# File 'lib/ruby_units/unit.rb', line 731 def -(other) case other when Unit case when self.zero? -other.dup when self =~ other case when [self, other].all? {|x| x.is_temperature?} 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? 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.unit.scalar/self.units.unit.to_base.scalar)) 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
1080 1081 1082 1083 |
# File 'lib/ruby_units/unit.rb', line 1080 def -@ return -@scalar if self.unitless? return (self.dup * -1) end |
#/(other) ⇒ Unit
Divide two units. Throws an exception if divisor is 0
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 |
# File 'lib/ruby_units/unit.rb', line 785 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 = Unit.eliminate_terms(@scalar/other.scalar, @numerator + other.denominator ,@denominator + other.numerator) opts.merge!(:signature=> @signature - other.signature) return Unit.new(opts) when Numeric raise ZeroDivisionError if other.zero? return 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.
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 |
# File 'lib/ruby_units/unit.rb', line 597 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.zero? return self.base_scalar <=> 0 when other.instance_of?(Unit) raise ArgumentError, "Incompatible Units (#{self.units} !~ #{other.units})" unless self =~ other return self.base_scalar <=> other.base_scalar else x,y = coerce(other) return x <=> y 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.
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 |
# File 'lib/ruby_units/unit.rb', line 622 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
671 672 673 674 675 676 677 678 679 680 681 682 683 |
# File 'lib/ruby_units/unit.rb', line 671 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
648 649 650 651 652 653 654 655 656 657 658 659 660 |
# File 'lib/ruby_units/unit.rb', line 648 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
1087 1088 1089 1090 |
# File 'lib/ruby_units/unit.rb', line 1087 def abs return @scalar.abs if self.unitless? return Unit.new(@scalar.abs, @numerator, @denominator) end |
#ago ⇒ Unit
1163 1164 1165 |
# File 'lib/ruby_units/unit.rb', line 1163 def ago return self.before end |
#as_json(*args) ⇒ String
Returns string formatted for json
1029 1030 1031 |
# File 'lib/ruby_units/unit.rb', line 1029 def as_json(*args) to_s end |
#before(time_point = ::Time.now) ⇒ Unit Also known as: before_now
1169 1170 1171 1172 1173 1174 1175 1176 |
# File 'lib/ruby_units/unit.rb', line 1169 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 |
#ceil ⇒ Numeric, Unit
ceil of a unit
1094 1095 1096 1097 |
# File 'lib/ruby_units/unit.rb', line 1094 def ceil return @scalar.ceil if self.unitless? return 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
1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 |
# File 'lib/ruby_units/unit.rb', line 1227 def coerce(other) if other.respond_to? :to_unit return [other.to_unit, self] end case other when Unit return [other, self] else return [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.
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 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 |
# File 'lib/ruby_units/unit.rb', line 939 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 Unit.new("#{q} #{target_unit}") else case other when Unit return self if other.units == self.units target = other when String target = Unit.new(other) else raise ArgumentError, "Unknown target units" end raise ArgumentError, "Incompatible Units" 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 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
254 255 256 257 258 259 260 261 262 263 |
# File 'lib/ruby_units/unit.rb', line 254 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
807 808 809 810 811 812 813 814 |
# File 'lib/ruby_units/unit.rb', line 807 def divmod(other) raise ArgumentError, "Incompatible Units" 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
1100 1101 1102 1103 |
# File 'lib/ruby_units/unit.rb', line 1100 def floor return @scalar.floor if self.unitless? return Unit.new(@scalar.floor, @numerator, @denominator) end |
#from(time_point) ⇒ Time, ... Also known as: after, from_now
1212 1213 1214 1215 1216 1217 1218 1219 |
# File 'lib/ruby_units/unit.rb', line 1212 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(option = nil) ⇒ String
Normally pretty prints the unit, but if you really want to see the guts of it, pass ‘:dump’
556 557 558 559 |
# File 'lib/ruby_units/unit.rb', line 556 def inspect(option=nil) return super() if option == :dump return self.to_s end |
#inverse ⇒ Unit
returns inverse of Unit (1/unit)
916 917 918 |
# File 'lib/ruby_units/unit.rb', line 916 def inverse return Unit("1") / self end |
#is_base? ⇒ Boolean Also known as: base?
Is this unit in base form?
428 429 430 431 432 433 434 |
# File 'lib/ruby_units/unit.rb', line 428 def is_base? return @is_base if defined? @is_base @is_base = (@numerator + @denominator).compact.uniq. map {|unit| 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.
571 572 573 |
# File 'lib/ruby_units/unit.rb', line 571 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
564 565 566 |
# File 'lib/ruby_units/unit.rb', line 564 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…)
386 387 388 |
# File 'lib/ruby_units/unit.rb', line 386 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
247 248 249 |
# File 'lib/ruby_units/unit.rb', line 247 def kind_of?(klass) self.scalar.kind_of?(klass) end |
#power(n) ⇒ Unit
returns the unit raised to the n-th power
866 867 868 869 870 871 872 873 874 875 876 877 |
# File 'lib/ruby_units/unit.rb', line 866 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’.unit.pred #=> ‘1 mm’.unit only works when the scalar is an integer
1131 1132 1133 1134 |
# File 'lib/ruby_units/unit.rb', line 1131 def pred raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i return 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)
886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 |
# File 'lib/ruby_units/unit.rb', line 886 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 Unit.new(:scalar=>q,:numerator=>num,:denominator=>den) end |
#round(ndigits = 0) ⇒ Numeric, Unit
1106 1107 1108 1109 |
# File 'lib/ruby_units/unit.rb', line 1106 def round(ndigits = 0) return @scalar.round(ndigits) if self.unitless? return Unit.new(@scalar.round(ndigits), @numerator, @denominator) end |
#since(time_point) ⇒ Unit
1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 |
# File 'lib/ruby_units/unit.rb', line 1183 def since(time_point) case time_point when Time return (Time.now - time_point).unit('s').convert_to(self) when DateTime, Date return (DateTime.now - time_point).unit('d').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’.unit.succ #=> ‘2 mm’.unit only works when the scalar is an integer
1121 1122 1123 1124 |
# File 'lib/ruby_units/unit.rb', line 1121 def succ raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i return Unit.new(@scalar.to_i.succ, @numerator, @denominator) end |
#temperature_scale ⇒ String
returns the ‘degree’ unit associated with a temperature unit
579 580 581 582 |
# File 'lib/ruby_units/unit.rb', line 579 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
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 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 |
# File 'lib/ruby_units/unit.rb', line 441 def to_base return self if self.is_base? if @@UNIT_MAP[self.units] =~ /\A<(?:temp|deg)[CRF]>\Z/ if RUBY_VERSION < "1.9" # :nocov_19: @signature = @@KINDS.index(:temperature) # :nocov_19: else #:nocov: @signature = @@KINDS.key(:temperature) #:nocov: end 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= Unit.new(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
1005 1006 1007 1008 |
# File 'lib/ruby_units/unit.rb', line 1005 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
1151 1152 1153 |
# File 'lib/ruby_units/unit.rb', line 1151 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
1146 1147 1148 |
# File 'lib/ruby_units/unit.rb', line 1146 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
997 998 999 1000 |
# File 'lib/ruby_units/unit.rb', line 997 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
1013 1014 1015 1016 |
# File 'lib/ruby_units/unit.rb', line 1013 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
1022 1023 1024 1025 |
# File 'lib/ruby_units/unit.rb', line 1022 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
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 |
# File 'lib/ruby_units/unit.rb', line 511 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 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 "#{@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.
1138 1139 1140 |
# File 'lib/ruby_units/unit.rb', line 1138 def to_time return Time.at(self) end |
#to_unit ⇒ Unit Also known as: unit
421 422 423 |
# File 'lib/ruby_units/unit.rb', line 421 def to_unit self end |
#to_yaml(opts = {}) ⇒ String
basically a copy of the basic to_yaml. Needed because otherwise it ends up coercing the object to a string before YAML’izing it.
278 279 280 281 282 283 284 285 286 |
# File 'lib/ruby_units/unit.rb', line 278 def to_yaml( opts = {} ) YAML::quick_emit( object_id, opts ) do |out| out.map( taguri, to_yaml_style ) do |map| for m in to_yaml_properties do map.add( m[1..-1], instance_variable_get( m ) ) end end end end |
#to_yaml_properties ⇒ Array
a list of properties to emit to yaml
270 271 272 |
# File 'lib/ruby_units/unit.rb', line 270 def to_yaml_properties %w{@scalar @numerator @denominator @signature @base_scalar} end |
#truncate ⇒ Numeric, Unit
1112 1113 1114 1115 |
# File 'lib/ruby_units/unit.rb', line 1112 def truncate return @scalar.truncate if self.unitless? return 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’
587 588 589 |
# File 'lib/ruby_units/unit.rb', line 587 def unitless? return(@numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY) end |
#units ⇒ String
returns the ‘unit’ part of the Unit object without the scalar
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 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 |
# File 'lib/ruby_units/unit.rb', line 1035 def units return "" if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY return @unit_name unless @unit_name.nil? output_numerator = [] output_denominator = [] num = @numerator.clone.compact den = @denominator.clone.compact if @numerator == UNITY_ARRAY output_numerator << "1" else while defn = Unit.definition(num.shift) do if defn && defn.prefix? output_numerator << defn.display_name + Unit.definition(num.shift).display_name else output_numerator << defn.display_name end end end if @denominator == UNITY_ARRAY output_denominator = [] else while defn = Unit.definition(den.shift) do if defn && defn.prefix? output_denominator << defn.display_name + Unit.definition(den.shift).display_name 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 @unit_name = out unless self.kind == :temperature return out end |
#until(time_point) ⇒ Unit
1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 |
# File 'lib/ruby_units/unit.rb', line 1197 def until(time_point) case time_point when Time return (time_point - Time.now).unit('s').convert_to(self) when DateTime, Date return (time_point - DateTime.now).unit('d').convert_to(self) else raise ArgumentError, "Must specify a Time, Date, or DateTime" end end |
#zero? ⇒ Boolean
true if scalar is zero
1157 1158 1159 |
# File 'lib/ruby_units/unit.rb', line 1157 def zero? return self.base_scalar.zero? end |