unit 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 Daniel Mendler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
@@ -1,484 +1,5 @@
1
- # encoding: utf-8
2
- require 'yaml'
3
-
4
- class Unit < Numeric
5
- VERSION = '0.2.1'
6
-
7
- attr_reader :numerator, :denominator, :unit, :normalized, :system
8
-
9
- def initialize(numerator, denominator, unit, system)
10
- @system = system
11
- @numerator = numerator
12
- @denominator = denominator
13
- @unit = unit.dup
14
- @normalized = nil
15
- reduce!
16
- end
17
-
18
- def initialize_copy(other)
19
- @system = other.system
20
- @numerator = other.numerator
21
- @denominator = other.denominator
22
- @unit = other.unit.dup
23
- @normalized = other.normalized
24
- end
25
-
26
- # Converts to base units
27
- def normalize
28
- @normalized ||= dup.normalize!
29
- end
30
-
31
- # Converts to base units
32
- def normalize!
33
- if @normalized != self
34
- begin
35
- last_unit = @unit
36
- @unit = []
37
- last_unit.each do |prefix, unit, exp|
38
- if prefix != :one
39
- if exp >= 0
40
- @numerator *= @system.prefix[prefix][:value] ** exp
41
- else
42
- @denominator *= @system.prefix[prefix][:value] ** -exp
43
- end
44
- end
45
- if @system.unit[unit]
46
- @unit += Unit.power_unit(@system.unit[unit][:def], exp)
47
- else
48
- @unit << [:one, unit, exp]
49
- end
50
- end
51
- end while last_unit != @unit
52
- reduce!
53
- @normalized = self
54
- end
55
- self
56
- end
57
-
58
- def *(other)
59
- a, b = coerce(other)
60
- Unit.new(a.numerator * b.numerator, a.denominator * b.denominator, a.unit + b.unit, system)
61
- end
62
-
63
- def /(other)
64
- a, b = coerce(other)
65
- Unit.new(a.numerator * b.denominator, a.denominator * b.numerator, a.unit + Unit.power_unit(b.unit, -1), system)
66
- end
67
-
68
- def +(other)
69
- raise TypeError, 'Incompatible units' if !compatible?(other)
70
- a, b = coerce(other)
71
- a, b = a.normalize, b.normalize
72
- Unit.new(a.numerator * b.denominator + b.numerator * a.denominator, a.denominator * b.denominator, a.unit, system).in(self)
73
- end
74
-
75
- def **(exp)
76
- raise TypeError if Unit === exp
77
- Unit.new(numerator ** exp, denominator ** exp, Unit.power_unit(unit, exp), system)
78
- end
79
-
80
- def -(other)
81
- self + (-other)
82
- end
83
-
84
- def -@
85
- Unit.new(-numerator, denominator, unit, system)
86
- end
87
-
88
- def ==(other)
89
- a, b = coerce(other)
90
- a, b = a.normalize, b.normalize
91
- a.numerator == b.numerator && a.denominator == b.denominator && a.unit == b.unit
92
- end
93
-
94
- # Number without dimension
95
- def dimensionless?
96
- normalize.unit.empty?
97
- end
98
-
99
- alias unitless? dimensionless?
100
-
101
- # Compatible units can be added
102
- def compatible?(other)
103
- a, b = coerce(other)
104
- a, b = a.normalize, b.normalize
105
- (a.unit == b.unit) || (a == 0 || b == 0)
106
- end
107
-
108
- alias compatible_with? compatible?
109
-
110
- # Convert to other unit
111
- def in(unit)
112
- unit = unit.to_unit(system)
113
- (self / unit).normalize * unit
114
- end
115
-
116
- def inspect
117
- unit.empty? ? %{Unit("#{number_string}")} : %{Unit("#{number_string} #{unit_string('.')}")}
118
- end
119
-
120
- def to_s
121
- unit.empty? ? number_string : "#{number_string} #{unit_string('·')}"
122
- end
123
-
124
- def to_tex
125
- unit.empty? ? number_string : "\SI{#{number_string}}{#{unit_string('.')}}"
126
- end
127
-
128
- def to_i
129
- (@numerator / @denominator).to_i
130
- end
131
-
132
- def to_f
133
- @numerator.to_f / @denominator.to_f
134
- end
135
-
136
- def approx
137
- to_f.unit(unit)
138
- end
139
-
140
- def to_unit(system = nil)
141
- system ||= Unit::System::DEFAULT
142
- raise TypeError, 'Different unit system' if @system != system
143
- self
144
- end
145
-
146
- def coerce(val)
147
- raise TypeError, 'No unit support' if !val.respond_to? :to_unit
148
- [self, val.to_unit(system)]
149
- end
150
-
151
- def method_missing(name)
152
- if name.to_s[0..2] == 'in_'
153
- self.in(Unit.method_name_to_unit(name))
154
- else
155
- super
156
- end
157
- end
158
-
159
- def self.method_name_to_unit(name)
160
- name.to_s.sub(/^in_/, '').sub(/^per_/, '1/').gsub('_per_', '/').gsub('_', ' ')
161
- end
162
-
163
- private
164
-
165
- def number_string
166
- @numerator.to_s << (@denominator == 1 ? '' : "/#{@denominator}")
167
- end
168
-
169
- def unit_string(sep)
170
- (unit_list(@unit.select {|prefix, name, exp| exp >= 0 }) +
171
- unit_list(@unit.select {|prefix, name, exp| exp < 0 })).join(sep)
172
- end
173
-
174
- def unit_list(list)
175
- units = []
176
- list.each do |prefix, name, exp|
177
- unit = ''
178
- unit << (@system.prefix[prefix] ? @system.prefix[prefix][:symbol] : prefix.to_s) if prefix != :one
179
- unit << (@system.unit[name] ? @system.unit[name][:symbol] : name.to_s)
180
- unit << '^' << exp.to_s if exp != 1
181
- units << unit
182
- end
183
- units.sort
184
- end
185
-
186
- def self.power_unit(unit, pow)
187
- unit.map {|prefix, name, exp| [prefix, name, exp * pow] }
188
- end
189
-
190
- # Reduce units and prefixes
191
- def reduce!
192
- # Remove numbers from units
193
- numbers = @unit.select {|prefix, unit, exp| Numeric === unit }
194
- @unit -= numbers
195
- numbers.each do |prefix, number, exp|
196
- raise RuntimeError, 'Numeric unit with prefix' if prefix != :one
197
- if exp >= 0
198
- @numerator *= number ** exp
199
- else
200
- @denominator *= number ** -exp
201
- end
202
- end
203
-
204
- # Reduce number
205
- if Integer === @numerator && Integer === @denominator
206
- r = Rational(@numerator, @denominator)
207
- @numerator = r.numerator
208
- @denominator = r.denominator
209
- else
210
- r = @numerator / @denominator
211
- if Rational === r
212
- @numerator = r.numerator
213
- @denominator = r.denominator
214
- else
215
- @numerator = r
216
- @denominator = 1
217
- end
218
- end
219
-
220
- if @numerator == 0
221
- @denominator = 1
222
- @unit.clear
223
- end
224
-
225
- # Reduce units
226
- @unit.sort!
227
- i, current = 1, 0
228
- while i < @unit.size do
229
- while i < @unit.size && @unit[current][0] == @unit[i][0] && @unit[current][1] == @unit[i][1]
230
- @unit[current] = @unit[current].dup
231
- @unit[current][2] += @unit[i][2]
232
- i += 1
233
- end
234
- if @unit[current][2] == 0
235
- @unit.slice!(current, i - current)
236
- else
237
- @unit.slice!(current + 1, i - current - 1)
238
- current += 1
239
- end
240
- i = current + 1
241
- end
242
-
243
- # Reduce prefixes
244
- @unit.each_with_index do |(prefix1, unit1, exp1), k|
245
- next if exp1 < 0
246
- @unit.each_with_index do |(prefix2, unit2, exp2), j|
247
- if exp2 < 0 && exp2 == -exp1
248
- q, r = @system.prefix[prefix1][:value].divmod @system.prefix[prefix2][:value]
249
- if r == 0 && new_prefix = @system.prefix_value[q]
250
- @unit[k] = @unit[k].dup
251
- @unit[j] = @unit[j].dup
252
- @unit[k][0] = new_prefix
253
- @unit[j][0] = :one
254
- end
255
- end
256
- end
257
- end
258
-
259
- self
260
- end
261
-
262
- public
263
-
264
- class System
265
- attr_reader :name, :unit, :unit_symbol, :prefix, :prefix_symbol, :prefix_value
266
-
267
- def initialize(name, &block)
268
- @name = name
269
- @unit = {}
270
- @unit_symbol = {}
271
-
272
- # one is internal trivial prefix
273
- @prefix = {:one => {:symbol => 'one', :value => 1} }
274
- @prefix_symbol = {'one' => :one}
275
- @prefix_value = {1 => :one}
276
-
277
- block.call(self) if block
278
- end
279
-
280
- def load(filename)
281
- data = YAML.load_file(File.join(File.dirname(__FILE__), 'systems', "#{filename}.yml"))
282
-
283
- (data['prefixes'] || {}).each do |name, prefix|
284
- name = name.to_sym
285
- symbols = [prefix['sym'] || []].flatten
286
- base = prefix['base']
287
- exp = prefix['exp']
288
- value = base ** exp
289
- $stderr.puts "Prefix #{name} already defined" if @prefix[name]
290
- @prefix[name] = { :symbol => symbols.first, :value => value }
291
- symbols.each do |sym|
292
- $stderr.puts "Prefix symbol #{sym} for #{name} already defined" if @prefix_symbol[name]
293
- @prefix_symbol[sym] = name
294
- end
295
- @prefix_symbol[name.to_s] = @prefix_value[value] = name
296
- end
297
-
298
- (data['units'] || {}).each do |name, unit|
299
- name = name.to_sym
300
- symbols = [unit['sym'] || []].flatten
301
- $stderr.puts "Unit #{name} already defined" if @unit[name]
302
- @unit[name] = { :symbol => symbols.first, :def => parse_unit(unit['def']) }
303
- symbols.each do |sym|
304
- $stderr.puts "Unit symbol #{sym} for #{name} already defined" if @unit_symbol[name]
305
- @unit_symbol[sym] = name
306
- end
307
- @unit_symbol[name.to_s] = name
308
- end
309
-
310
- @unit.each {|name, unit| validate_unit(unit[:def]) }
311
-
312
- true
313
- end
314
-
315
- def validate_unit(units)
316
- units.each do |prefix, unit, exp|
317
- #raise TypeError, 'Prefix must be symbol' if !(Symbol === prefix)
318
- #raise TypeError, 'Unit must be symbol' if !(Numeric === unit || Symbol === unit)
319
- #raise TypeError, 'Exponent must be numeric' if !(Numeric === exp)
320
- raise TypeError, "Undefined prefix #{prefix}" if !@prefix[prefix]
321
- raise TypeError, "Undefined unit #{unit}" if !(Numeric === unit || @unit[unit])
322
- end
323
- end
324
-
325
- def parse_unit(expr)
326
- stack, result, implicit_mul = [], [], false
327
- expr.to_s.scan(TOKENIZER).each do |tok|
328
- if tok == '('
329
- stack << '('
330
- implicit_mul = false
331
- elsif tok == ')'
332
- compute(result, stack.pop) while !stack.empty? && stack.last != '('
333
- raise(SyntaxError, 'Unexpected token )') if stack.empty?
334
- stack.pop
335
- implicit_mul = true
336
- elsif OPERATOR.key?(tok)
337
- compute(result, stack.pop) while !stack.empty? && stack.last != '(' && OPERATOR[stack.last][1] >= OPERATOR[tok][1]
338
- stack << OPERATOR[tok][0]
339
- implicit_mul = false
340
- else
341
- val = case tok
342
- when REAL then [[:one, tok.to_f, 1]]
343
- when DEC then [[:one, tok.to_i, 1]]
344
- when SYMBOL then symbol_to_unit(tok)
345
- end
346
- stack << '*' if implicit_mul
347
- implicit_mul = true
348
- result << val
349
- end
350
- end
351
- compute(result, stack.pop) while !stack.empty?
352
- result.last
353
- end
354
-
355
- private
356
-
357
- REAL = /^-?(?:(?:\d*\.\d+|\d+\.\d*)(?:[eE][-+]?\d+)?|\d+[eE][-+]?\d+)$/
358
- DEC = /^-?\d+$/
359
- SYMBOL = /^[a-zA-Z_°'"][\w_°'"]*$/
360
- OPERATOR = { '/' => ['/', 1], '*' => ['*', 1], '·' => ['*', 1], '^' => ['^', 2], '**' => ['^', 2] }
361
- OPERATOR_TOKENS = OPERATOR.keys.sort_by {|x| -x.size }. map {|x| Regexp.quote(x) }
362
- VALUE_TOKENS = [REAL.source[1..-2], DEC.source[1..-2], SYMBOL.source[1..-2]]
363
- TOKENIZER = Regexp.new((OPERATOR_TOKENS + VALUE_TOKENS + ['\\(', '\\)']).join('|'))
364
-
365
- def lookup_symbol(symbol)
366
- if unit_symbol[symbol]
367
- [[:one, unit_symbol[symbol], 1]]
368
- else
369
- found = prefix_symbol.keys.find do |sym|
370
- symbol[0..sym.size-1] == sym && unit_symbol[symbol[sym.size..-1]]
371
- end
372
- [[prefix_symbol[found], unit_symbol[symbol[found.size..-1]], 1]] if found
373
- end
374
- end
375
-
376
- def symbol_to_unit(symbol)
377
- lookup_symbol(symbol) ||
378
- (symbol[-1..-1] == 's' ? lookup_symbol(symbol[0..-2]) : nil) || # Try english plural
379
- [[:one, symbol.to_sym, 1]]
380
- end
381
-
382
- def compute(result, op)
383
- b = result.pop
384
- a = result.pop
385
- result << case op
386
- when '*' then a + b
387
- when '/' then a + Unit.power_unit(b, -1)
388
- when '^' then Unit.power_unit(a, b[0][1])
389
- else raise SyntaxError, "Unexpected token #{op}"
390
- end
391
- end
392
-
393
- public
394
-
395
- SI = new('SI') do |system|
396
- system.load(:si)
397
- system.load(:binary)
398
- system.load(:degree)
399
- system.load(:time)
400
- end
401
-
402
- DEFAULT = SI
403
- end
404
- end
405
-
406
- def Unit(*args)
407
- numerator = Numeric === args.first ? args.shift : 1
408
- denominator = Numeric === args.first ? args.shift : 1
409
-
410
- system = args.index {|x| Unit::System === x }
411
- system = system ? args.delete_at(system) : Unit::System::DEFAULT
412
-
413
- unit = args.index {|x| String === x }
414
- unit = system.parse_unit(args.delete_at(unit)) if unit
415
-
416
- unless unit
417
- unit = args.index {|x| Array === x }
418
- unit = args.delete_at(unit) if unit
419
- end
420
-
421
- unit ||= []
422
- system.validate_unit(unit)
423
-
424
- raise ArgumentError, 'wrong number of arguments' unless args.empty?
425
-
426
- Unit.new(numerator, denominator, unit, system)
427
- end
428
-
429
- class Numeric
430
- def to_unit(system = nil)
431
- system ||= Unit::System::DEFAULT
432
- Unit.new(self, 1, [], system)
433
- end
434
-
435
- def method_missing(name, *args)
436
- Unit.method_name_to_unit(name).to_unit(*args) * self
437
- rescue TypeError => ex
438
- super
439
- end
440
-
441
- def unit(unit, system = nil)
442
- unit.to_unit(system) * self
443
- end
444
- end
445
-
446
- class Rational
447
- def to_unit(system = nil)
448
- system ||= Unit::System::DEFAULT
449
- Unit.new(numerator, denominator, [], system)
450
- end
451
- end
452
-
453
- class String
454
- def to_unit(system = nil)
455
- system ||= Unit::System::DEFAULT
456
- unit = system.parse_unit(self)
457
- system.validate_unit(unit)
458
- Unit.new(1, 1, unit, system)
459
- end
460
- end
461
-
462
- class Symbol
463
- def to_unit(system = nil)
464
- to_s.to_unit(system)
465
- end
466
- end
467
-
468
- class Array
469
- def to_unit(system = nil)
470
- system ||= Unit::System::DEFAULT
471
- system.validate_unit(self)
472
- Unit.new(1, 1, self, system)
473
- end
474
- end
475
-
476
- # Units use symbols which must be sortable (Fix for Ruby 1.8)
477
- if !:test.respond_to? :<=>
478
- class Symbol
479
- include Comparable
480
- def <=>(other)
481
- self.to_i <=> other.to_i
482
- end
483
- end
484
- end
1
+ require 'unit/version'
2
+ require 'unit/compatibility'
3
+ require 'unit/class'
4
+ require 'unit/system'
5
+ require 'unit/constructor'