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/Gemfile DELETED
@@ -1,7 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- group :development do
4
- gem 'bundler', '~> 1.0'
5
- gem 'rcov'
6
- gem 'jeweler'
7
- end
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
-
@@ -1,9 +0,0 @@
1
- # make a unit from an array
2
- # [1, 'mm'].unit => 1 mm
3
- class Array
4
- def to_unit(other = nil)
5
- other ? Unit.new(self).to(other) : Unit.new(self)
6
- end
7
- alias :unit :to_unit
8
- alias :u :to_unit
9
- end
@@ -1,2 +0,0 @@
1
- module CMath
2
- end
@@ -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
@@ -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
@@ -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
@@ -1,8 +0,0 @@
1
- # make a unitless unit with a given scalar
2
- class Numeric
3
- def to_unit(other = nil)
4
- other ? Unit.new(self, other) : Unit.new(self)
5
- end
6
- alias :unit :to_unit
7
- alias :u :to_unit
8
- end
@@ -1,8 +0,0 @@
1
- class Object
2
- def Unit(*other)
3
- other.to_unit
4
- end
5
-
6
- alias :U :Unit
7
- alias :u :Unit
8
- end
@@ -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?(.+)?|&plusmn;|\+\/-/)
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 =~ /(\+\/-|&plusmn;)/
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