ruby-units 0.2.3 → 0.3.1

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/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