ruby-units 1.2.0 → 1.3.0.a
Sign up to get free protection for your applications and to get access to all the features.
- 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
|