unit 0.2.1 → 0.3.0

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