ruby-units 1.2.0 → 1.3.0.a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/TODO +2 -3
- data/spec/ruby-units/array_spec.rb +14 -0
- data/spec/ruby-units/complex_spec.rb +37 -0
- data/spec/ruby-units/date_spec.rb +38 -0
- data/spec/ruby-units/math_spec.rb +63 -0
- data/spec/ruby-units/numeric_spec.rb +12 -0
- data/spec/ruby-units/object_spec.rb +7 -0
- data/spec/ruby-units/string_spec.rb +65 -0
- data/spec/ruby-units/time_spec.rb +28 -0
- data/spec/ruby-units/unit_spec.rb +855 -0
- data/spec/spec_helper.rb +4 -0
- data/test/test_cache.rb +26 -0
- data/test/test_ruby-units.rb +40 -11
- metadata +49 -59
- data/CHANGELOG.txt +0 -206
- data/Gemfile +0 -7
- data/Manifest.txt +0 -19
- data/RakeFile +0 -40
- data/VERSION +0 -1
- data/lib/ruby-units.rb +0 -13
- data/lib/ruby_units.rb +0 -13
- data/lib/ruby_units/array.rb +0 -9
- data/lib/ruby_units/cmath.rb +0 -2
- data/lib/ruby_units/complex.rb +0 -10
- data/lib/ruby_units/date.rb +0 -57
- data/lib/ruby_units/math.rb +0 -101
- data/lib/ruby_units/numeric.rb +0 -8
- data/lib/ruby_units/object.rb +0 -8
- data/lib/ruby_units/ruby-units.rb +0 -1161
- data/lib/ruby_units/string.rb +0 -118
- data/lib/ruby_units/time.rb +0 -73
- data/lib/ruby_units/units.rb +0 -248
- data/lib/ruby_units/version.rb +0 -5
- data/ruby-units.gemspec +0 -73
data/Gemfile
DELETED
data/Manifest.txt
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
CHANGELOG.txt
|
2
|
-
Manifest.txt
|
3
|
-
README.md
|
4
|
-
LICENSE.txt
|
5
|
-
Rakefile
|
6
|
-
lib/ruby-units.rb
|
7
|
-
lib/ruby_units.rb
|
8
|
-
lib/ruby_units/units.rb
|
9
|
-
lib/ruby_units/math.rb
|
10
|
-
lib/ruby_units/date.rb
|
11
|
-
lib/ruby_units/time.rb
|
12
|
-
lib/ruby_units/string.rb
|
13
|
-
lib/ruby_units/array.rb
|
14
|
-
lib/ruby_units/numeric.rb
|
15
|
-
lib/ruby_units/object.rb
|
16
|
-
lib/ruby_units/array.rb
|
17
|
-
lib/ruby_units/complex.rb
|
18
|
-
lib/ruby_units/ruby-units.rb
|
19
|
-
test/test_ruby-units.rb
|
data/RakeFile
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
require 'rake/testtask'
|
4
|
-
require './lib/ruby-units'
|
5
|
-
|
6
|
-
begin
|
7
|
-
require 'jeweler'
|
8
|
-
Jeweler::Tasks.new do |gem|
|
9
|
-
gem.name = "ruby-units"
|
10
|
-
gem.summary = %Q{A class that performs unit conversions and unit math}
|
11
|
-
gem.description = %Q{Provides classes and methods to perform unit math and conversions}
|
12
|
-
gem.authors = ["Kevin Olbrich, Ph.D."]
|
13
|
-
gem.email = ["kevin.olbrich+ruby_units@gmail.com"]
|
14
|
-
gem.homepage = "https://github.com/olbrich/ruby-units"
|
15
|
-
gem.has_rdoc = true
|
16
|
-
gem.files.exclude(".autotest")
|
17
|
-
end
|
18
|
-
Jeweler::GemcutterTasks.new
|
19
|
-
rescue LoadError
|
20
|
-
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
-
end
|
22
|
-
|
23
|
-
begin
|
24
|
-
require 'rcov/rcovtask'
|
25
|
-
desc "Generate code coverage"
|
26
|
-
Rcov::RcovTask.new do |t|
|
27
|
-
t.test_files = FileList['test/test*.rb']
|
28
|
-
#t.verbose = true # uncomment to see the executed command
|
29
|
-
end
|
30
|
-
rescue
|
31
|
-
end
|
32
|
-
|
33
|
-
desc "Run unit tests"
|
34
|
-
Rake::TestTask.new do |t|
|
35
|
-
t.libs << "test"
|
36
|
-
t.test_files = FileList['test/test*.rb']
|
37
|
-
# t.verbose = true
|
38
|
-
end
|
39
|
-
|
40
|
-
task :default => :test
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
1.2.0
|
data/lib/ruby-units.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
-
require "ruby_units/version"
|
3
|
-
require 'ruby_units/array'
|
4
|
-
require 'ruby_units/date'
|
5
|
-
require 'ruby_units/time'
|
6
|
-
require 'ruby_units/math'
|
7
|
-
require 'ruby_units/complex'
|
8
|
-
require 'ruby_units/numeric'
|
9
|
-
require 'ruby_units/object'
|
10
|
-
require 'ruby_units/string'
|
11
|
-
require 'ruby_units/units'
|
12
|
-
require 'ruby_units/ruby-units'
|
13
|
-
|
data/lib/ruby_units.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
-
require "ruby_units/version"
|
3
|
-
require 'ruby_units/array'
|
4
|
-
require 'ruby_units/date'
|
5
|
-
require 'ruby_units/time'
|
6
|
-
require 'ruby_units/math'
|
7
|
-
require 'ruby_units/complex'
|
8
|
-
require 'ruby_units/numeric'
|
9
|
-
require 'ruby_units/object'
|
10
|
-
require 'ruby_units/string'
|
11
|
-
require 'ruby_units/units'
|
12
|
-
require 'ruby_units/ruby-units'
|
13
|
-
|
data/lib/ruby_units/array.rb
DELETED
data/lib/ruby_units/cmath.rb
DELETED
data/lib/ruby_units/complex.rb
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
class Complex < Numeric
|
2
|
-
def to_unit(other = nil)
|
3
|
-
real_unit = self.real.to_unit
|
4
|
-
image_unit = self.imaginary.to_unit
|
5
|
-
raise ArgumentError, 'Units on real and imaginary parts are incompatible' unless real_unit =~ image_unit
|
6
|
-
final_unit = (real_unit.units.empty? ? image_unit.units : real_unit.units).to_unit
|
7
|
-
final_unit * Complex(real_unit.to(final_unit).scalar, image_unit.to(final_unit).scalar)
|
8
|
-
end
|
9
|
-
alias :unit :to_unit
|
10
|
-
end
|
data/lib/ruby_units/date.rb
DELETED
@@ -1,57 +0,0 @@
|
|
1
|
-
# Allow date objects to do offsets by a time unit
|
2
|
-
# Date.today + U"1 week" => gives today+1 week
|
3
|
-
|
4
|
-
require 'date'
|
5
|
-
|
6
|
-
class Date
|
7
|
-
alias :unit_date_add :+
|
8
|
-
def +(unit)
|
9
|
-
case unit
|
10
|
-
when Unit
|
11
|
-
unit = unit.to('d').round if ['y', 'decade', 'century'].include? unit.units
|
12
|
-
unit_date_add(unit.to('day').scalar)
|
13
|
-
when Time
|
14
|
-
unit_date_add(unit.to_datetime)
|
15
|
-
else
|
16
|
-
unit_date_add(unit)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
|
21
|
-
alias :unit_date_sub :-
|
22
|
-
def -(unit)
|
23
|
-
case unit
|
24
|
-
when Unit
|
25
|
-
unit = unit.to('d').round if ['y', 'decade', 'century'].include? unit.units
|
26
|
-
unit_date_sub(unit.to('day').scalar)
|
27
|
-
when Time
|
28
|
-
unit_date_sub(unit.to_datetime)
|
29
|
-
else
|
30
|
-
unit_date_sub(unit)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def to_unit(other = nil)
|
35
|
-
other ? Unit.new(self).to(other) : Unit.new(self)
|
36
|
-
end
|
37
|
-
alias :unit :to_unit
|
38
|
-
|
39
|
-
unless Date.instance_methods.include?(:to_time)
|
40
|
-
def to_time
|
41
|
-
Time.local(*ParseDate.parsedate(self.to_s))
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
alias :units_datetime_inspect :inspect
|
46
|
-
def inspect(raw = false)
|
47
|
-
return self.units_datetime_inspect if raw
|
48
|
-
self.to_s
|
49
|
-
end
|
50
|
-
|
51
|
-
unless Date.instance_methods.include?(:to_date)
|
52
|
-
def to_date
|
53
|
-
Date.civil(self.year, self.month, self.day)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
end
|
data/lib/ruby_units/math.rb
DELETED
@@ -1,101 +0,0 @@
|
|
1
|
-
# Math will convert unit objects to radians and then attempt to use the value for
|
2
|
-
# trigonometric functions.
|
3
|
-
require 'mathn'
|
4
|
-
|
5
|
-
module Math
|
6
|
-
|
7
|
-
alias :unit_sqrt :sqrt
|
8
|
-
def sqrt(n)
|
9
|
-
if Unit === n
|
10
|
-
(n**(Rational(1,2))).to_unit
|
11
|
-
else
|
12
|
-
unit_sqrt(n)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
module_function :unit_sqrt
|
16
|
-
module_function :sqrt
|
17
|
-
|
18
|
-
if self.respond_to?(:cbrt)
|
19
|
-
alias :unit_cbrt :cbrt
|
20
|
-
def cbrt(n)
|
21
|
-
if Unit === n
|
22
|
-
(n**(Rational(1,3))).to_unit
|
23
|
-
else
|
24
|
-
unit_cbrt(n)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
module_function :unit_cbrt
|
28
|
-
module_function :cbrt
|
29
|
-
end
|
30
|
-
|
31
|
-
alias :unit_sin :sin
|
32
|
-
def sin(n)
|
33
|
-
Unit === n ? unit_sin(n.to('radian').scalar) : unit_sin(n)
|
34
|
-
end
|
35
|
-
module_function :unit_sin
|
36
|
-
module_function :sin
|
37
|
-
|
38
|
-
alias :unit_cos :cos
|
39
|
-
def cos(n)
|
40
|
-
Unit === n ? unit_cos(n.to('radian').scalar) : unit_cos(n)
|
41
|
-
end
|
42
|
-
module_function :unit_cos
|
43
|
-
module_function :cos
|
44
|
-
|
45
|
-
alias :unit_sinh :sinh
|
46
|
-
def sinh(n)
|
47
|
-
Unit === n ? unit_sinh(n.to('radian').scalar) : unit_sinh(n)
|
48
|
-
end
|
49
|
-
module_function :unit_sinh
|
50
|
-
module_function :sinh
|
51
|
-
|
52
|
-
alias :unit_cosh :cosh
|
53
|
-
def cosh(n)
|
54
|
-
Unit === n ? unit_cosh(n.to('radian').scalar) : unit_cosh(n)
|
55
|
-
end
|
56
|
-
module_function :unit_cosh
|
57
|
-
module_function :cosh
|
58
|
-
|
59
|
-
alias :unit_tan :tan
|
60
|
-
def tan(n)
|
61
|
-
Unit === n ? unit_tan(n.to('radian').scalar) : unit_tan(n)
|
62
|
-
end
|
63
|
-
module_function :tan
|
64
|
-
module_function :unit_tan
|
65
|
-
|
66
|
-
alias :unit_tanh :tanh
|
67
|
-
def tanh(n)
|
68
|
-
Unit === n ? unit_tanh(n.to('radian').scalar) : unit_tanh(n)
|
69
|
-
end
|
70
|
-
module_function :unit_tanh
|
71
|
-
module_function :tanh
|
72
|
-
|
73
|
-
alias :unit_hypot :hypot
|
74
|
-
# Convert parameters to consistent units and perform the function
|
75
|
-
def hypot(x,y)
|
76
|
-
if Unit === x && Unit === y
|
77
|
-
(x**2 + y**2)**(1/2)
|
78
|
-
else
|
79
|
-
unit_hypot(x,y)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
module_function :unit_hypot
|
83
|
-
module_function :hypot
|
84
|
-
|
85
|
-
alias :unit_atan2 :atan2
|
86
|
-
def atan2(x,y)
|
87
|
-
case
|
88
|
-
when (Unit === x && Unit === y) && (x !~ y)
|
89
|
-
raise ArgumentError, "Incompatible Units"
|
90
|
-
when (Unit === x && Unit === y) && (x =~ y)
|
91
|
-
Math::unit_atan2(x.base_scalar, y.base_scalar)
|
92
|
-
when (Unit === x || Unit === y)
|
93
|
-
raise ArgumentError, "Incompatible Units"
|
94
|
-
else
|
95
|
-
Math::unit_atan2(x,y)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
module_function :unit_atan2
|
99
|
-
module_function :atan2
|
100
|
-
|
101
|
-
end
|
data/lib/ruby_units/numeric.rb
DELETED
data/lib/ruby_units/object.rb
DELETED
@@ -1,1161 +0,0 @@
|
|
1
|
-
require 'date'
|
2
|
-
if RUBY_VERSION < "1.9"
|
3
|
-
require 'parsedate'
|
4
|
-
require 'rational'
|
5
|
-
end
|
6
|
-
# = Ruby Units
|
7
|
-
#
|
8
|
-
# Copyright 2006-2010 by Kevin C. Olbrich, Ph.D.
|
9
|
-
#
|
10
|
-
# See http://rubyforge.org/ruby-units/
|
11
|
-
#
|
12
|
-
# http://www.sciwerks.org
|
13
|
-
#
|
14
|
-
# mailto://kevin.olbrich+ruby-units@gmail.com
|
15
|
-
#
|
16
|
-
# See README for detailed usage instructions and examples
|
17
|
-
#
|
18
|
-
# ==Unit Definition Format
|
19
|
-
#
|
20
|
-
# '<name>' => [%w{prefered_name synonyms}, conversion_to_base, :classification, %w{<base> <units> <in> <numerator>} , %w{<base> <units> <in> <denominator>} ],
|
21
|
-
#
|
22
|
-
# Prefixes (e.g., a :prefix classification) get special handling
|
23
|
-
# Note: The accuracy of unit conversions depends on the precision of the conversion factor.
|
24
|
-
# If you have more accurate estimates for particular conversion factors, please send them
|
25
|
-
# to me and I will incorporate them into the next release. It is also incumbent on the end-user
|
26
|
-
# to ensure that the accuracy of any conversions is sufficient for their intended application.
|
27
|
-
#
|
28
|
-
# While there are a large number of unit specified in the base package,
|
29
|
-
# there are also a large number of units that are not included.
|
30
|
-
# This package covers nearly all SI, Imperial, and units commonly used
|
31
|
-
# in the United States. If your favorite units are not listed here, send me an email
|
32
|
-
#
|
33
|
-
# To add / override a unit definition, add a code block like this..
|
34
|
-
#
|
35
|
-
# class Unit < Numeric
|
36
|
-
# @@USER_DEFINITIONS = {
|
37
|
-
# <name>' => [%w{prefered_name synonyms}, conversion_to_base, :classification, %w{<base> <units> <in> <numerator>} , %w{<base> <units> <in> <denominator>} ]
|
38
|
-
# }
|
39
|
-
# end
|
40
|
-
# Unit.setup
|
41
|
-
class Unit < Numeric
|
42
|
-
# pre-generate hashes from unit definitions for performance.
|
43
|
-
VERSION = Unit::Version::STRING
|
44
|
-
@@USER_DEFINITIONS = {}
|
45
|
-
@@PREFIX_VALUES = {}
|
46
|
-
@@PREFIX_MAP = {}
|
47
|
-
@@UNIT_MAP = {}
|
48
|
-
@@UNIT_VALUES = {}
|
49
|
-
@@OUTPUT_MAP = {}
|
50
|
-
@@BASE_UNITS = ['<meter>','<kilogram>','<second>','<mole>', '<farad>', '<ampere>','<radian>','<kelvin>','<temp-K>','<byte>','<dollar>','<candela>','<each>','<steradian>','<decibel>']
|
51
|
-
UNITY = '<1>'
|
52
|
-
UNITY_ARRAY= [UNITY]
|
53
|
-
FEET_INCH_REGEX = /(\d+)\s*(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inches)/
|
54
|
-
TIME_REGEX = /(\d+)*:(\d+)*:*(\d+)*[:,]*(\d+)*/
|
55
|
-
LBS_OZ_REGEX = /(\d+)\s*(?:#|lbs|pounds|pound-mass)+[\s,]*(\d+)\s*(?:oz|ounces)/
|
56
|
-
SCI_NUMBER = %r{([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)}
|
57
|
-
RATIONAL_NUMBER = /(\d+)\/(\d+)/
|
58
|
-
COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/
|
59
|
-
NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/
|
60
|
-
UNIT_STRING_REGEX = /#{SCI_NUMBER}*\s*([^\/]*)\/*(.+)*/
|
61
|
-
TOP_REGEX = /([^ \*]+)(?:\^|\*\*)([\d-]+)/
|
62
|
-
BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/
|
63
|
-
UNCERTAIN_REGEX = /#{SCI_NUMBER}\s*\+\/-\s*#{SCI_NUMBER}\s(.+)/
|
64
|
-
COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/
|
65
|
-
RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(.+)?/
|
66
|
-
KELVIN = ['<kelvin>']
|
67
|
-
FAHRENHEIT = ['<fahrenheit>']
|
68
|
-
RANKINE = ['<rankine>']
|
69
|
-
CELSIUS = ['<celsius>']
|
70
|
-
|
71
|
-
SIGNATURE_VECTOR = [:length, :time, :temperature, :mass, :current, :substance, :luminosity, :currency, :memory, :angle, :capacitance]
|
72
|
-
@@KINDS = {
|
73
|
-
-312058=>:resistance,
|
74
|
-
-312038=>:inductance,
|
75
|
-
-152040=>:magnetism,
|
76
|
-
-152038=>:magnetism,
|
77
|
-
-152058=>:potential,
|
78
|
-
-39=>:acceleration,
|
79
|
-
-38=>:radiation,
|
80
|
-
-20=>:frequency,
|
81
|
-
-19=>:speed,
|
82
|
-
-18=>:viscosity,
|
83
|
-
0=>:unitless,
|
84
|
-
1=>:length,
|
85
|
-
2=>:area,
|
86
|
-
3=>:volume,
|
87
|
-
20=>:time,
|
88
|
-
400=>:temperature,
|
89
|
-
7942=>:power,
|
90
|
-
7959=>:pressure,
|
91
|
-
7962=>:energy,
|
92
|
-
7979=>:viscosity,
|
93
|
-
7961=>:force,
|
94
|
-
7997=>:mass_concentration,
|
95
|
-
8000=>:mass,
|
96
|
-
159999=>:magnetism,
|
97
|
-
160000=>:current,
|
98
|
-
160020=>:charge,
|
99
|
-
312058=>:resistance,
|
100
|
-
3199980=>:activity,
|
101
|
-
3199997=>:molar_concentration,
|
102
|
-
3200000=>:substance,
|
103
|
-
63999998=>:illuminance,
|
104
|
-
64000000=>:luminous_power,
|
105
|
-
1280000000=>:currency,
|
106
|
-
25600000000=>:memory,
|
107
|
-
511999999980=>:angular_velocity,
|
108
|
-
512000000000=>:angle,
|
109
|
-
10240000000000=>:capacitance,
|
110
|
-
}
|
111
|
-
|
112
|
-
@@cached_units = {}
|
113
|
-
@@base_unit_cache = {}
|
114
|
-
|
115
|
-
def self.setup
|
116
|
-
@@ALL_UNIT_DEFINITIONS = UNIT_DEFINITIONS.merge!(@@USER_DEFINITIONS)
|
117
|
-
for unit in (@@ALL_UNIT_DEFINITIONS) do
|
118
|
-
key, value = unit
|
119
|
-
if value[2] == :prefix then
|
120
|
-
@@PREFIX_VALUES[key]=value[1]
|
121
|
-
for name in value[0] do
|
122
|
-
@@PREFIX_MAP[name]=key
|
123
|
-
end
|
124
|
-
else
|
125
|
-
@@UNIT_VALUES[key]={}
|
126
|
-
@@UNIT_VALUES[key][:scalar]=value[1]
|
127
|
-
@@UNIT_VALUES[key][:numerator]=value[3] if value[3]
|
128
|
-
@@UNIT_VALUES[key][:denominator]=value[4] if value[4]
|
129
|
-
for name in value[0] do
|
130
|
-
@@UNIT_MAP[name]=key
|
131
|
-
end
|
132
|
-
end
|
133
|
-
@@OUTPUT_MAP[key]=value[0][0]
|
134
|
-
end
|
135
|
-
@@PREFIX_REGEX = @@PREFIX_MAP.keys.sort_by {|prefix| [prefix.length, prefix]}.reverse.join('|')
|
136
|
-
@@UNIT_REGEX = @@UNIT_MAP.keys.sort_by {|unit_name| [unit_name.length, unit]}.reverse.join('|')
|
137
|
-
@@UNIT_MATCH_REGEX = /(#{@@PREFIX_REGEX})*?(#{@@UNIT_REGEX})\b/
|
138
|
-
Unit.new(1)
|
139
|
-
end
|
140
|
-
|
141
|
-
include Comparable
|
142
|
-
attr_accessor :scalar, :numerator, :denominator, :signature, :base_scalar, :base_numerator, :base_denominator, :output, :unit_name
|
143
|
-
|
144
|
-
def to_yaml_properties
|
145
|
-
%w{@scalar @numerator @denominator @signature @base_scalar}
|
146
|
-
end
|
147
|
-
|
148
|
-
# needed to make complex units play nice -- otherwise not detected as a complex_generic
|
149
|
-
|
150
|
-
def kind_of?(klass)
|
151
|
-
self.scalar.kind_of?(klass)
|
152
|
-
end
|
153
|
-
|
154
|
-
def copy(from)
|
155
|
-
@scalar = from.scalar
|
156
|
-
@numerator = from.numerator
|
157
|
-
@denominator = from.denominator
|
158
|
-
@is_base = from.is_base?
|
159
|
-
@signature = from.signature
|
160
|
-
@base_scalar = from.base_scalar
|
161
|
-
@unit_name = from.unit_name rescue nil
|
162
|
-
end
|
163
|
-
|
164
|
-
# basically a copy of the basic to_yaml. Needed because otherwise it ends up coercing the object to a string
|
165
|
-
# before YAML'izing it.
|
166
|
-
if RUBY_VERSION < "1.9"
|
167
|
-
def to_yaml( opts = {} )
|
168
|
-
YAML::quick_emit( object_id, opts ) do |out|
|
169
|
-
out.map( taguri, to_yaml_style ) do |map|
|
170
|
-
for m in to_yaml_properties do
|
171
|
-
map.add( m[1..-1], instance_variable_get( m ) )
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
# Create a new Unit object. Can be initialized using a string, or a hash
|
179
|
-
# Valid formats include:
|
180
|
-
# "5.6 kg*m/s^2"
|
181
|
-
# "5.6 kg*m*s^-2"
|
182
|
-
# "5.6 kilogram*meter*second^-2"
|
183
|
-
# "2.2 kPa"
|
184
|
-
# "37 degC"
|
185
|
-
# "1" -- creates a unitless constant with value 1
|
186
|
-
# "GPa" -- creates a unit with scalar 1 with units 'GPa'
|
187
|
-
# 6'4" -- recognized as 6 feet + 4 inches
|
188
|
-
# 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
|
189
|
-
#
|
190
|
-
def initialize(*options)
|
191
|
-
@scalar = nil
|
192
|
-
@base_scalar = nil
|
193
|
-
@unit_name = nil
|
194
|
-
@signature = nil
|
195
|
-
@output = {}
|
196
|
-
if options.size == 2
|
197
|
-
begin
|
198
|
-
cached = @@cached_units[options[1]] * options[0]
|
199
|
-
copy(cached)
|
200
|
-
rescue
|
201
|
-
initialize("#{options[0]} #{(options[1].units rescue options[1])}")
|
202
|
-
end
|
203
|
-
return
|
204
|
-
end
|
205
|
-
if options.size == 3
|
206
|
-
options[1] = options[1].join if options[1].kind_of?(Array)
|
207
|
-
options[2] = options[2].join if options[2].kind_of?(Array)
|
208
|
-
begin
|
209
|
-
cached = @@cached_units["#{options[1]}/#{options[2]}"] * options[0]
|
210
|
-
copy(cached)
|
211
|
-
rescue
|
212
|
-
initialize("#{options[0]} #{options[1]}/#{options[2]}")
|
213
|
-
end
|
214
|
-
return
|
215
|
-
end
|
216
|
-
|
217
|
-
case options[0]
|
218
|
-
when Hash
|
219
|
-
@scalar = options[0][:scalar] || 1
|
220
|
-
@numerator = options[0][:numerator] || UNITY_ARRAY
|
221
|
-
@denominator = options[0][:denominator] || UNITY_ARRAY
|
222
|
-
@signature = options[0][:signature]
|
223
|
-
when Array
|
224
|
-
initialize(*options[0])
|
225
|
-
return
|
226
|
-
when Numeric
|
227
|
-
@scalar = options[0]
|
228
|
-
@numerator = @denominator = UNITY_ARRAY
|
229
|
-
when Time
|
230
|
-
@scalar = options[0].to_f
|
231
|
-
@numerator = ['<second>']
|
232
|
-
@denominator = UNITY_ARRAY
|
233
|
-
when DateTime
|
234
|
-
@scalar = options[0].ajd
|
235
|
-
@numerator = ['<day>']
|
236
|
-
@denominator = UNITY_ARRAY
|
237
|
-
when ""
|
238
|
-
raise ArgumentError, "No Unit Specified"
|
239
|
-
when String
|
240
|
-
parse(options[0])
|
241
|
-
else
|
242
|
-
raise ArgumentError, "Invalid Unit Format"
|
243
|
-
end
|
244
|
-
self.update_base_scalar
|
245
|
-
raise ArgumentError, "Temperature out of range" if self.is_temperature? && self.base_scalar < 0
|
246
|
-
|
247
|
-
unary_unit = self.units || ""
|
248
|
-
opt_units = options[0].scan(NUMBER_REGEX)[0][1] if String === options[0]
|
249
|
-
unless @@cached_units.keys.include?(opt_units) || (opt_units =~ /(temp|deg(C|K|R|F))|(pounds|lbs[ ,]\d+ ounces|oz)|('\d+")|(ft|feet[ ,]\d+ in|inch|inches)|%|(#{TIME_REGEX})|i\s?(.+)?|±|\+\/-/)
|
250
|
-
@@cached_units[opt_units] = (self.scalar == 1 ? self : opt_units.unit) if opt_units && !opt_units.empty?
|
251
|
-
end
|
252
|
-
unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /(temp|deg)(C|K|R|F)/) then
|
253
|
-
@@cached_units[unary_unit] = (self.scalar == 1 ? self : unary_unit.unit)
|
254
|
-
end
|
255
|
-
[@scalar, @numerator, @denominator, @base_scalar, @signature, @is_base].each {|x| x.freeze}
|
256
|
-
self
|
257
|
-
end
|
258
|
-
|
259
|
-
def kind
|
260
|
-
return @@KINDS[self.signature]
|
261
|
-
end
|
262
|
-
|
263
|
-
def self.cached
|
264
|
-
return @@cached_units
|
265
|
-
end
|
266
|
-
|
267
|
-
def self.clear_cache
|
268
|
-
@@cached_units = {}
|
269
|
-
@@base_unit_cache = {}
|
270
|
-
Unit.new(1)
|
271
|
-
end
|
272
|
-
|
273
|
-
def self.base_unit_cache
|
274
|
-
return @@base_unit_cache
|
275
|
-
end
|
276
|
-
|
277
|
-
def self.parse(input)
|
278
|
-
first, second = input.scan(/(.+)\s(?:in|to|as)\s(.+)/i).first
|
279
|
-
second.nil? ? first.unit : first.unit.to(second)
|
280
|
-
end
|
281
|
-
|
282
|
-
def to_unit
|
283
|
-
self
|
284
|
-
end
|
285
|
-
alias :unit :to_unit
|
286
|
-
|
287
|
-
# Returns 'true' if the Unit is represented in base units
|
288
|
-
def is_base?
|
289
|
-
return @is_base if defined? @is_base
|
290
|
-
return @is_base=true if self.degree? && self.numerator.size == 1 && self.denominator == UNITY_ARRAY && self.units =~ /(deg|temp)K/
|
291
|
-
n = @numerator + @denominator
|
292
|
-
for x in n.compact do
|
293
|
-
return @is_base=false unless x == UNITY || (@@BASE_UNITS.include?((x)))
|
294
|
-
end
|
295
|
-
return @is_base = true
|
296
|
-
end
|
297
|
-
|
298
|
-
# convert to base SI units
|
299
|
-
# results of the conversion are cached so subsequent calls to this will be fast
|
300
|
-
def to_base
|
301
|
-
return self if self.is_base?
|
302
|
-
if self.units =~ /\A(deg|temp)(C|F|K|C)\Z/
|
303
|
-
@signature = 400
|
304
|
-
base = case self.units
|
305
|
-
when /temp/
|
306
|
-
self.to('tempK')
|
307
|
-
when /deg/
|
308
|
-
self.to('degK')
|
309
|
-
end
|
310
|
-
return base
|
311
|
-
end
|
312
|
-
|
313
|
-
cached = ((@@base_unit_cache[self.units] * self.scalar) rescue nil)
|
314
|
-
return cached if cached
|
315
|
-
|
316
|
-
num = []
|
317
|
-
den = []
|
318
|
-
q = 1
|
319
|
-
for unit in @numerator.compact do
|
320
|
-
if @@PREFIX_VALUES[unit]
|
321
|
-
q *= @@PREFIX_VALUES[unit]
|
322
|
-
else
|
323
|
-
q *= @@UNIT_VALUES[unit][:scalar] if @@UNIT_VALUES[unit]
|
324
|
-
num << @@UNIT_VALUES[unit][:numerator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:numerator]
|
325
|
-
den << @@UNIT_VALUES[unit][:denominator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:denominator]
|
326
|
-
end
|
327
|
-
end
|
328
|
-
for unit in @denominator.compact do
|
329
|
-
if @@PREFIX_VALUES[unit]
|
330
|
-
q /= @@PREFIX_VALUES[unit]
|
331
|
-
else
|
332
|
-
q /= @@UNIT_VALUES[unit][:scalar] if @@UNIT_VALUES[unit]
|
333
|
-
den << @@UNIT_VALUES[unit][:numerator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:numerator]
|
334
|
-
num << @@UNIT_VALUES[unit][:denominator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:denominator]
|
335
|
-
end
|
336
|
-
end
|
337
|
-
|
338
|
-
num = num.flatten.compact
|
339
|
-
den = den.flatten.compact
|
340
|
-
num = UNITY_ARRAY if num.empty?
|
341
|
-
base= Unit.new(Unit.eliminate_terms(q,num,den))
|
342
|
-
@@base_unit_cache[self.units]=base
|
343
|
-
return base * @scalar
|
344
|
-
end
|
345
|
-
|
346
|
-
# Generate human readable output.
|
347
|
-
# If the name of a unit is passed, the unit will first be converted to the target unit before output.
|
348
|
-
# some named conversions are available
|
349
|
-
#
|
350
|
-
# :ft - outputs in feet and inches (e.g., 6'4")
|
351
|
-
# :lbs - outputs in pounds and ounces (e.g, 8 lbs, 8 oz)
|
352
|
-
#
|
353
|
-
# You can also pass a standard format string (i.e., '%0.2f')
|
354
|
-
# or a strftime format string.
|
355
|
-
#
|
356
|
-
# output is cached so subsequent calls for the same format will be fast
|
357
|
-
#
|
358
|
-
def to_s(target_units=nil)
|
359
|
-
out = @output[target_units]
|
360
|
-
if out
|
361
|
-
return out
|
362
|
-
else
|
363
|
-
case target_units
|
364
|
-
when :ft
|
365
|
-
inches = self.to("in").scalar.to_int
|
366
|
-
out = "#{(inches / 12).truncate}\'#{(inches % 12).round}\""
|
367
|
-
when :lbs
|
368
|
-
ounces = self.to("oz").scalar.to_int
|
369
|
-
out = "#{(ounces / 16).truncate} lbs, #{(ounces % 16).round} oz"
|
370
|
-
when String
|
371
|
-
out = case target_units
|
372
|
-
when /(%[\-+\.\w#]+)\s*(.+)*/ #format string like '%0.2f in'
|
373
|
-
begin
|
374
|
-
if $2 #unit specified, need to convert
|
375
|
-
self.to($2).to_s($1)
|
376
|
-
else
|
377
|
-
"#{$1 % @scalar} #{$2 || self.units}".strip
|
378
|
-
end
|
379
|
-
rescue
|
380
|
-
(DateTime.new(0) + self).strftime(target_units)
|
381
|
-
end
|
382
|
-
when /(\S+)/ #unit only 'mm' or '1/mm'
|
383
|
-
"#{self.to($1).to_s}"
|
384
|
-
else
|
385
|
-
raise "unhandled case"
|
386
|
-
end
|
387
|
-
else
|
388
|
-
out = case @scalar
|
389
|
-
when Rational
|
390
|
-
"#{@scalar} #{self.units}"
|
391
|
-
else
|
392
|
-
"#{'%g' % @scalar} #{self.units}"
|
393
|
-
end.strip
|
394
|
-
end
|
395
|
-
@output[target_units] = out
|
396
|
-
return out
|
397
|
-
end
|
398
|
-
end
|
399
|
-
|
400
|
-
# Normally pretty prints the unit, but if you really want to see the guts of it, pass ':dump'
|
401
|
-
def inspect(option=nil)
|
402
|
-
return super() if option == :dump
|
403
|
-
self.to_s
|
404
|
-
end
|
405
|
-
|
406
|
-
# true if unit is a 'temperature', false if a 'degree' or anything else
|
407
|
-
def is_temperature?
|
408
|
-
self.is_degree? && self.units =~ /temp/
|
409
|
-
end
|
410
|
-
alias :temperature? :is_temperature?
|
411
|
-
|
412
|
-
# true if a degree unit or equivalent.
|
413
|
-
def is_degree?
|
414
|
-
self.kind == :temperature
|
415
|
-
end
|
416
|
-
alias :degree? :is_degree?
|
417
|
-
|
418
|
-
# returns the 'degree' unit associated with a temperature unit
|
419
|
-
# '100 tempC'.unit.temperature_scale #=> 'degC'
|
420
|
-
def temperature_scale
|
421
|
-
return nil unless self.is_temperature?
|
422
|
-
self.units =~ /temp(C|F|R|K)/
|
423
|
-
"deg#{$1}"
|
424
|
-
end
|
425
|
-
|
426
|
-
# returns true if no associated units
|
427
|
-
# false, even if the units are "unitless" like 'radians, each, etc'
|
428
|
-
def unitless?
|
429
|
-
(@numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY)
|
430
|
-
end
|
431
|
-
|
432
|
-
# Compare two Unit objects. Throws an exception if they are not of compatible types.
|
433
|
-
# Comparisons are done based on the value of the unit in base SI units.
|
434
|
-
def <=>(other)
|
435
|
-
case
|
436
|
-
when other.zero? && !self.is_temperature?
|
437
|
-
return self.base_scalar <=> 0
|
438
|
-
when Unit === other
|
439
|
-
raise ArgumentError, "Incompatible Units" unless self =~ other
|
440
|
-
return self.base_scalar <=> other.base_scalar
|
441
|
-
else
|
442
|
-
x,y = coerce(other)
|
443
|
-
return x <=> y
|
444
|
-
end
|
445
|
-
end
|
446
|
-
|
447
|
-
# check to see if units are compatible, but not the scalar part
|
448
|
-
# this check is done by comparing signatures for performance reasons
|
449
|
-
# if passed a string, it will create a unit object with the string and then do the comparison
|
450
|
-
# this permits a syntax like:
|
451
|
-
# unit =~ "mm"
|
452
|
-
# if you want to do a regexp on the unit string do this ...
|
453
|
-
# unit.units =~ /regexp/
|
454
|
-
def =~(other)
|
455
|
-
return true if self == 0 || other == 0
|
456
|
-
case other
|
457
|
-
when Unit
|
458
|
-
self.signature == other.signature
|
459
|
-
else
|
460
|
-
x,y = coerce(other)
|
461
|
-
x =~ y
|
462
|
-
end
|
463
|
-
end
|
464
|
-
|
465
|
-
alias :compatible? :=~
|
466
|
-
alias :compatible_with? :=~
|
467
|
-
|
468
|
-
# Compare two units. Returns true if quantities and units match
|
469
|
-
#
|
470
|
-
# Unit("100 cm") === Unit("100 cm") # => true
|
471
|
-
# Unit("100 cm") === Unit("1 m") # => false
|
472
|
-
def ===(other)
|
473
|
-
case other
|
474
|
-
when Unit
|
475
|
-
(self.scalar == other.scalar) && (self.units == other.units)
|
476
|
-
else
|
477
|
-
x,y = coerce(other)
|
478
|
-
x === y
|
479
|
-
end
|
480
|
-
end
|
481
|
-
|
482
|
-
alias :same? :===
|
483
|
-
alias :same_as? :===
|
484
|
-
|
485
|
-
# Add two units together. Result is same units as receiver and scalar and base_scalar are updated appropriately
|
486
|
-
# throws an exception if the units are not compatible.
|
487
|
-
# It is possible to add Time objects to units of time
|
488
|
-
def +(other)
|
489
|
-
if Unit === other
|
490
|
-
case
|
491
|
-
when self.zero?
|
492
|
-
other.dup
|
493
|
-
when self =~ other
|
494
|
-
raise ArgumentError, "Cannot add two temperatures" if ([self, other].all? {|x| x.is_temperature?})
|
495
|
-
if [self, other].any? {|x| x.is_temperature?}
|
496
|
-
if self.is_temperature?
|
497
|
-
Unit.new(:scalar => (self.scalar + other.to(self.temperature_scale).scalar), :numerator => @numerator, :denominator=>@denominator, :signature => @signature)
|
498
|
-
else
|
499
|
-
Unit.new(:scalar => (other.scalar + self.to(other.temperature_scale).scalar), :numerator => other.numerator, :denominator=>other.denominator, :signature => other.signature)
|
500
|
-
end
|
501
|
-
else
|
502
|
-
@q ||= ((@@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar) rescue (self.units.unit.to_base.scalar))
|
503
|
-
Unit.new(:scalar=>(self.base_scalar + other.base_scalar)*@q, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
|
504
|
-
end
|
505
|
-
else
|
506
|
-
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
|
507
|
-
end
|
508
|
-
elsif Time === other
|
509
|
-
other + self
|
510
|
-
else
|
511
|
-
x,y = coerce(other)
|
512
|
-
y + x
|
513
|
-
end
|
514
|
-
end
|
515
|
-
|
516
|
-
# Subtract two units. Result is same units as receiver and scalar and base_scalar are updated appropriately
|
517
|
-
# throws an exception if the units are not compatible.
|
518
|
-
def -(other)
|
519
|
-
if Unit === other
|
520
|
-
case
|
521
|
-
when self.zero?
|
522
|
-
-other.dup
|
523
|
-
when self =~ other
|
524
|
-
case
|
525
|
-
when [self, other].all? {|x| x.is_temperature?}
|
526
|
-
Unit.new(:scalar => (self.base_scalar - other.base_scalar), :numerator => KELVIN, :denominator => UNITY_ARRAY, :signature => @signature).to(self.temperature_scale)
|
527
|
-
when self.is_temperature?
|
528
|
-
Unit.new(:scalar => (self.base_scalar - other.base_scalar), :numerator => ['<temp-K>'], :denominator => UNITY_ARRAY, :signature => @signature).to(self)
|
529
|
-
when other.is_temperature?
|
530
|
-
raise ArgumentError, "Cannot subtract a temperature from a differential degree unit"
|
531
|
-
else
|
532
|
-
@q ||= ((@@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar) rescue (self.units.unit.scalar/self.units.unit.to_base.scalar))
|
533
|
-
Unit.new(:scalar=>(self.base_scalar - other.base_scalar)*@q, :numerator=>@numerator, :denominator=>@denominator, :signature=>@signature)
|
534
|
-
end
|
535
|
-
else
|
536
|
-
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
|
537
|
-
end
|
538
|
-
elsif Time === other
|
539
|
-
other - self
|
540
|
-
else
|
541
|
-
x,y = coerce(other)
|
542
|
-
y-x
|
543
|
-
end
|
544
|
-
end
|
545
|
-
|
546
|
-
# Multiply two units.
|
547
|
-
def *(other)
|
548
|
-
case other
|
549
|
-
when Unit
|
550
|
-
raise ArgumentError, "Cannot multiply by temperatures" if [other,self].any? {|x| x.is_temperature?}
|
551
|
-
opts = Unit.eliminate_terms(@scalar*other.scalar, @numerator + other.numerator ,@denominator + other.denominator)
|
552
|
-
opts.merge!(:signature => @signature + other.signature)
|
553
|
-
Unit.new(opts)
|
554
|
-
when Numeric
|
555
|
-
Unit.new(:scalar=>@scalar*other, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
|
556
|
-
else
|
557
|
-
x,y = coerce(other)
|
558
|
-
x * y
|
559
|
-
end
|
560
|
-
end
|
561
|
-
|
562
|
-
# Divide two units.
|
563
|
-
# Throws an exception if divisor is 0
|
564
|
-
def /(other)
|
565
|
-
case other
|
566
|
-
when Unit
|
567
|
-
raise ZeroDivisionError if other.zero?
|
568
|
-
raise ArgumentError, "Cannot divide with temperatures" if [other,self].any? {|x| x.is_temperature?}
|
569
|
-
opts = Unit.eliminate_terms(@scalar/other.scalar, @numerator + other.denominator ,@denominator + other.numerator)
|
570
|
-
opts.merge!(:signature=> @signature - other.signature)
|
571
|
-
Unit.new(opts)
|
572
|
-
when Numeric
|
573
|
-
raise ZeroDivisionError if other.zero?
|
574
|
-
Unit.new(:scalar=>@scalar/other, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
|
575
|
-
else
|
576
|
-
x,y = coerce(other)
|
577
|
-
y / x
|
578
|
-
end
|
579
|
-
end
|
580
|
-
|
581
|
-
# divide two units and return quotient and remainder
|
582
|
-
# when both units are in the same units we just use divmod on the raw scalars
|
583
|
-
# otherwise we use the scalar of the base unit which will be a float
|
584
|
-
def divmod(other)
|
585
|
-
raise ArgumentError, "Incompatible Units" unless self =~ other
|
586
|
-
if self.units == other.units
|
587
|
-
return self.scalar.divmod(other.scalar)
|
588
|
-
else
|
589
|
-
return self.to_base.scalar.divmod(other.to_base.scalar)
|
590
|
-
end
|
591
|
-
end
|
592
|
-
|
593
|
-
# Exponentiate. Only takes integer powers.
|
594
|
-
# Note that anything raised to the power of 0 results in a Unit object with a scalar of 1, and no units.
|
595
|
-
# Throws an exception if exponent is not an integer.
|
596
|
-
# Ideally this routine should accept a float for the exponent
|
597
|
-
# It should then convert the float to a rational and raise the unit by the numerator and root it by the denominator
|
598
|
-
# but, sadly, floats can't be converted to rationals.
|
599
|
-
#
|
600
|
-
# For now, if a rational is passed in, it will be used, otherwise we are stuck with integers and certain floats < 1
|
601
|
-
def **(other)
|
602
|
-
raise ArgumentError, "Cannot raise a temperature to a power" if self.is_temperature?
|
603
|
-
if Numeric === other
|
604
|
-
return Unit("1") if other.zero?
|
605
|
-
return self if other == 1
|
606
|
-
return self.inverse if other == -1
|
607
|
-
end
|
608
|
-
case other
|
609
|
-
when Rational
|
610
|
-
self.power(other.numerator).root(other.denominator)
|
611
|
-
when Integer
|
612
|
-
self.power(other)
|
613
|
-
when Float
|
614
|
-
return self**(other.to_i) if other == other.to_i
|
615
|
-
valid = (1..9).map {|x| 1/x}
|
616
|
-
raise ArgumentError, "Not a n-th root (1..9), use 1/n" unless valid.include? other.abs
|
617
|
-
self.root((1/other).to_int)
|
618
|
-
else
|
619
|
-
raise ArgumentError, "Invalid Exponent"
|
620
|
-
end
|
621
|
-
end
|
622
|
-
|
623
|
-
# returns the unit raised to the n-th power. Integers only
|
624
|
-
def power(n)
|
625
|
-
raise ArgumentError, "Cannot raise a temperature to a power" if self.is_temperature?
|
626
|
-
raise ArgumentError, "Can only use Integer exponenents" unless Integer === n
|
627
|
-
return self if n == 1
|
628
|
-
return Unit("1") if n == 0
|
629
|
-
return self.inverse if n == -1
|
630
|
-
if n > 0 then
|
631
|
-
(1..(n-1).to_i).inject(self) {|product, x| product * self}
|
632
|
-
else
|
633
|
-
(1..-(n-1).to_i).inject(self) {|product, x| product / self}
|
634
|
-
end
|
635
|
-
end
|
636
|
-
|
637
|
-
# Calculates the n-th root of a unit, where n = (1..9)
|
638
|
-
# if n < 0, returns 1/unit^(1/n)
|
639
|
-
def root(n)
|
640
|
-
raise ArgumentError, "Cannot take the root of a temperature" if self.is_temperature?
|
641
|
-
raise ArgumentError, "Exponent must an Integer" unless Integer === n
|
642
|
-
raise ArgumentError, "0th root undefined" if n == 0
|
643
|
-
return self if n == 1
|
644
|
-
return self.root(n.abs).inverse if n < 0
|
645
|
-
|
646
|
-
vec = self.unit_signature_vector
|
647
|
-
vec=vec.map {|x| x % n}
|
648
|
-
raise ArgumentError, "Illegal root" unless vec.max == 0
|
649
|
-
num = @numerator.dup
|
650
|
-
den = @denominator.dup
|
651
|
-
|
652
|
-
for item in @numerator.uniq do
|
653
|
-
x = num.find_all {|i| i==item}.size
|
654
|
-
r = ((x/n)*(n-1)).to_int
|
655
|
-
r.times {|y| num.delete_at(num.index(item))}
|
656
|
-
end
|
657
|
-
|
658
|
-
for item in @denominator.uniq do
|
659
|
-
x = den.find_all {|i| i==item}.size
|
660
|
-
r = ((x/n)*(n-1)).to_int
|
661
|
-
r.times {|y| den.delete_at(den.index(item))}
|
662
|
-
end
|
663
|
-
q = @scalar < 0 ? (-1)**Rational(1,n) * (@scalar.abs)**Rational(1,n) : @scalar**Rational(1,n)
|
664
|
-
Unit.new(:scalar=>q,:numerator=>num,:denominator=>den)
|
665
|
-
end
|
666
|
-
|
667
|
-
# returns inverse of Unit (1/unit)
|
668
|
-
def inverse
|
669
|
-
Unit("1") / self
|
670
|
-
end
|
671
|
-
|
672
|
-
# convert to a specified unit string or to the same units as another Unit
|
673
|
-
#
|
674
|
-
# unit >> "kg" will covert to kilograms
|
675
|
-
# unit1 >> unit2 converts to same units as unit2 object
|
676
|
-
#
|
677
|
-
# To convert a Unit object to match another Unit object, use:
|
678
|
-
# unit1 >>= unit2
|
679
|
-
# Throws an exception if the requested target units are incompatible with current Unit.
|
680
|
-
#
|
681
|
-
# Special handling for temperature conversions is supported. If the Unit object is converted
|
682
|
-
# from one temperature unit to another, the proper temperature offsets will be used.
|
683
|
-
# Supports Kelvin, Celsius, fahrenheit, and Rankine scales.
|
684
|
-
#
|
685
|
-
# Note that if temperature is part of a compound unit, the temperature will be treated as a differential
|
686
|
-
# and the units will be scaled appropriately.
|
687
|
-
def to(other)
|
688
|
-
return self if other.nil?
|
689
|
-
return self if TrueClass === other
|
690
|
-
return self if FalseClass === other
|
691
|
-
if (Unit === other && other.is_temperature?) || (String === other && other =~ /temp(K|C|R|F)/)
|
692
|
-
raise ArgumentError, "Receiver is not a temperature unit" unless self.degree?
|
693
|
-
start_unit = self.units
|
694
|
-
target_unit = other.units rescue other
|
695
|
-
unless @base_scalar
|
696
|
-
@base_scalar = case start_unit
|
697
|
-
when 'tempC'
|
698
|
-
@scalar + 273.15
|
699
|
-
when 'tempK'
|
700
|
-
@scalar
|
701
|
-
when 'tempF'
|
702
|
-
(@scalar+459.67)*(5.0/9.0)
|
703
|
-
when 'tempR'
|
704
|
-
@scalar*(5.0/9.0)
|
705
|
-
end
|
706
|
-
end
|
707
|
-
q= case target_unit
|
708
|
-
when 'tempC'
|
709
|
-
@base_scalar - 273.15
|
710
|
-
when 'tempK'
|
711
|
-
@base_scalar
|
712
|
-
when 'tempF'
|
713
|
-
@base_scalar * (9.0/5.0) - 459.67
|
714
|
-
when 'tempR'
|
715
|
-
@base_scalar * (9.0/5.0)
|
716
|
-
end
|
717
|
-
|
718
|
-
Unit.new("#{q} #{target_unit}")
|
719
|
-
else
|
720
|
-
case other
|
721
|
-
when Unit
|
722
|
-
return self if other.units == self.units
|
723
|
-
target = other
|
724
|
-
when String
|
725
|
-
target = Unit.new(other)
|
726
|
-
else
|
727
|
-
raise ArgumentError, "Unknown target units"
|
728
|
-
end
|
729
|
-
raise ArgumentError, "Incompatible Units" unless self =~ target
|
730
|
-
one = @numerator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[i][:scalar] }.compact
|
731
|
-
two = @denominator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[i][:scalar] }.compact
|
732
|
-
v = one.inject(1) {|product,n| product*n} / two.inject(1) {|product,n| product*n}
|
733
|
-
one = target.numerator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[x][:scalar] }.compact
|
734
|
-
two = target.denominator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[x][:scalar] }.compact
|
735
|
-
y = one.inject(1) {|product,n| product*n} / two.inject(1) {|product,n| product*n}
|
736
|
-
q = @scalar * v/y
|
737
|
-
Unit.new(:scalar=>q, :numerator=>target.numerator, :denominator=>target.denominator, :signature => target.signature)
|
738
|
-
end
|
739
|
-
end
|
740
|
-
alias :>> :to
|
741
|
-
alias :convert_to :to
|
742
|
-
|
743
|
-
# converts the unit back to a float if it is unitless. Otherwise raises an exception
|
744
|
-
def to_f
|
745
|
-
return @scalar.to_f if self.unitless?
|
746
|
-
raise RuntimeError, "Can't convert to Float unless unitless (#{self.to_s}). Use Unit#scalar"
|
747
|
-
end
|
748
|
-
|
749
|
-
# converts the unit back to a complex if it is unitless. Otherwise raises an exception
|
750
|
-
|
751
|
-
def to_c
|
752
|
-
return Complex(@scalar) if self.unitless?
|
753
|
-
raise RuntimeError, "Can't convert to Complex unless unitless. Use Unit#scalar"
|
754
|
-
end
|
755
|
-
|
756
|
-
# returns the 'unit' part of the Unit object without the scalar
|
757
|
-
def units
|
758
|
-
return "" if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY
|
759
|
-
return @unit_name unless @unit_name.nil?
|
760
|
-
output_n = []
|
761
|
-
output_d =[]
|
762
|
-
num = @numerator.clone.compact
|
763
|
-
den = @denominator.clone.compact
|
764
|
-
if @numerator == UNITY_ARRAY
|
765
|
-
output_n << "1"
|
766
|
-
else
|
767
|
-
num.each_with_index do |token,index|
|
768
|
-
if token && @@PREFIX_VALUES[token] then
|
769
|
-
output_n << "#{@@OUTPUT_MAP[token]}#{@@OUTPUT_MAP[num[index+1]]}"
|
770
|
-
num[index+1]=nil
|
771
|
-
else
|
772
|
-
output_n << "#{@@OUTPUT_MAP[token]}" if token
|
773
|
-
end
|
774
|
-
end
|
775
|
-
end
|
776
|
-
if @denominator == UNITY_ARRAY
|
777
|
-
output_d = ['1']
|
778
|
-
else
|
779
|
-
den.each_with_index do |token,index|
|
780
|
-
if token && @@PREFIX_VALUES[token] then
|
781
|
-
output_d << "#{@@OUTPUT_MAP[token]}#{@@OUTPUT_MAP[den[index+1]]}"
|
782
|
-
den[index+1]=nil
|
783
|
-
else
|
784
|
-
output_d << "#{@@OUTPUT_MAP[token]}" if token
|
785
|
-
end
|
786
|
-
end
|
787
|
-
end
|
788
|
-
on = output_n.reject {|x| x.empty?}.map {|x| [x, output_n.find_all {|z| z==x}.size]}.uniq.map {|x| ("#{x[0]}".strip+ (x[1] > 1 ? "^#{x[1]}" : ''))}
|
789
|
-
od = output_d.reject {|x| x.empty?}.map {|x| [x, output_d.find_all {|z| z==x}.size]}.uniq.map {|x| ("#{x[0]}".strip+ (x[1] > 1 ? "^#{x[1]}" : ''))}
|
790
|
-
out = "#{on.join('*')}#{od == ['1'] ? '': '/'+od.join('*')}".strip
|
791
|
-
@unit_name = out unless self.kind == :temperature
|
792
|
-
return out
|
793
|
-
end
|
794
|
-
|
795
|
-
# negates the scalar of the Unit
|
796
|
-
def -@
|
797
|
-
return -@scalar if self.unitless?
|
798
|
-
self.dup * -1
|
799
|
-
end
|
800
|
-
|
801
|
-
# returns abs of scalar, without the units
|
802
|
-
def abs
|
803
|
-
return @scalar.abs
|
804
|
-
end
|
805
|
-
|
806
|
-
def ceil
|
807
|
-
return @scalar.ceil if self.unitless?
|
808
|
-
Unit.new(@scalar.ceil, @numerator, @denominator)
|
809
|
-
end
|
810
|
-
|
811
|
-
def floor
|
812
|
-
return @scalar.floor if self.unitless?
|
813
|
-
Unit.new(@scalar.floor, @numerator, @denominator)
|
814
|
-
end
|
815
|
-
|
816
|
-
# if unitless, returns an int, otherwise raises an error
|
817
|
-
def to_i
|
818
|
-
return @scalar.to_int if self.unitless?
|
819
|
-
raise RuntimeError, 'Cannot convert to Integer unless unitless'
|
820
|
-
end
|
821
|
-
alias :to_int :to_i
|
822
|
-
|
823
|
-
# Tries to make a Time object from current unit. Assumes the current unit hold the duration in seconds from the epoch.
|
824
|
-
def to_time
|
825
|
-
Time.at(self)
|
826
|
-
end
|
827
|
-
alias :time :to_time
|
828
|
-
|
829
|
-
def truncate
|
830
|
-
return @scalar.truncate if self.unitless?
|
831
|
-
Unit.new(@scalar.truncate, @numerator, @denominator)
|
832
|
-
end
|
833
|
-
|
834
|
-
# convert a duration to a DateTime. This will work so long as the duration is the duration from the zero date
|
835
|
-
# defined by DateTime
|
836
|
-
def to_datetime
|
837
|
-
DateTime.new!(self.to('d').scalar)
|
838
|
-
end
|
839
|
-
|
840
|
-
def to_date
|
841
|
-
Date.new0(self.to('d').scalar)
|
842
|
-
end
|
843
|
-
|
844
|
-
def round
|
845
|
-
return @scalar.round if self.unitless?
|
846
|
-
Unit.new(@scalar.round, @numerator, @denominator)
|
847
|
-
end
|
848
|
-
|
849
|
-
# true if scalar is zero
|
850
|
-
def zero?
|
851
|
-
return self.to_base.scalar.zero?
|
852
|
-
end
|
853
|
-
|
854
|
-
# '5 min'.unit.ago
|
855
|
-
def ago
|
856
|
-
self.before
|
857
|
-
end
|
858
|
-
|
859
|
-
# '5 min'.before(time)
|
860
|
-
def before(time_point = ::Time.now)
|
861
|
-
raise ArgumentError, "Must specify a Time" unless time_point
|
862
|
-
if String === time_point
|
863
|
-
time_point.time - self rescue time_point.datetime - self
|
864
|
-
else
|
865
|
-
time_point - self rescue time_point.to_datetime - self
|
866
|
-
end
|
867
|
-
end
|
868
|
-
alias :before_now :before
|
869
|
-
|
870
|
-
# 'min'.since(time)
|
871
|
-
def since(time_point = ::Time.now)
|
872
|
-
case time_point
|
873
|
-
when Time
|
874
|
-
(Time.now - time_point).unit('s').to(self)
|
875
|
-
when DateTime, Date
|
876
|
-
(DateTime.now - time_point).unit('d').to(self)
|
877
|
-
when String
|
878
|
-
(DateTime.now - time_point.to_datetime(:context=>:past)).unit('d').to(self)
|
879
|
-
else
|
880
|
-
raise ArgumentError, "Must specify a Time, DateTime, or String"
|
881
|
-
end
|
882
|
-
end
|
883
|
-
|
884
|
-
# 'min'.until(time)
|
885
|
-
def until(time_point = ::Time.now)
|
886
|
-
case time_point
|
887
|
-
when Time
|
888
|
-
(time_point - Time.now).unit('s').to(self)
|
889
|
-
when DateTime, Date
|
890
|
-
(time_point - DateTime.now).unit('d').to(self)
|
891
|
-
when String
|
892
|
-
(time_point.to_datetime(:context=>:future) - DateTime.now).unit('d').to(self)
|
893
|
-
else
|
894
|
-
raise ArgumentError, "Must specify a Time, DateTime, or String"
|
895
|
-
end
|
896
|
-
end
|
897
|
-
|
898
|
-
# '5 min'.from(time)
|
899
|
-
def from(time_point = ::Time.now)
|
900
|
-
raise ArgumentError, "Must specify a Time" unless time_point
|
901
|
-
if String === time_point
|
902
|
-
time_point.time + self rescue time_point.datetime + self
|
903
|
-
else
|
904
|
-
time_point + self rescue time_point.to_datetime + self
|
905
|
-
end
|
906
|
-
end
|
907
|
-
alias :after :from
|
908
|
-
alias :from_now :from
|
909
|
-
|
910
|
-
# returns next unit in a range. '1 mm'.unit.succ #=> '2 mm'.unit
|
911
|
-
# only works when the scalar is an integer
|
912
|
-
def succ
|
913
|
-
raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i
|
914
|
-
q = @scalar.to_i.succ
|
915
|
-
Unit.new(q, @numerator, @denominator)
|
916
|
-
end
|
917
|
-
|
918
|
-
# automatically coerce objects to units when possible
|
919
|
-
# if an object defines a 'to_unit' method, it will be coerced using that method
|
920
|
-
def coerce(other)
|
921
|
-
if other.respond_to? :to_unit
|
922
|
-
return [other.to_unit, self]
|
923
|
-
end
|
924
|
-
case other
|
925
|
-
when Unit
|
926
|
-
[other, self]
|
927
|
-
else
|
928
|
-
[Unit.new(other), self]
|
929
|
-
end
|
930
|
-
end
|
931
|
-
|
932
|
-
# Protected and Private Functions that should only be called from this class
|
933
|
-
protected
|
934
|
-
|
935
|
-
|
936
|
-
def update_base_scalar
|
937
|
-
return @base_scalar unless @base_scalar.nil?
|
938
|
-
if self.is_base?
|
939
|
-
@base_scalar = @scalar
|
940
|
-
@signature = unit_signature
|
941
|
-
else
|
942
|
-
base = self.to_base
|
943
|
-
@base_scalar = base.scalar
|
944
|
-
@signature = base.signature
|
945
|
-
end
|
946
|
-
end
|
947
|
-
|
948
|
-
# calculates the unit signature vector used by unit_signature
|
949
|
-
def unit_signature_vector
|
950
|
-
return self.to_base.unit_signature_vector unless self.is_base?
|
951
|
-
vector = Array.new(SIGNATURE_VECTOR.size,0)
|
952
|
-
for element in @numerator
|
953
|
-
if r=@@ALL_UNIT_DEFINITIONS[element]
|
954
|
-
n = SIGNATURE_VECTOR.index(r[2])
|
955
|
-
vector[n] = vector[n] + 1 if n
|
956
|
-
end
|
957
|
-
end
|
958
|
-
for element in @denominator
|
959
|
-
if r=@@ALL_UNIT_DEFINITIONS[element]
|
960
|
-
n = SIGNATURE_VECTOR.index(r[2])
|
961
|
-
vector[n] = vector[n] - 1 if n
|
962
|
-
end
|
963
|
-
end
|
964
|
-
vector
|
965
|
-
end
|
966
|
-
|
967
|
-
private
|
968
|
-
|
969
|
-
def initialize_copy(other)
|
970
|
-
@numerator = other.numerator.dup
|
971
|
-
@denominator = other.denominator.dup
|
972
|
-
end
|
973
|
-
|
974
|
-
# calculates the unit signature id for use in comparing compatible units and simplification
|
975
|
-
# the signature is based on a simple classification of units and is based on the following publication
|
976
|
-
#
|
977
|
-
# Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering,
|
978
|
-
# 21(8), Aug 1995, pp.651-661
|
979
|
-
# doi://10.1109/32.403789
|
980
|
-
# http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr.
|
981
|
-
#
|
982
|
-
def unit_signature
|
983
|
-
return @signature unless @signature.nil?
|
984
|
-
vector = unit_signature_vector
|
985
|
-
vector.each_with_index {|item,index| vector[index] = item * 20**index}
|
986
|
-
@signature=vector.inject(0) {|sum,n| sum+n}
|
987
|
-
end
|
988
|
-
|
989
|
-
def self.eliminate_terms(q, n, d)
|
990
|
-
num = n.dup
|
991
|
-
den = d.dup
|
992
|
-
|
993
|
-
num.delete_if {|v| v == UNITY}
|
994
|
-
den.delete_if {|v| v == UNITY}
|
995
|
-
combined = Hash.new(0)
|
996
|
-
|
997
|
-
i = 0
|
998
|
-
loop do
|
999
|
-
break if i > num.size
|
1000
|
-
if @@PREFIX_VALUES.has_key? num[i]
|
1001
|
-
k = [num[i],num[i+1]]
|
1002
|
-
i += 2
|
1003
|
-
else
|
1004
|
-
k = num[i]
|
1005
|
-
i += 1
|
1006
|
-
end
|
1007
|
-
combined[k] += 1 unless k.nil? || k == UNITY
|
1008
|
-
end
|
1009
|
-
|
1010
|
-
j = 0
|
1011
|
-
loop do
|
1012
|
-
break if j > den.size
|
1013
|
-
if @@PREFIX_VALUES.has_key? den[j]
|
1014
|
-
k = [den[j],den[j+1]]
|
1015
|
-
j += 2
|
1016
|
-
else
|
1017
|
-
k = den[j]
|
1018
|
-
j += 1
|
1019
|
-
end
|
1020
|
-
combined[k] -= 1 unless k.nil? || k == UNITY
|
1021
|
-
end
|
1022
|
-
|
1023
|
-
num = []
|
1024
|
-
den = []
|
1025
|
-
for key, value in combined do
|
1026
|
-
case
|
1027
|
-
when value > 0
|
1028
|
-
value.times {num << key}
|
1029
|
-
when value < 0
|
1030
|
-
value.abs.times {den << key}
|
1031
|
-
end
|
1032
|
-
end
|
1033
|
-
num = UNITY_ARRAY if num.empty?
|
1034
|
-
den = UNITY_ARRAY if den.empty?
|
1035
|
-
{:scalar=>q, :numerator=>num.flatten.compact, :denominator=>den.flatten.compact}
|
1036
|
-
end
|
1037
|
-
|
1038
|
-
|
1039
|
-
# parse a string into a unit object.
|
1040
|
-
# Typical formats like :
|
1041
|
-
# "5.6 kg*m/s^2"
|
1042
|
-
# "5.6 kg*m*s^-2"
|
1043
|
-
# "5.6 kilogram*meter*second^-2"
|
1044
|
-
# "2.2 kPa"
|
1045
|
-
# "37 degC"
|
1046
|
-
# "1" -- creates a unitless constant with value 1
|
1047
|
-
# "GPa" -- creates a unit with scalar 1 with units 'GPa'
|
1048
|
-
# 6'4" -- recognized as 6 feet + 4 inches
|
1049
|
-
# 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
|
1050
|
-
def parse(passed_unit_string="0")
|
1051
|
-
unit_string = passed_unit_string.dup
|
1052
|
-
if unit_string =~ /\$\s*(#{NUMBER_REGEX})/
|
1053
|
-
unit_string = "#{$1} USD"
|
1054
|
-
end
|
1055
|
-
unit_string.gsub!(/%/,'percent')
|
1056
|
-
unit_string.gsub!(/'/,'feet')
|
1057
|
-
unit_string.gsub!(/"/,'inch')
|
1058
|
-
unit_string.gsub!(/#/,'pound')
|
1059
|
-
if defined?(Uncertain) && unit_string =~ /(\+\/-|±)/
|
1060
|
-
value, uncertainty, unit_s = unit_string.scan(UNCERTAIN_REGEX)[0]
|
1061
|
-
result = unit_s.unit * Uncertain.new(value.to_f,uncertainty.to_f)
|
1062
|
-
copy(result)
|
1063
|
-
return
|
1064
|
-
end
|
1065
|
-
|
1066
|
-
if defined?(Complex) && unit_string =~ COMPLEX_NUMBER
|
1067
|
-
real, imaginary, unit_s = unit_string.scan(COMPLEX_REGEX)[0]
|
1068
|
-
result = Unit(unit_s || '1') * Complex(real.to_f,imaginary.to_f)
|
1069
|
-
copy(result)
|
1070
|
-
return
|
1071
|
-
end
|
1072
|
-
|
1073
|
-
if defined?(Rational) && unit_string =~ RATIONAL_NUMBER
|
1074
|
-
numerator, denominator, unit_s = unit_string.scan(RATIONAL_REGEX)[0]
|
1075
|
-
result = Unit(unit_s || '1') * Rational(numerator.to_i,denominator.to_i)
|
1076
|
-
copy(result)
|
1077
|
-
return
|
1078
|
-
end
|
1079
|
-
|
1080
|
-
unit_string =~ NUMBER_REGEX
|
1081
|
-
unit = @@cached_units[$2]
|
1082
|
-
mult = ($1.empty? ? 1.0 : $1.to_f) rescue 1.0
|
1083
|
-
if unit
|
1084
|
-
copy(unit)
|
1085
|
-
@scalar *= mult
|
1086
|
-
@base_scalar *= mult
|
1087
|
-
return self
|
1088
|
-
end
|
1089
|
-
unit_string.gsub!(/<(#{@@UNIT_REGEX})><(#{@@UNIT_REGEX})>/, '\1*\2')
|
1090
|
-
unit_string.gsub!(/[<>]/,"")
|
1091
|
-
|
1092
|
-
if unit_string =~ /:/
|
1093
|
-
hours, minutes, seconds, microseconds = unit_string.scan(TIME_REGEX)[0]
|
1094
|
-
raise ArgumentError, "Invalid Duration" if [hours, minutes, seconds, microseconds].all? {|x| x.nil?}
|
1095
|
-
result = "#{hours || 0} h".unit +
|
1096
|
-
"#{minutes || 0} minutes".unit +
|
1097
|
-
"#{seconds || 0} seconds".unit +
|
1098
|
-
"#{microseconds || 0} usec".unit
|
1099
|
-
copy(result)
|
1100
|
-
return
|
1101
|
-
end
|
1102
|
-
|
1103
|
-
|
1104
|
-
# Special processing for unusual unit strings
|
1105
|
-
# feet -- 6'5"
|
1106
|
-
feet, inches = unit_string.scan(FEET_INCH_REGEX)[0]
|
1107
|
-
if (feet && inches)
|
1108
|
-
result = Unit.new("#{feet} ft") + Unit.new("#{inches} inches")
|
1109
|
-
copy(result)
|
1110
|
-
return
|
1111
|
-
end
|
1112
|
-
|
1113
|
-
# weight -- 8 lbs 12 oz
|
1114
|
-
pounds, oz = unit_string.scan(LBS_OZ_REGEX)[0]
|
1115
|
-
if (pounds && oz)
|
1116
|
-
result = Unit.new("#{pounds} lbs") + Unit.new("#{oz} oz")
|
1117
|
-
copy(result)
|
1118
|
-
return
|
1119
|
-
end
|
1120
|
-
|
1121
|
-
raise( ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.count('/') > 1
|
1122
|
-
raise( ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.scan(/\s[02-9]/).size > 0
|
1123
|
-
@scalar, top, bottom = unit_string.scan(UNIT_STRING_REGEX)[0] #parse the string into parts
|
1124
|
-
top.scan(TOP_REGEX).each do |item|
|
1125
|
-
n = item[1].to_i
|
1126
|
-
x = "#{item[0]} "
|
1127
|
-
case
|
1128
|
-
when n>=0
|
1129
|
-
top.gsub!(/#{item[0]}(\^|\*\*)#{n}/) {|s| x * n}
|
1130
|
-
when n<0
|
1131
|
-
bottom = "#{bottom} #{x * -n}"; top.gsub!(/#{item[0]}(\^|\*\*)#{n}/,"")
|
1132
|
-
end
|
1133
|
-
end
|
1134
|
-
bottom.gsub!(BOTTOM_REGEX) {|s| "#{$1} " * $2.to_i} if bottom
|
1135
|
-
@scalar = @scalar.to_f unless @scalar.nil? || @scalar.empty?
|
1136
|
-
@scalar = 1 unless @scalar.kind_of? Numeric
|
1137
|
-
|
1138
|
-
@numerator ||= UNITY_ARRAY
|
1139
|
-
@denominator ||= UNITY_ARRAY
|
1140
|
-
@numerator = top.scan(@@UNIT_MATCH_REGEX).delete_if {|x| x.empty?}.compact if top
|
1141
|
-
@denominator = bottom.scan(@@UNIT_MATCH_REGEX).delete_if {|x| x.empty?}.compact if bottom
|
1142
|
-
|
1143
|
-
|
1144
|
-
us = "#{(top || '' + bottom || '')}".to_s.gsub(@@UNIT_MATCH_REGEX,'').gsub(/[\d\*, "'_^\/\$]/,'')
|
1145
|
-
raise( ArgumentError, "'#{passed_unit_string}' Unit not recognized #{us.inspect}") unless us.empty?
|
1146
|
-
|
1147
|
-
@numerator = @numerator.map do |item|
|
1148
|
-
@@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]]
|
1149
|
-
end.flatten.compact.delete_if {|x| x.empty?}
|
1150
|
-
|
1151
|
-
@denominator = @denominator.map do |item|
|
1152
|
-
@@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]]
|
1153
|
-
end.flatten.compact.delete_if {|x| x.empty?}
|
1154
|
-
|
1155
|
-
@numerator = UNITY_ARRAY if @numerator.empty?
|
1156
|
-
@denominator = UNITY_ARRAY if @denominator.empty?
|
1157
|
-
self
|
1158
|
-
end
|
1159
|
-
end
|
1160
|
-
|
1161
|
-
Unit.setup
|