ruby-units 1.2.0 → 1.3.0.a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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