ruby-units 0.2.3 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -108,5 +108,13 @@ Change Log for Ruby-units
108
108
  * 'string'.to_datetime returns a DateTime object
109
109
  * 'string'.time returns a Time object or a DateTime if the Time object fails
110
110
  * 'string'.datetime returns a DateTime or a Time if the DateTime fails
111
-
112
-
111
+
112
+ 2006-10-02 0.3.0 * Performance enhanced by caching results of many functions
113
+ (Thanks to Kurt Stephens for pushing this.)
114
+ * Throws an exception if the unit is not recognized
115
+ * units can now identify what 'kind' they are (:length, :mass, etc..)
116
+ * New constructors:
117
+ Unit(1,"mm")
118
+ Unit(1,"mm/s")
119
+ Unit(1,"mm","s")
120
+ 2006-10-02 0.3.1 * minor bug fixes
data/lib/ruby-units.rb CHANGED
@@ -2,7 +2,7 @@ require 'mathn'
2
2
  require 'rational'
3
3
  require 'date'
4
4
  require 'parsedate'
5
- # = Ruby Units 0.2.3
5
+ # = Ruby Units 0.3.1
6
6
  #
7
7
  # Copyright 2006 by Kevin C. Olbrich, Ph.D.
8
8
  #
@@ -40,49 +40,124 @@ require 'parsedate'
40
40
  class Unit < Numeric
41
41
  require 'units'
42
42
  # pre-generate hashes from unit definitions for performance.
43
+ VERSION = '0.3.0'
43
44
  @@USER_DEFINITIONS = {}
44
45
  @@PREFIX_VALUES = {}
45
46
  @@PREFIX_MAP = {}
46
47
  @@UNIT_MAP = {}
47
48
  @@UNIT_VALUES = {}
48
49
  @@OUTPUT_MAP = {}
49
- @@UNIT_VECTORS = {}
50
+ @@BASE_UNITS = ['<meter>','<kilogram>','<second>','<mole>', '<farad>', '<ampere>','<radian>','<kelvin>','<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
+ LBS_OZ_REGEX = /(\d+)\s*(?:#|lbs|pounds)+[\s,]*(\d+)\s*(?:oz|ounces)/
55
+ SCI_NUMBER = %r{([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)}
56
+ NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/
57
+ UNIT_STRING_REGEX = /#{SCI_NUMBER}*\s*([^\/]*)\/*(.+)*/
58
+ TOP_REGEX = /([^ \*]+)(?:\^|\*\*)([\d-]+)/
59
+ BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/
60
+
61
+ KELVIN = ['<kelvin>']
62
+ FARENHEIT = ['<farenheit>']
63
+ RANKINE = ['<rankine>']
64
+ CELCIUS = ['<celcius>']
50
65
 
66
+ SIGNATURE_VECTOR = [:length, :time, :temperature, :mass, :current, :substance, :luminosity, :currency, :memory, :angle, :capacitance]
67
+ @@KINDS = {
68
+ -312058=>:resistance,
69
+ -312038=>:inductance,
70
+ -152040=>:magnetism,
71
+ -152038=>:magnetism,
72
+ -152058=>:potential,
73
+ -39=>:acceleration,
74
+ -38=>:radiation,
75
+ -20=>:frequency,
76
+ -19=>:speed,
77
+ -18=>:viscosity,
78
+ 0=>:unitless,
79
+ 1=>:length,
80
+ 2=>:area,
81
+ 3=>:volume,
82
+ 20=>:time,
83
+ 400=>:temperature,
84
+ 7942=>:power,
85
+ 7959=>:pressure,
86
+ 7962=>:energy,
87
+ 7979=>:viscosity,
88
+ 7981=>:force,
89
+ 7997=>:mass_concentration,
90
+ 8000=>:mass,
91
+ 159999=>:magnetism,
92
+ 160000=>:current,
93
+ 160020=>:charge,
94
+ 312058=>:resistance,
95
+ 3199980=>:activity,
96
+ 3199997=>:molar_concentration,
97
+ 3200000=>:substance,
98
+ 63999998=>:illuminance,
99
+ 64000000=>:luminous_power,
100
+ 1280000000=>:currency,
101
+ 25600000000=>:memory,
102
+ 511999999980=>:angular_velocity,
103
+ 512000000000=>:angle,
104
+ 10240000000000=>:capacitance,
105
+ }
106
+
107
+ @@cached_units = {}
108
+ @@base_unit_cache = {}
109
+
51
110
  def self.setup
52
- (UNIT_DEFINITIONS.merge!(@@USER_DEFINITIONS)).each do |key, value|
111
+ @@ALL_UNIT_DEFINITIONS = UNIT_DEFINITIONS.merge!(@@USER_DEFINITIONS)
112
+ for unit in (@@ALL_UNIT_DEFINITIONS) do
113
+ key, value = unit
53
114
  if value[2] == :prefix then
54
- @@PREFIX_VALUES[Regexp.escape(key)]=value[1]
55
- value[0].each {|x| @@PREFIX_MAP[Regexp.escape(x)]=key}
115
+ @@PREFIX_VALUES[key]=value[1]
116
+ for name in value[0] do
117
+ @@PREFIX_MAP[name]=key
118
+ end
56
119
  else
57
- @@UNIT_VALUES[Regexp.escape(key)]={}
58
- @@UNIT_VALUES[Regexp.escape(key)][:scalar]=value[1]
59
- @@UNIT_VALUES[Regexp.escape(key)][:numerator]=value[3] if value[3]
60
- @@UNIT_VALUES[Regexp.escape(key)][:denominator]=value[4] if value[4]
61
- value[0].each {|x| @@UNIT_MAP[Regexp.escape(x)]=key}
62
- @@UNIT_VECTORS[value[2]] = [] unless @@UNIT_VECTORS[value[2]]
63
- @@UNIT_VECTORS[value[2]] = @@UNIT_VECTORS[value[2]]+[Regexp.escape(key)]
120
+ @@UNIT_VALUES[key]={}
121
+ @@UNIT_VALUES[key][:scalar]=value[1]
122
+ @@UNIT_VALUES[key][:numerator]=value[3] if value[3]
123
+ @@UNIT_VALUES[key][:denominator]=value[4] if value[4]
124
+ for name in value[0] do
125
+ @@UNIT_MAP[name]=key
126
+ end
64
127
  end
65
- @@OUTPUT_MAP[Regexp.escape(key)]=value[0][0]
128
+ @@OUTPUT_MAP[key]=value[0][0]
66
129
  end
67
130
  @@PREFIX_REGEX = @@PREFIX_MAP.keys.sort_by {|prefix| prefix.length}.reverse.join('|')
68
131
  @@UNIT_REGEX = @@UNIT_MAP.keys.sort_by {|unit| unit.length}.reverse.join('|')
132
+ @@UNIT_MATCH_REGEX = /(#{@@PREFIX_REGEX})*?(#{@@UNIT_REGEX})\b/
133
+
69
134
  end
70
135
 
71
- self.setup
72
136
 
73
137
  include Comparable
74
- attr_accessor :scalar, :numerator, :denominator, :signature, :base_scalar
138
+ attr_accessor :scalar, :numerator, :denominator, :signature, :base_scalar, :base_numerator, :base_denominator, :output, :unit_name
75
139
 
76
140
  def to_yaml_properties
77
141
  %w{@scalar @numerator @denominator @signature @base_scalar}
78
142
  end
143
+
144
+ def copy(from)
145
+ @scalar = from.scalar
146
+ @numerator = from.numerator
147
+ @denominator = from.denominator
148
+ @is_base = from.is_base?
149
+ @signature = from.signature
150
+ @base_scalar = from.base_scalar
151
+ @output = from.output rescue nil
152
+ @unit_name = from.unit_name rescue nil
153
+ end
79
154
 
80
155
  # basically a copy of the basic to_yaml. Needed because otherwise it ends up coercing the object to a string
81
156
  # before YAML'izing it.
82
157
  def to_yaml( opts = {} )
83
158
  YAML::quick_emit( object_id, opts ) do |out|
84
159
  out.map( taguri, to_yaml_style ) do |map|
85
- to_yaml_properties.each do |m|
160
+ for m in to_yaml_properties do
86
161
  map.add( m[1..-1], instance_variable_get( m ) )
87
162
  end
88
163
  end
@@ -101,28 +176,77 @@ class Unit < Numeric
101
176
  # 6'4" -- recognized as 6 feet + 4 inches
102
177
  # 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
103
178
  #
104
- def initialize(options)
105
- case options
106
- when String: parse(options)
179
+ def initialize(*options)
180
+ if options.size == 2
181
+ begin
182
+ cached = @@cached_units[options[1]] * options[0]
183
+ copy(cached)
184
+ rescue
185
+ initialize("#{options[0]} #{options[1]}")
186
+ end
187
+ return
188
+ end
189
+ if options.size == 3
190
+ begin
191
+ cached = @@cached_units["#{options[1]}/#{options[2]}"] * options[0]
192
+ copy(cached)
193
+ rescue
194
+ initialize("#{options[0]} #{options[1]}/#{options[2]}")
195
+ end
196
+ return
197
+ end
198
+
199
+
200
+ case options[0]
201
+ when String: parse(options[0])
107
202
  when Hash:
108
- @scalar = options[:scalar] || 1
109
- @numerator = options[:numerator] || ["<1>"]
110
- @denominator = options[:denominator] || []
111
- when Array:
112
- parse("#{options[0]} #{options[1]}/#{options[2]}")
203
+ @scalar = options[0][:scalar] || 1
204
+ @numerator = options[0][:numerator] || UNITY_ARRAY
205
+ @denominator = options[0][:denominator] || UNITY_ARRAY
206
+ @signature = options[0][:signature]
207
+ when Array:
208
+ initialize(*options[0])
209
+ return
113
210
  when Numeric:
114
- @scalar = options
115
- @numerator = @denominator = ['<1>']
211
+ @scalar = options[0]
212
+ @numerator = @denominator = UNITY_ARRAY
116
213
  when Time:
117
- @scalar = options.to_f
214
+ @scalar = options[0].to_f
118
215
  @numerator = ['<second>']
119
- @denominator = ['<1>']
216
+ @denominator = UNITY_ARRAY
120
217
  else
121
218
  raise ArgumentError, "Invalid Unit Format"
122
219
  end
123
220
  self.update_base_scalar
124
221
  self.replace_temperature
125
- self.freeze
222
+
223
+ unary_unit = self.units
224
+ opt_units = options[0].scan(NUMBER_REGEX)[0][1] if String === options[0]
225
+ unless @@cached_units.keys.include?(opt_units) || (opt_units =~ /(temp|deg)(C|K|R|F)/)
226
+ @@cached_units[opt_units] = (self.scalar == 1 ? self : opt_units.unit) if opt_units && !opt_units.empty?
227
+ end
228
+ unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /(temp|deg)(C|K|R|F)/) then
229
+ @@cached_units[unary_unit] = (self.scalar == 1 ? self : unary_unit.unit)
230
+ end
231
+ @scalar.freeze
232
+ @numerator.freeze
233
+ @denominator.freeze
234
+ @base_scalar.freeze
235
+ @signature.freeze
236
+ @is_base.freeze
237
+ self
238
+ end
239
+
240
+ def kind
241
+ return @@KINDS[self.signature]
242
+ end
243
+
244
+ def self.cached
245
+ return @@cached_units
246
+ end
247
+
248
+ def self.base_unit_cache
249
+ return @@base_unit_cache
126
250
  end
127
251
 
128
252
  def to_unit
@@ -132,48 +256,49 @@ class Unit < Numeric
132
256
 
133
257
  # Returns 'true' if the Unit is represented in base units
134
258
  def is_base?
135
- return true if @signature == 400 && @numerator.size == 1 && @numerator[0] =~ /(celcius|kelvin|farenheit|rankine)/
259
+ return @is_base if defined? @is_base
260
+ return @is_base=true if @signature == 400 && @numerator.size == 1 && @numerator[0] =~ /(celcius|kelvin|farenheit|rankine)/
136
261
  n = @numerator + @denominator
137
- n.compact.each do |x|
138
- return false unless x == '<1>' ||
139
- (@@UNIT_VALUES[Regexp.escape(x)] &&
140
- @@UNIT_VALUES[Regexp.escape(x)][:denominator].nil? &&
141
- @@UNIT_VALUES[Regexp.escape(x)][:numerator].include?(Regexp.escape(x)))
262
+ for x in n.compact do
263
+ return @is_base=false unless x == UNITY || (@@BASE_UNITS.include?((x)))
142
264
  end
143
- return true
265
+ return @is_base = true
144
266
  end
145
267
 
146
268
  #convert to base SI units
147
269
  def to_base
148
270
  return self if self.is_base?
149
- # return self.to('degK') if self.units =~ /temp(C|K|F|R)/
271
+ cached = @@base_unit_cache[self.units] * self.scalar rescue nil
272
+ return cached if cached
273
+
150
274
  num = []
151
275
  den = []
152
- q = @scalar
153
- @numerator.compact.each do |unit|
154
- if @@PREFIX_VALUES[Regexp.escape(unit)]
155
- q *= @@PREFIX_VALUES[Regexp.escape(unit)]
276
+ q = 1
277
+ for unit in @numerator.compact do
278
+ if @@PREFIX_VALUES[unit]
279
+ q *= @@PREFIX_VALUES[unit]
156
280
  else
157
- q *= @@UNIT_VALUES[Regexp.escape(unit)][:scalar] if @@UNIT_VALUES[Regexp.escape(unit)]
158
- num << @@UNIT_VALUES[Regexp.escape(unit)][:numerator] if @@UNIT_VALUES[Regexp.escape(unit)] && @@UNIT_VALUES[Regexp.escape(unit)][:numerator]
159
- den << @@UNIT_VALUES[Regexp.escape(unit)][:denominator] if @@UNIT_VALUES[Regexp.escape(unit)] && @@UNIT_VALUES[Regexp.escape(unit)][:denominator]
281
+ q *= @@UNIT_VALUES[unit][:scalar] if @@UNIT_VALUES[unit]
282
+ num << @@UNIT_VALUES[unit][:numerator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:numerator]
283
+ den << @@UNIT_VALUES[unit][:denominator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:denominator]
160
284
  end
161
285
  end
162
- @denominator.compact.each do |unit|
163
- if @@PREFIX_VALUES[Regexp.escape(unit)]
164
- q /= @@PREFIX_VALUES[Regexp.escape(unit)]
286
+ for unit in @denominator.compact do
287
+ if @@PREFIX_VALUES[unit]
288
+ q /= @@PREFIX_VALUES[unit]
165
289
  else
166
- q /= @@UNIT_VALUES[Regexp.escape(unit)][:scalar] if @@UNIT_VALUES[Regexp.escape(unit)]
167
- den << @@UNIT_VALUES[Regexp.escape(unit)][:numerator] if @@UNIT_VALUES[Regexp.escape(unit)] && @@UNIT_VALUES[Regexp.escape(unit)][:numerator]
168
- num << @@UNIT_VALUES[Regexp.escape(unit)][:denominator] if @@UNIT_VALUES[Regexp.escape(unit)] && @@UNIT_VALUES[Regexp.escape(unit)][:denominator]
290
+ q /= @@UNIT_VALUES[unit][:scalar] if @@UNIT_VALUES[unit]
291
+ den << @@UNIT_VALUES[unit][:numerator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:numerator]
292
+ num << @@UNIT_VALUES[unit][:denominator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:denominator]
169
293
  end
170
294
  end
171
295
 
172
296
  num = num.flatten.compact
173
297
  den = den.flatten.compact
174
- num = ['<1>'] if num.empty?
175
-
176
- Unit.new(Unit.eliminate_terms(q,num,den))
298
+ num = UNITY_ARRAY if num.empty?
299
+ base= Unit.new(Unit.eliminate_terms(q,num,den))
300
+ @@base_unit_cache[self.units]=base
301
+ return base * @scalar
177
302
  end
178
303
 
179
304
  # Generate human readable output.
@@ -183,23 +308,29 @@ class Unit < Numeric
183
308
  # :ft - outputs in feet and inches (e.g., 6'4")
184
309
  # :lbs - outputs in pounds and ounces (e.g, 8 lbs, 8 oz)
185
310
  def to_s(target_units=nil)
186
- case target_units
187
- when :ft:
188
- inches = self.to("in").scalar
189
- "#{(inches / 12).truncate}\'#{(inches % 12).round}\""
190
- when :lbs:
191
- ounces = self.to("oz").scalar
192
- "#{(ounces / 16).truncate} lbs, #{(ounces % 16).round} oz"
193
- when String
194
- begin #first try a standard format string
195
- target_units =~ /(%[\w\d#+-.]*)*\s*(.+)*/
196
- return self.to($2).to_s($1) if $2
197
- "#{($1 || '%g') % @scalar || 0} #{self.units}".strip
198
- rescue #if that is malformed, try a time string
199
- return (Time.gm(0) + self).strftime(target_units)
200
- end
311
+ out = @output[target_units] rescue nil
312
+ if out
313
+ return out
201
314
  else
202
- "#{'%g' % @scalar} #{self.units}".strip
315
+ case target_units
316
+ when :ft:
317
+ inches = self.to("in").scalar
318
+ out = "#{(inches / 12).truncate}\'#{(inches % 12).round}\""
319
+ when :lbs:
320
+ ounces = self.to("oz").scalar
321
+ out = "#{(ounces / 16).truncate} lbs, #{(ounces % 16).round} oz"
322
+ when String
323
+ begin #first try a standard format string
324
+ target_units =~ /(%[\w\d#+-.]*)*\s*(.+)*/
325
+ out = $2 ? self.to($2).to_s($1) : "#{($1 || '%g') % @scalar || 0} #{self.units}".strip
326
+ rescue #if that is malformed, try a time string
327
+ out = (Time.gm(0) + self).strftime(target_units)
328
+ end
329
+ else
330
+ out = "#{'%g' % @scalar} #{self.units}".strip
331
+ end
332
+ @output = {target_units => out}
333
+ return out
203
334
  end
204
335
  end
205
336
 
@@ -209,8 +340,9 @@ class Unit < Numeric
209
340
  end
210
341
 
211
342
  # returns true if no associated units
343
+ # false, even if the units are "unitless" like 'radians, each, etc'
212
344
  def unitless?
213
- (@numerator == ['<1>'] && @denominator == ['<1>'])
345
+ (@numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY)
214
346
  end
215
347
 
216
348
  # Compare two Unit objects. Throws an exception if they are not of compatible types.
@@ -266,9 +398,8 @@ class Unit < Numeric
266
398
  def +(other)
267
399
  if Unit === other
268
400
  if self =~ other then
269
- a = self.to_base
270
- b = other.to_base
271
- Unit.new(:scalar=>(a.scalar + b.scalar), :numerator=>a.numerator, :denominator=>b.denominator).to(self)
401
+ q = self.zero? ? 1 : (self.scalar / self.base_scalar)
402
+ Unit.new(:scalar=>(self.base_scalar + other.base_scalar)*q, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
272
403
  else
273
404
  raise ArgumentError, "Incompatible Units"
274
405
  end
@@ -285,9 +416,8 @@ class Unit < Numeric
285
416
  def -(other)
286
417
  if Unit === other
287
418
  if self =~ other then
288
- a = self.to_base
289
- b = other.to_base
290
- Unit.new(:scalar=>(a.scalar - b.scalar), :numerator=>a.numerator, :denominator=>b.denominator).to(self)
419
+ q = self.zero? ? 1 : (self.scalar / self.base_scalar)
420
+ Unit.new(:scalar=>(self.base_scalar - other.base_scalar)*q, :numerator=>@numerator, :denominator=>@denominator, :signature=>@signature)
291
421
  else
292
422
  raise ArgumentError, "Incompatible Units"
293
423
  end
@@ -295,14 +425,19 @@ class Unit < Numeric
295
425
  other - self
296
426
  else
297
427
  x,y = coerce(other)
298
- x - y
428
+ y-x
299
429
  end
300
430
  end
301
431
 
302
432
  # Multiply two units.
303
433
  def *(other)
304
- if Unit === other
305
- Unit.new(Unit.eliminate_terms(@scalar*other.scalar, @numerator + other.numerator ,@denominator + other.denominator))
434
+ case other
435
+ when Unit
436
+ opts = Unit.eliminate_terms(@scalar*other.scalar, @numerator + other.numerator ,@denominator + other.denominator)
437
+ opts.merge!(:signature => @signature + other.signature)
438
+ Unit.new(opts)
439
+ when Numeric
440
+ Unit.new(:scalar=>@scalar*other, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
306
441
  else
307
442
  x,y = coerce(other)
308
443
  x * y
@@ -312,9 +447,15 @@ class Unit < Numeric
312
447
  # Divide two units.
313
448
  # Throws an exception if divisor is 0
314
449
  def /(other)
315
- if Unit === other
450
+ case other
451
+ when Unit
452
+ raise ZeroDivisionError if other.zero?
453
+ opts = Unit.eliminate_terms(@scalar/other.scalar, @numerator + other.denominator ,@denominator + other.numerator)
454
+ opts.merge!(:signature=> @signature - other.signature)
455
+ Unit.new(opts)
456
+ when Numeric
316
457
  raise ZeroDivisionError if other.zero?
317
- Unit.new(Unit.eliminate_terms(@scalar/other.scalar, @numerator + other.denominator ,@denominator + other.numerator))
458
+ Unit.new(:scalar=>@scalar/other, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
318
459
  else
319
460
  x,y = coerce(other)
320
461
  y / x
@@ -375,16 +516,16 @@ class Unit < Numeric
375
516
  vec = self.unit_signature_vector
376
517
  vec=vec.map {|x| x % n}
377
518
  raise ArgumentError, "Illegal root" unless vec.max == 0
378
- num = @numerator.clone
379
- den = @denominator.clone
519
+ num = @numerator.dup
520
+ den = @denominator.dup
380
521
 
381
- @numerator.uniq.each do |item|
522
+ for item in @numerator.uniq do
382
523
  x = num.find_all {|i| i==item}.size
383
524
  r = ((x/n)*(n-1)).to_int
384
525
  r.times {|x| num.delete_at(num.index(item))}
385
526
  end
386
527
 
387
- @denominator.uniq.each do |item|
528
+ for item in @denominator.uniq do
388
529
  x = den.find_all {|i| i==item}.size
389
530
  r = ((x/n)*(n-1)).to_int
390
531
  r.times {|x| den.delete_at(den.index(item))}
@@ -419,7 +560,6 @@ class Unit < Numeric
419
560
  return self if FalseClass === other
420
561
  if (Unit === other && other.units =~ /temp(K|C|R|F)/) || (String === other && other =~ /temp(K|C|R|F)/)
421
562
  raise ArgumentError, "Receiver is not a temperature unit" unless self.signature==400
422
- return self.to_base.to(other) unless self.is_base?
423
563
  start_unit = self.units
424
564
  target_unit = other.units rescue other
425
565
  q=case start_unit
@@ -452,7 +592,8 @@ class Unit < Numeric
452
592
  when 'tempR' : @scalar
453
593
  end
454
594
  else
455
- raise ArgumentError, "Unknown temperature conversion requested #{self.numerator}"
595
+ return self.to_base.to(other) unless self.is_base?
596
+ #raise ArgumentError, "Unknown temperature conversion requested #{self.numerator}"
456
597
  end
457
598
  target_unit =~ /temp(C|K|F|R)/
458
599
  Unit.new("#{q} deg#{$1}")
@@ -466,14 +607,14 @@ class Unit < Numeric
466
607
  raise ArgumentError, "Unknown target units"
467
608
  end
468
609
  raise ArgumentError, "Incompatible Units" unless self =~ target
469
- one = @numerator.map {|x| @@PREFIX_VALUES[Regexp.escape(x)] ? @@PREFIX_VALUES[Regexp.escape(x)] : x}.map {|i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[Regexp.escape(i)][:scalar] }.compact
470
- two = @denominator.map {|x| @@PREFIX_VALUES[Regexp.escape(x)] ? @@PREFIX_VALUES[Regexp.escape(x)] : x}.map {|i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[Regexp.escape(i)][:scalar] }.compact
610
+ one = @numerator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[i][:scalar] }.compact
611
+ two = @denominator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[i][:scalar] }.compact
471
612
  v = one.inject(1) {|product,n| product*n} / two.inject(1) {|product,n| product*n}
472
- one = target.numerator.map {|x| @@PREFIX_VALUES[Regexp.escape(x)] ? @@PREFIX_VALUES[Regexp.escape(x)] : x}.map {|x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[Regexp.escape(x)][:scalar] }.compact
473
- two = target.denominator.map {|x| @@PREFIX_VALUES[Regexp.escape(x)] ? @@PREFIX_VALUES[Regexp.escape(x)] : x}.map {|x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[Regexp.escape(x)][:scalar] }.compact
613
+ one = target.numerator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[x][:scalar] }.compact
614
+ two = target.denominator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[x][:scalar] }.compact
474
615
  y = one.inject(1) {|product,n| product*n} / two.inject(1) {|product,n| product*n}
475
616
  q = @scalar * v/y
476
- Unit.new(:scalar=>q, :numerator=>target.numerator, :denominator=>target.denominator)
617
+ Unit.new(:scalar=>q, :numerator=>target.numerator, :denominator=>target.denominator, :signature => target.signature)
477
618
  end
478
619
  end
479
620
  alias :>> :to
@@ -489,43 +630,46 @@ class Unit < Numeric
489
630
 
490
631
  # returns the 'unit' part of the Unit object without the scalar
491
632
  def units
492
- return "" if @numerator == ["<1>"] && @denominator == ["<1>"]
633
+ return "" if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY
634
+ return @unit_name unless @unit_name.nil?
493
635
  output_n = []
494
636
  output_d =[]
495
637
  num = @numerator.clone.compact
496
638
  den = @denominator.clone.compact
497
- if @numerator == ["<1>"]
639
+ if @numerator == UNITY_ARRAY
498
640
  output_n << "1"
499
641
  else
500
642
  num.each_with_index do |token,index|
501
- if token && @@PREFIX_VALUES[Regexp.escape(token)] then
502
- output_n << "#{@@OUTPUT_MAP[Regexp.escape(token)]}#{@@OUTPUT_MAP[Regexp.escape(num[index+1])]}"
643
+ if token && @@PREFIX_VALUES[token] then
644
+ output_n << "#{@@OUTPUT_MAP[token]}#{@@OUTPUT_MAP[num[index+1]]}"
503
645
  num[index+1]=nil
504
646
  else
505
- output_n << "#{@@OUTPUT_MAP[Regexp.escape(token)]}" if token
647
+ output_n << "#{@@OUTPUT_MAP[token]}" if token
506
648
  end
507
649
  end
508
650
  end
509
- if @denominator == ['<1>']
651
+ if @denominator == UNITY_ARRAY
510
652
  output_d = ['1']
511
653
  else
512
654
  den.each_with_index do |token,index|
513
- if token && @@PREFIX_VALUES[Regexp.escape(token)] then
514
- output_d << "#{@@OUTPUT_MAP[Regexp.escape(token)]}#{@@OUTPUT_MAP[Regexp.escape(den[index+1])]}"
655
+ if token && @@PREFIX_VALUES[token] then
656
+ output_d << "#{@@OUTPUT_MAP[token]}#{@@OUTPUT_MAP[den[index+1]]}"
515
657
  den[index+1]=nil
516
658
  else
517
- output_d << "#{@@OUTPUT_MAP[Regexp.escape(token)]}" if token
659
+ output_d << "#{@@OUTPUT_MAP[token]}" if token
518
660
  end
519
661
  end
520
662
  end
521
663
  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]}" : ''))}
522
664
  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]}" : ''))}
523
- "#{on.join('*')}#{od == ['1'] ? '': '/'+od.join('*')}".strip
665
+ out = "#{on.join('*')}#{od == ['1'] ? '': '/'+od.join('*')}".strip
666
+ @unit_name = out unless self.kind == :temperature
667
+ return out
524
668
  end
525
669
 
526
670
  # negates the scalar of the Unit
527
671
  def -@
528
- Unit.new([-@scalar,@numerator,@denominator])
672
+ Unit.new(-@scalar,@numerator,@denominator)
529
673
  end
530
674
 
531
675
  # returns abs of scalar, without the units
@@ -534,18 +678,18 @@ class Unit < Numeric
534
678
  end
535
679
 
536
680
  def ceil
537
- Unit.new([@scalar.ceil, @numerator, @denominator])
681
+ Unit.new(@scalar.ceil, @numerator, @denominator)
538
682
  end
539
683
 
540
684
  def floor
541
- Unit.new([@scalar.floor, @numerator, @denominator])
685
+ Unit.new(@scalar.floor, @numerator, @denominator)
542
686
  end
543
687
 
544
688
  # changes internal scalar to an integer, but retains the units
545
689
  # if unitless, returns an int
546
690
  def to_int
547
691
  return @scalar.to_int if unitless?
548
- Unit.new([@scalar.to_int, @numerator, @denominator])
692
+ Unit.new(@scalar.to_int, @numerator, @denominator)
549
693
  end
550
694
 
551
695
  # Tries to make a Time object from current unit
@@ -557,7 +701,7 @@ class Unit < Numeric
557
701
  alias :truncate :to_int
558
702
 
559
703
  def round
560
- Unit.new([@scalar.round, @numerator, @denominator])
704
+ Unit.new(@scalar.round, @numerator, @denominator)
561
705
  end
562
706
 
563
707
  # true if scalar is zero
@@ -567,16 +711,16 @@ class Unit < Numeric
567
711
 
568
712
  # '5 min'.unit.ago
569
713
  def ago
570
- Time.now - self rescue DateTime.now - self
714
+ self.before
571
715
  end
572
716
 
573
717
  # '5 min'.before(time)
574
718
  def before(time_point = ::Time.now)
575
719
  raise ArgumentError, "Must specify a Time" unless time_point
576
720
  if String === time_point
577
- time_point.time - self
721
+ time_point.time - self rescue time_point.datetime - self
578
722
  else
579
- time_point - self
723
+ time_point - self rescue time_point.to_datetime - self
580
724
  end
581
725
  end
582
726
  alias :before_now :before
@@ -585,7 +729,7 @@ class Unit < Numeric
585
729
  def since(time_point = ::Time.now)
586
730
  case time_point
587
731
  when Time: (Time.now - time_point).unit('s').to(self)
588
- when DateTime: (DateTime.now - time_point).unit('d').to(self)
732
+ when DateTime, Date: (DateTime.now - time_point).unit('d').to(self)
589
733
  when String:
590
734
  (DateTime.now - time_point.time(:context=>:past)).unit('d').to(self)
591
735
  else
@@ -597,7 +741,7 @@ class Unit < Numeric
597
741
  def until(time_point = ::Time.now)
598
742
  case time_point
599
743
  when Time: (time_point - Time.now).unit('s').to(self)
600
- when DateTime: (time_point - DateTime.now).unit('d').to(self)
744
+ when DateTime, Date: (time_point - DateTime.now).unit('d').to(self)
601
745
  when String:
602
746
  r = (time_point.time(:context=>:future) - DateTime.now)
603
747
  Time === time_point.time ? r.unit('s').to(self) : r.unit('d').to(self)
@@ -610,9 +754,9 @@ class Unit < Numeric
610
754
  def from(time_point = ::Time.now)
611
755
  raise ArgumentError, "Must specify a Time" unless time_point
612
756
  if String === time_point
613
- time_point.time + self
757
+ time_point.time + self rescue time_point.datetime + self
614
758
  else
615
- time_point + self
759
+ time_point + self rescue time_point.to_datetime + self
616
760
  end
617
761
  end
618
762
  alias :after :from
@@ -621,7 +765,7 @@ class Unit < Numeric
621
765
  def succ
622
766
  raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i
623
767
  q = @scalar.to_i.succ
624
- Unit.new([q, @numerator, @denominator])
768
+ Unit.new(q, @numerator, @denominator)
625
769
  end
626
770
 
627
771
  # Protected and Private Functions that should only be called from this class
@@ -629,6 +773,7 @@ class Unit < Numeric
629
773
 
630
774
 
631
775
  def update_base_scalar
776
+ return @base_scalar unless @base_scalar.nil?
632
777
  if self.is_base?
633
778
  @base_scalar = @scalar
634
779
  @signature = unit_signature
@@ -653,37 +798,42 @@ class Unit < Numeric
653
798
  def unit_signature_vector
654
799
  return self.to_base.unit_signature_vector unless self.is_base?
655
800
  result = self
656
- y = [:length, :time, :temperature, :mass, :current, :substance, :luminosity, :currency, :memory, :angle]
657
- vector = Array.new(y.size,0)
658
- y.each_with_index do |units,index|
659
- vector[index] = result.numerator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
660
- vector[index] -= result.denominator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
801
+ vector = Array.new(SIGNATURE_VECTOR.size,0)
802
+ for element in @numerator
803
+ if r=@@ALL_UNIT_DEFINITIONS[element]
804
+ n = SIGNATURE_VECTOR.index(r[2])
805
+ vector[n] = vector[n] + 1 if n
806
+ end
807
+ end
808
+ for element in @denominator
809
+ if r=@@ALL_UNIT_DEFINITIONS[element]
810
+ n = SIGNATURE_VECTOR.index(r[2])
811
+ vector[n] = vector[n] - 1 if n
812
+ end
661
813
  end
662
814
  vector
663
815
  end
664
816
 
665
817
  def replace_temperature
666
- return self unless self.signature == 400 && self.units =~ /temp(R|K|F|C)/
818
+ return self unless self.kind == :temperature && self.units =~ /temp(R|K|F|C)/
667
819
  un = $1
668
- target = self.units
669
820
  @numerator = case un
670
- when 'R' : ['<rankine>']
671
- when 'C' : ['<celcius>']
672
- when 'F' : ['<farenheit>']
673
- when 'K' : ['<kelvin>']
821
+ when 'R' : RANKINE
822
+ when 'C' : CELCIUS
823
+ when 'F' : FARENHEIT
824
+ when 'K' : KELVIN
674
825
  end
826
+ @unit_name = nil
675
827
  r= self.to("tempK")
676
- @numerator = r.numerator
677
- @denominator = r.denominator
678
- @scalar = r.scalar
828
+ copy(r)
679
829
  end
680
-
681
-
830
+
682
831
  private
683
832
 
684
833
  def initialize_copy(other)
685
- @numerator = other.numerator.clone
686
- @denominator = other.denominator.clone
834
+ @numerator = other.numerator.dup
835
+ @denominator = other.denominator.dup
836
+
687
837
  end
688
838
 
689
839
  # calculates the unit signature id for use in comparing compatible units and simplification
@@ -695,17 +845,18 @@ class Unit < Numeric
695
845
  # 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.
696
846
  #
697
847
  def unit_signature
848
+ return @signature unless @signature.nil?
698
849
  vector = unit_signature_vector
699
850
  vector.each_with_index {|item,index| vector[index] = item * 20**index}
700
851
  @signature=vector.inject(0) {|sum,n| sum+n}
701
852
  end
702
853
 
703
854
  def self.eliminate_terms(q, n, d)
704
- num = n.clone
705
- den = d.clone
855
+ num = n.dup
856
+ den = d.dup
706
857
 
707
- num.delete_if {|v| v == '<1>'}
708
- den.delete_if {|v| v == '<1>'}
858
+ num.delete_if {|v| v == UNITY}
859
+ den.delete_if {|v| v == UNITY}
709
860
  combined = Hash.new(0)
710
861
 
711
862
  i = 0
@@ -718,7 +869,7 @@ class Unit < Numeric
718
869
  k = num[i]
719
870
  i += 1
720
871
  end
721
- combined[k] += 1 unless k.nil? || k == '<1>'
872
+ combined[k] += 1 unless k.nil? || k == UNITY
722
873
  end
723
874
 
724
875
  j = 0
@@ -731,19 +882,19 @@ class Unit < Numeric
731
882
  k = den[j]
732
883
  j += 1
733
884
  end
734
- combined[k] -= 1 unless k.nil? || k == '<1>'
885
+ combined[k] -= 1 unless k.nil? || k == UNITY
735
886
  end
736
887
 
737
888
  num = []
738
889
  den = []
739
- combined.each do |key,value|
890
+ for key, value in combined do
740
891
  case
741
892
  when value > 0 : value.times {num << key}
742
893
  when value < 0 : value.abs.times {den << key}
743
894
  end
744
895
  end
745
- num = ["<1>"] if num.empty?
746
- den = ["<1>"] if den.empty?
896
+ num = UNITY_ARRAY if num.empty?
897
+ den = UNITY_ARRAY if den.empty?
747
898
  {:scalar=>q, :numerator=>num.flatten.compact, :denominator=>den.flatten.compact}
748
899
  end
749
900
 
@@ -759,36 +910,47 @@ class Unit < Numeric
759
910
  # "GPa" -- creates a unit with scalar 1 with units 'GPa'
760
911
  # 6'4" -- recognized as 6 feet + 4 inches
761
912
  # 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
762
- def parse(unit_string="0")
763
- @numerator = ['<1>']
764
- @denominator = ['<1>']
913
+ def parse(passed_unit_string="0")
914
+
915
+ unit_string = passed_unit_string.dup
916
+ if unit_string =~ /\$\s*(#{NUMBER_REGEX})/
917
+ unit_string = "#{$1} USD"
918
+ end
919
+ if unit_string =~ /(#{SCI_NUMBER})\s*%/
920
+ unit_string = "#{$1} percent"
921
+ end
922
+
923
+ unit_string =~ NUMBER_REGEX
924
+ unit = @@cached_units[$2]
925
+ mult = ($1.empty? ? 1.0 : $1.to_f) rescue 1.0
926
+ if unit
927
+ copy(unit)
928
+ @scalar *= mult
929
+ @base_scalar *= mult
930
+ return self
931
+ end
932
+
765
933
  unit_string.gsub!(/[<>]/,"")
766
934
 
767
935
  # Special processing for unusual unit strings
768
936
  # feet -- 6'5"
769
- feet, inches = unit_string.scan(/(\d+)\s*(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inches)/)[0]
937
+ feet, inches = unit_string.scan(FEET_INCH_REGEX)[0]
770
938
  if (feet && inches)
771
939
  result = Unit.new("#{feet} ft") + Unit.new("#{inches} inches")
772
- @scalar = result.scalar
773
- @numerator = result.numerator
774
- @denominator = result.denominator
775
- @base_scalar = result.base_scalar
940
+ copy(result)
776
941
  return self
777
942
  end
778
943
 
779
944
  # weight -- 8 lbs 12 oz
780
- pounds, oz = unit_string.scan(/(\d+)\s*(?:#|lbs|pounds)+[\s,]*(\d+)\s*(?:oz|ounces)/)[0]
945
+ pounds, oz = unit_string.scan(LBS_OZ_REGEX)[0]
781
946
  if (pounds && oz)
782
947
  result = Unit.new("#{pounds} lbs") + Unit.new("#{oz} oz")
783
- @scalar = result.scalar
784
- @numerator = result.numerator
785
- @denominator = result.denominator
786
- @base_scalar = result.base_scalar
948
+ copy(result)
787
949
  return self
788
950
  end
789
- @scalar, top, bottom = unit_string.scan(/([\dEe+.-]*)\s*([^\/]*)\/*(.+)*/)[0] #parse the string into parts
951
+ @scalar, top, bottom = unit_string.scan(UNIT_STRING_REGEX)[0] #parse the string into parts
790
952
 
791
- top.scan(/([^ \*]+)(?:\^|\*\*)([\d-]+)/).each do |item|
953
+ top.scan(TOP_REGEX).each do |item|
792
954
  n = item[1].to_i
793
955
  x = "#{item[0]} "
794
956
  case
@@ -796,33 +958,27 @@ class Unit < Numeric
796
958
  when n<0 : bottom = "#{bottom} #{x * -n}"; top.gsub!(/#{item[0]}(\^|\*\*)#{n}/,"")
797
959
  end
798
960
  end
799
-
800
- bottom.gsub!(/([^* ]+)(?:\^|\*\*)(\d+)/) {|s| "#{$1} " * $2.to_i} if bottom
801
- if @scalar.empty?
802
- if top =~ /[\dEe+.-]+/
803
- @scalar = top.to_f # need this for 'number only' initialization
804
- else
805
- @scalar = 1 # need this for 'unit only' intialization
806
- end
807
- else
808
- @scalar = @scalar.to_f
809
- end
810
-
811
- @numerator = top.scan(/(#{@@PREFIX_REGEX})*?(#{@@UNIT_REGEX})\b/).delete_if {|x| x.empty?}.compact if top
812
- @denominator = bottom.scan(/(#{@@PREFIX_REGEX})*?(#{@@UNIT_REGEX})\b/).delete_if {|x| x.empty?}.compact if bottom
961
+ bottom.gsub!(BOTTOM_REGEX) {|s| "#{$1} " * $2.to_i} if bottom
962
+ @scalar = @scalar.to_f unless @scalar.nil? || @scalar.empty?
963
+ @scalar = 1 unless @scalar.kind_of? Numeric
964
+
965
+ @numerator ||= UNITY_ARRAY
966
+ @denominator ||= UNITY_ARRAY
967
+ @numerator = top.scan(@@UNIT_MATCH_REGEX).delete_if {|x| x.empty?}.compact if top
968
+ @denominator = bottom.scan(@@UNIT_MATCH_REGEX).delete_if {|x| x.empty?}.compact if bottom
969
+ us = "#{(top || '' + bottom || '')}".to_s.gsub(@@UNIT_MATCH_REGEX,'').gsub(/[\d\*, "'_^\/\$]/,'')
970
+ raise( ArgumentError, "'#{passed_unit_string}' Unit not recognized (#{us})") unless us.empty?
813
971
 
814
972
  @numerator = @numerator.map do |item|
815
- item.map {|x| Regexp.escape(x) if x}
816
973
  @@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]]
817
974
  end.flatten.compact.delete_if {|x| x.empty?}
818
975
 
819
976
  @denominator = @denominator.map do |item|
820
- item.map {|x| Regexp.escape(x) if x}
821
977
  @@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]]
822
978
  end.flatten.compact.delete_if {|x| x.empty?}
823
979
 
824
- @numerator = ['<1>'] if @numerator.empty?
825
- @denominator = ['<1>'] if @denominator.empty?
980
+ @numerator = UNITY_ARRAY if @numerator.empty?
981
+ @denominator = UNITY_ARRAY if @denominator.empty?
826
982
  self
827
983
  end
828
984
  end
@@ -832,9 +988,11 @@ end
832
988
  # Date.today + U"1 week" => gives today+1 week
833
989
  class Date
834
990
  alias :unit_date_add :+
835
- def +(unit)
991
+ def +unit
836
992
  case unit
837
- when Unit: unit_date_add(unit.to('day').scalar)
993
+ when Unit:
994
+ unit = unit.to('d').round if ['y', 'decade', 'century'].include? unit.units
995
+ unit_date_add(unit.to('day').scalar)
838
996
  when Time: unit_date_add(unit.to_datetime)
839
997
  else
840
998
  unit_date_add(unit)
@@ -842,9 +1000,11 @@ class Date
842
1000
  end
843
1001
 
844
1002
  alias :unit_date_sub :-
845
- def -(unit)
1003
+ def -unit
846
1004
  case unit
847
- when Unit: unit_date_sub(unit.to('day').scalar)
1005
+ when Unit:
1006
+ unit = unit.to('d').round if ['y', 'decade', 'century'].include? unit.units
1007
+ unit_date_sub(unit.to('day').scalar)
848
1008
  when Time: unit_date_sub(unit.to_datetime)
849
1009
  else
850
1010
  unit_date_sub(unit)
@@ -864,9 +1024,10 @@ class Date
864
1024
  end
865
1025
 
866
1026
  class Object
867
- def Unit(other)
1027
+ def Unit(*other)
868
1028
  other.to_unit
869
1029
  end
1030
+
870
1031
  alias :U :Unit
871
1032
  alias :u :Unit
872
1033
  end
@@ -874,7 +1035,7 @@ end
874
1035
  # make a unitless unit with a given scalar
875
1036
  class Numeric
876
1037
  def to_unit(other = nil)
877
- other ? Unit.new(self) * Unit.new(other) : Unit.new(self)
1038
+ other ? Unit.new(self, other) : Unit.new(self)
878
1039
  end
879
1040
  alias :unit :to_unit
880
1041
  alias :u :to_unit
@@ -893,7 +1054,7 @@ end
893
1054
  # make a string into a unit
894
1055
  class String
895
1056
  def to_unit(other = nil)
896
- other ? Unit.new(self) >> other : Unit.new(self)
1057
+ other ? Unit.new(self).to(other) : Unit.new(self)
897
1058
  end
898
1059
  alias :unit :to_unit
899
1060
  alias :u :to_unit
@@ -961,6 +1122,16 @@ class String
961
1122
  raise RuntimeError, "Invalid Time String" if r == DateTime.new
962
1123
  return r
963
1124
  end
1125
+
1126
+ def to_date(options={})
1127
+ begin
1128
+ r = Chronic.parse(self,options).to_date
1129
+ rescue
1130
+ r = Date.civil(*ParseDate.parsedate(self)[0..5].compact)
1131
+ end
1132
+ raise RuntimeError, 'Invalid Date String' if r == Date.new
1133
+ return r
1134
+ end
964
1135
 
965
1136
  def datetime(options = {})
966
1137
  self.to_datetime(options) rescue self.to_time(options)
@@ -994,6 +1165,10 @@ class Time
994
1165
  DateTime.civil(1970,1,1)+(self.to_f+self.gmt_offset)/86400
995
1166
  end
996
1167
 
1168
+ def to_date
1169
+ Date.civil(1970,1,1)+(self.to_f+self.gmt_offset)/86400
1170
+ end
1171
+
997
1172
  def +(other)
998
1173
  case other
999
1174
  when Unit: unit_add(other.to('s').scalar)
@@ -1085,4 +1260,6 @@ module Math
1085
1260
  module_function :tan
1086
1261
  module_function :unit_tanh
1087
1262
  module_function :tanh
1088
- end
1263
+ end
1264
+
1265
+ Unit.setup
data/lib/units.rb CHANGED
@@ -71,6 +71,7 @@ UNIT_DEFINITIONS = {
71
71
  #area
72
72
  '<hectare>'=>[%w{hectare}, 10000, :area, %w{<meter> <meter>}],
73
73
  '<acre>'=>[%w(acre acres), 4046.85642, :area, %w{<meter> <meter>}],
74
+ '<sqft>'=>[%w(sqft), 1, :area, %w{<feet> <feet>}],
74
75
 
75
76
  #volume
76
77
  '<liter>' => [%w{l L liter liters litre litres}, 0.001, :volume, %w{<meter> <meter> <meter>}],
@@ -87,6 +88,9 @@ UNIT_DEFINITIONS = {
87
88
  '<mph>' => [%w{mph}, 0.44704, :speed, %w{<meter>}, %w{<second>}],
88
89
  '<knot>' => [%w{kn knot knots}, 0.514444444, :speed, %w{<meter>}, %w{<second>}],
89
90
  '<fps>' => [%w{fps}, 0.3048, :speed, %w{<meter>}, %w{<second>}],
91
+
92
+ #acceleration
93
+ '<gee>' => [%w{gee}, 9.80655, :acceleration, %w{<meter>}, %w{<second> <second>}],
90
94
 
91
95
  #temperature_difference
92
96
  '<kelvin>' => [%w{degK kelvin Kelvin}, 1.0, :temperature, %w{<kelvin>}],
@@ -184,15 +188,15 @@ UNIT_DEFINITIONS = {
184
188
  '<steradian>' => [%w{sr steradian steradians}, 1.0, :solid_angle, %w{<steradian>}],
185
189
 
186
190
  #rotation
187
- '<rotation>' => [%w{rotation}, 2.0*Math::PI, :rotation, %w{<radian>}],
188
- '<rpm>' =>[%w{rpm}, 2.0*Math::PI / 60.0, :rotation, %w{<radian>}, %w{<second>}],
191
+ '<rotation>' => [%w{rotation}, 2.0*Math::PI, :angle, %w{<radian>}],
192
+ '<rpm>' =>[%w{rpm}, 2.0*Math::PI / 60.0, :angular_velocity, %w{<radian>}, %w{<second>}],
189
193
 
190
194
  #memory
191
195
  '<byte>' =>[%w{B byte}, 1.0, :memory, %w{<byte>}],
192
196
  '<bit>' =>[%w{b bit}, 0.125, :memory, %w{<byte>}],
193
197
 
194
- #money
195
- '<dollar>'=>[['$','dollar','USD'], 1.0, :currency, %w{<dollar>}],
198
+ #currency
199
+ '<dollar>'=>[%w{USD dollar}, 1.0, :currency, %w{<dollar>}],
196
200
  '<cents>' =>[%w{cents}, 0.01, :currency, %w{<dollar>}],
197
201
 
198
202
  #luminosity
@@ -204,25 +208,29 @@ UNIT_DEFINITIONS = {
204
208
  '<watt>' => [%w{W watt watts}, 1.0, :power, %w{<kilogram> <meter> <meter>}, %w{<second> <second> <second>}],
205
209
  '<horsepower>' => [%w{hp horsepower}, 745.699872, :power, %w{<kilogram> <meter> <meter>}, %w{<second> <second> <second>}],
206
210
 
207
- #radition
211
+ #radiation
208
212
  '<gray>' => [%w{Gy gray grays}, 1.0, :radiation, %w{<meter> <meter>}, %w{<second> <second>}],
209
213
  '<roentgen>' => [%w{R roentgen}, 0.009330, :radiation, %w{<meter> <meter>}, %w{<second> <second>}],
210
214
  '<sievert>' => [%w{Sv sievert sieverts}, 1.0, :radiation, %w{<meter> <meter>}, %w{<second> <second>}],
211
215
  '<becquerel>' => [%w{Bq bequerel bequerels}, 1.0, :radiation, %w{<1>},%w{<second>}],
212
216
  '<curie>' => [%w{Ci curie curies}, 3.7e10, :radiation, %w{<1>},%w{<second>}],
213
- '<cpm>' => [%w{cpm}, 1.0/60.0, :radiation, %w{<1>},%w{<second>}],
214
- '<dpm>' => [%w{dpm}, 1.0/60.0, :radiation, %w{<1>},%w{<second>}],
217
+
218
+ # rate
219
+ '<cpm>' => [%w{cpm}, 1.0/60.0, :rate, %w{<count>},%w{<second>}],
220
+ '<dpm>' => [%w{dpm}, 1.0/60.0, :rate, %w{<count>},%w{<second>}],
221
+ '<bpm>' => [%w{bpm}, 1.0/60.0, :rate, %w{<count>},%w{<second>}],
215
222
 
216
223
  #other
224
+ '<cell>' => [%w{cells cell}, 1, :counting, %w{<each>}],
217
225
  '<each>' => [%w{each}, 1.0, :counting, %w{<each>}],
218
226
  '<count>' => [%w{count}, 1.0, :counting, %w{<each>}],
219
227
  '<base-pair>' => [%w{bp}, 1.0, :counting, %w{<each>}],
220
228
  '<nucleotide>' => [%w{nt}, 1.0, :counting, %w{<each>}],
221
- '<molecule>' => [%w{molecule molecules}, 1.0, :counting, %w{<each>}],
229
+ '<molecule>' => [%w{molecule molecules}, 1.0, :counting, %w{<1>}],
222
230
  '<dozen>' => [%w{doz dz dozen},12.0,:prefix_only, %w{<each>}],
223
- '<percent>'=> [%w{% percent}, 0.01, :prefix_only, %w{<centi>}],
224
- '<ppm>' => [%w{ppm},1e-6,:prefix_only, %w{<micro>}],
225
- '<ppt>' => [%w{ppt},1e-9,:prefix_only, %w{<nano>}],
231
+ '<percent>'=> [%w{% percent}, 0.01, :prefix_only, %w{<1>}],
232
+ '<ppm>' => [%w{ppm},1e-6,:prefix_only, %w{<1>}],
233
+ '<ppt>' => [%w{ppt},1e-9,:prefix_only, %w{<1>}],
226
234
  '<gross>' => [%w{gr gross},144.0, :prefix_only, %w{<dozen> <dozen>}],
227
235
  '<decibel>' => [%w{dB decibel decibels}, 1.0, :logarithmic, %w{<decibel>}]
228
236
 
@@ -1,13 +1,12 @@
1
1
  require 'test/unit'
2
- require 'rubygems'
3
2
  require 'ruby-units'
4
- require 'yaml'
5
3
  require 'uncertain'
4
+ require 'rubygems'
5
+ require 'yaml'
6
6
  require 'chronic'
7
7
 
8
8
  class Unit < Numeric
9
9
  @@USER_DEFINITIONS = {'<inchworm>' => [%w{inworm inchworm}, 0.0254, :length, %w{<meter>} ],
10
- '<cell>' => [%w{cells cell}, 1, :counting, %w{<each>}],
11
10
  '<habenero>' => [%{degH}, 100, :temperature, %w{<celcius>}]}
12
11
  Unit.setup
13
12
  end
@@ -226,7 +225,7 @@ class TestRubyUnits < Test::Unit::TestCase
226
225
  end
227
226
 
228
227
  def test_create_from_array
229
- unit1 = Unit.new([1, "mm^2", "ul^2"])
228
+ unit1 = Unit.new(1, "mm^2", "ul^2")
230
229
  assert_equal 1, unit1.scalar
231
230
  assert_equal ['<milli>','<meter>','<milli>','<meter>'], unit1.numerator
232
231
  assert_equal ['<micro>','<liter>','<micro>','<liter>'], unit1.denominator
@@ -712,8 +711,28 @@ class TestRubyUnits < Test::Unit::TestCase
712
711
  end
713
712
 
714
713
  def test_format
715
- assert_equal "%0.2f" % "1 mm".unit, "1.00 mm"
716
-
714
+ assert_equal "%0.2f" % "1 mm".unit, "1.00 mm"
715
+ end
716
+
717
+ def test_bad_units
718
+ assert_raises(ArgumentError) { '1 doohickey / thingamabob'.unit}
719
+ assert_raises(ArgumentError) { '1 minimeter'.unit}
717
720
  end
718
721
 
722
+ def test_currency
723
+ assert_nothing_raised {a = "$1".unit}
724
+ end
725
+
726
+ def test_kind
727
+ a = "1 mm".unit
728
+ assert_equal a.kind, :length
729
+ end
730
+
731
+ def test_percent
732
+ assert_nothing_raised {
733
+ z = "1 percent".unit
734
+ a = "1%".unit
735
+ b = "0.01%".unit
736
+ }
737
+ end
719
738
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: ruby-units
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.2.3
7
- date: 2006-09-22 00:00:00 -04:00
6
+ version: 0.3.1
7
+ date: 2006-10-02 00:00:00 -04:00
8
8
  summary: A model that performs unit conversions and unit math
9
9
  require_paths:
10
10
  - lib