minad-units 0.1.2

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/Manifest.txt ADDED
@@ -0,0 +1,12 @@
1
+ lib/systems/time.yml
2
+ lib/systems/imperial.yml
3
+ lib/systems/si.yml
4
+ lib/systems/misc.yml
5
+ lib/systems/degree.yml
6
+ lib/systems/scientific.yml
7
+ lib/systems/binary.yml
8
+ lib/units.rb
9
+ Rakefile
10
+ README.markdown
11
+ spec/spec_units.rb
12
+ Manifest.txt
data/README.markdown ADDED
@@ -0,0 +1,21 @@
1
+ README
2
+ ======
3
+
4
+ Units introduces computation with units to ruby.
5
+
6
+ Usage
7
+ -----
8
+
9
+ require 'units'
10
+ puts 1.meter.in_kilometer
11
+ puts 1.MeV.in_joule
12
+ puts 10.KiB / 1.second
13
+ puts 10.KiB_per_second
14
+ puts Unit('1 m/s^2')
15
+
16
+ See the test cases for more examples.
17
+
18
+ Authors
19
+ -------
20
+
21
+ Daniel Mendler
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'hoe'
2
+
3
+ $:.unshift 'lib'
4
+ require 'units'
5
+
6
+ Hoe.new 'units', Unit::VERSION do |units|
7
+ units.developer 'Daniel Mendler', 'mail@daniel-mendler.de'
8
+ units.test_globs = ['spec/**/spec_*.rb']
9
+ end
@@ -0,0 +1,41 @@
1
+ units:
2
+ bit:
3
+ sym: bit
4
+ def: bit
5
+ byte:
6
+ sym: B
7
+ def: 8 * bit
8
+
9
+ prefixes:
10
+ kibi:
11
+ sym: Ki
12
+ base: 2
13
+ exp: 10
14
+ mebi:
15
+ sym: Mi
16
+ base: 2
17
+ exp: 20
18
+ gibi:
19
+ sym: Gi
20
+ base: 2
21
+ exp: 30
22
+ tebi:
23
+ sym: Ti
24
+ base: 2
25
+ exp: 40
26
+ pebi:
27
+ sym: Pi
28
+ base: 2
29
+ exp: 50
30
+ exbi:
31
+ sym: Ei
32
+ base: 2
33
+ exp: 60
34
+ zebi:
35
+ sym: Zi
36
+ base: 2
37
+ exp: 70
38
+ yobi:
39
+ sym: Yi
40
+ base: 2
41
+ exp: 80
@@ -0,0 +1,14 @@
1
+ units:
2
+ pi:
3
+ sym: π
4
+ def: 3.14159265358979323846
5
+
6
+ degree:
7
+ sym: °
8
+ def: 180 * radiant / pi
9
+ arcminute:
10
+ sym: \'
11
+ def: degree / 60
12
+ arcsecond:
13
+ sym: \"
14
+ def: arcminute / 60
@@ -0,0 +1,37 @@
1
+ units:
2
+ mil:
3
+ sym: mil
4
+ def: 25400 nanometer
5
+ inch:
6
+ sym: [in, inches]
7
+ def: 1000 mil
8
+ foot:
9
+ sym: [ft, feet]
10
+ def: 12 inch
11
+ yard:
12
+ sym: yd
13
+ def: 3 foot
14
+ mile:
15
+ sym: mi
16
+ def: 5280 foot
17
+ nauticalmile:
18
+ sym: nmi
19
+ def: 6080 foot
20
+
21
+ acre:
22
+ sym: ac
23
+ def: 43560 foot^2
24
+
25
+ ounce:
26
+ sym: oz
27
+ def: 28349523125 nanogram
28
+ pound:
29
+ sym: lb
30
+ def: 16 ounce
31
+
32
+ mph:
33
+ sym: mph
34
+ def: mile / hour
35
+ knot:
36
+ sym: kt
37
+ def: nauticalmile / hour
@@ -0,0 +1,18 @@
1
+ units:
2
+ are:
3
+ sym: a
4
+ def: 100 m^2
5
+
6
+ ton:
7
+ sym: t
8
+ def: 1 megagram
9
+ carat:
10
+ def: 200 milligram
11
+
12
+ liter:
13
+ sym: [l, L]
14
+ def: 1 decimeter^3
15
+
16
+ calorie:
17
+ sym: cal
18
+ def: 4184 millijoule
@@ -0,0 +1,25 @@
1
+ units:
2
+ astronomicalunit:
3
+ sym: [au, AU, a.u., ua]
4
+ def: 149597900000 meter
5
+ lightsecond:
6
+ sym: ls
7
+ def: 299792500 meter
8
+ lightminute:
9
+ sym: lm
10
+ def: 17987550000 meter
11
+ lightyear:
12
+ sym: ly
13
+ def: 9460528000000000 meter
14
+ parsec:
15
+ sym: pc
16
+ def: 30856780000000000 meter
17
+ angstroem:
18
+ sym: Å
19
+ def: 100 picometer
20
+ electronvolt:
21
+ sym: eV
22
+ def: 1.602176487e-19 joule
23
+ atomicmass:
24
+ sym: [u, Da]
25
+ def: 1.660538782e-27 kilogram
@@ -0,0 +1,175 @@
1
+ units:
2
+ # SI base units
3
+ meter:
4
+ sym: m
5
+ def: meter
6
+ # use gram instead of kilogram to avoid millikilogram etc
7
+ gram:
8
+ sym: g
9
+ def: gram
10
+ second:
11
+ sym: s
12
+ def: second
13
+ ampere:
14
+ sym: A
15
+ def: ampere
16
+ kelvin:
17
+ sym: K
18
+ def: kelvin
19
+ mole:
20
+ sym: mol
21
+ def: mole
22
+ candela:
23
+ sym: cd
24
+ def: candela
25
+
26
+ # Derived SI units
27
+ radiant:
28
+ sym: rad
29
+ def: 1
30
+ steradiant:
31
+ sym: sr
32
+ def: 1
33
+ hertz:
34
+ sym: Hz
35
+ def: 1 / second
36
+ newton:
37
+ sym: N
38
+ def: kilogram * meter / second ^ 2
39
+ pascal:
40
+ sym: Pa
41
+ def: newton / meter ^ 2
42
+ joule:
43
+ sym: J
44
+ def: newton * meter
45
+ watt:
46
+ sym: W
47
+ def: joule * second
48
+ coloumb:
49
+ sym: C
50
+ def: ampere * second
51
+ volt:
52
+ sym: V
53
+ def: joule / coloumb
54
+ farad:
55
+ sym: F
56
+ def: coloumb / volt
57
+ ohm:
58
+ sym: Ω
59
+ def: volt / ampere
60
+ siemens:
61
+ sym: S
62
+ def: 1 / ohm
63
+ weber:
64
+ sym: Wb
65
+ def: volt / second
66
+ tesla:
67
+ sym: T
68
+ def: weber / meter ^ 2
69
+ henry:
70
+ sym: H
71
+ def: weber / ampere
72
+ celsius:
73
+ sym: °C
74
+ def: kelvin
75
+ lumen:
76
+ sym: lm
77
+ def: candela * steradiant
78
+ lux:
79
+ sym: lx
80
+ def: lm / meter ^ 2
81
+ becquerel:
82
+ sym: Bq
83
+ def: hertz
84
+ gray:
85
+ sym: Gy
86
+ def: joule / kilogram
87
+ sievert:
88
+ sym: Sv
89
+ def: gray
90
+ katal:
91
+ sym: kat
92
+ def: mole / second
93
+
94
+ prefixes:
95
+ # SI prefices
96
+ yotta:
97
+ sym: Y
98
+ base: 10
99
+ exp: 24
100
+ zetta:
101
+ sym: Z
102
+ base: 10
103
+ exp: 21
104
+ exa:
105
+ sym: E
106
+ base: 10
107
+ exp: 18
108
+ peta:
109
+ sym: P
110
+ base: 10
111
+ exp: 15
112
+ tera:
113
+ sym: T
114
+ base: 10
115
+ exp: 12
116
+ giga:
117
+ sym: G
118
+ base: 10
119
+ exp: 9
120
+ mega:
121
+ sym: M
122
+ base: 10
123
+ exp: 6
124
+ kilo:
125
+ sym: k
126
+ base: 10
127
+ exp: 3
128
+ hecto:
129
+ sym: h
130
+ base: 10
131
+ exp: 2
132
+ deca:
133
+ sym: da
134
+ base: 10
135
+ exp: 1
136
+ deci:
137
+ sym: d
138
+ base: 10
139
+ exp: -1
140
+ centi:
141
+ sym: c
142
+ base: 10
143
+ exp: -2
144
+ milli:
145
+ sym: m
146
+ base: 10
147
+ exp: -3
148
+ micro:
149
+ sym: µ
150
+ base: 10
151
+ exp: -6
152
+ nano:
153
+ sym: n
154
+ base: 10
155
+ exp: -9
156
+ pico:
157
+ sym: p
158
+ base: 10
159
+ exp: -12
160
+ femto:
161
+ sym: f
162
+ base: 10
163
+ exp: -15
164
+ atto:
165
+ sym: a
166
+ base: 10
167
+ exp: -18
168
+ zepto:
169
+ sym: z
170
+ base: 10
171
+ exp: -21
172
+ yokto:
173
+ sym: y
174
+ base: 10
175
+ exp: -24
@@ -0,0 +1,13 @@
1
+ units:
2
+ minute:
3
+ sym: min
4
+ def: 60 * second
5
+ hour:
6
+ sym: h
7
+ def: 60 * minute
8
+ day:
9
+ sym: d
10
+ def: 24 * hour
11
+ year:
12
+ sym: a
13
+ def: 365 * day
data/lib/units.rb ADDED
@@ -0,0 +1,479 @@
1
+ # encoding: utf-8
2
+ require 'yaml'
3
+
4
+ class Unit < Numeric
5
+ VERSION = '0.1.2'
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
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(#{numerator}/#{denominator}, #{unit.inspect})"
118
+ end
119
+
120
+ def to_s
121
+ s = ''
122
+ s << @numerator.to_s
123
+ s << "/#{@denominator}" if @denominator != 1
124
+ positive = @unit.select {|prefix, name, exp| exp >= 0 }
125
+ negative = @unit.select {|prefix, name, exp| exp < 0 }
126
+ if positive.empty? && !negative.empty?
127
+ s << ' 1'
128
+ else
129
+ s << ' ' << unit_string(positive)
130
+ end
131
+ if !negative.empty?
132
+ s << '/' << unit_string(negative)
133
+ end
134
+ s
135
+ end
136
+
137
+ def to_i
138
+ (@numerator / @denominator).to_i
139
+ end
140
+
141
+ def to_f
142
+ @numerator.to_f / @denominator.to_f
143
+ end
144
+
145
+ def approx
146
+ to_f.unit(unit)
147
+ end
148
+
149
+ def to_unit(system)
150
+ raise TypeError, 'Different unit system' if @system != system
151
+ self
152
+ end
153
+
154
+ def coerce(val)
155
+ raise TypeError, 'No unit support' if !val.respond_to? :to_unit
156
+ [self, val.to_unit(system)]
157
+ end
158
+
159
+ def method_missing(name)
160
+ if name.to_s[0..2] == 'in_'
161
+ self.in(Unit.method_name_to_unit(name))
162
+ else
163
+ super
164
+ end
165
+ end
166
+
167
+ def self.method_name_to_unit(name)
168
+ name.to_s.sub(/^in_/, '').sub(/^per_/, '1/').gsub('_per_', '/').gsub('_', ' ')
169
+ end
170
+
171
+ private
172
+
173
+ def unit_string(list)
174
+ units = []
175
+ list.each do |prefix, name, exp|
176
+ unit = ''
177
+ unit << (@system.prefix[prefix] ? @system.prefix[prefix][:symbol] : prefix.to_s) if prefix != :one
178
+ unit << (@system.unit[name] ? @system.unit[name][:symbol] : name.to_s)
179
+ unit << '^' << exp.abs.to_s if exp.abs != 1
180
+ units << unit
181
+ end
182
+ units.sort.join('·')
183
+ end
184
+
185
+ def self.power_unit(unit, pow)
186
+ unit.map {|prefix, name, exp| [prefix, name, exp * pow] }
187
+ end
188
+
189
+ # Reduce units and prefixes
190
+ def reduce!
191
+ # Remove numbers from units
192
+ numbers = @unit.select {|prefix, unit, exp| Numeric === unit }
193
+ @unit -= numbers
194
+ numbers.each do |prefix, number, exp|
195
+ raise RuntimeError, 'Numeric unit with prefix' if prefix != :one
196
+ if exp >= 0
197
+ @numerator *= number ** exp
198
+ else
199
+ @denominator *= number ** -exp
200
+ end
201
+ end
202
+
203
+ # Reduce number
204
+ if Integer === @numerator && Integer === @denominator
205
+ r = Rational(@numerator, @denominator)
206
+ @numerator = r.numerator
207
+ @denominator = r.denominator
208
+ elsif Rational === @numerator || Rational === @denominator
209
+ r = @numerator / @denominator
210
+ @numerator = r.numerator
211
+ @denominator = r.denominator
212
+ else
213
+ @numerator /= @denominator
214
+ @denominator = 1
215
+ end
216
+
217
+ if @numerator == 0
218
+ @denominator = 1
219
+ @unit.clear
220
+ end
221
+
222
+ # Reduce units
223
+ @unit.sort!
224
+ i, current = 1, 0
225
+ while i < @unit.size do
226
+ while i < @unit.size && @unit[current][0] == @unit[i][0] && @unit[current][1] == @unit[i][1]
227
+ @unit[current] = @unit[current].dup
228
+ @unit[current][2] += @unit[i][2]
229
+ i += 1
230
+ end
231
+ if @unit[current][2] == 0
232
+ @unit.slice!(current, i - current)
233
+ else
234
+ @unit.slice!(current + 1, i - current - 1)
235
+ current += 1
236
+ end
237
+ i = current + 1
238
+ end
239
+
240
+ # Reduce prefixes
241
+ @unit.each_with_index do |(prefix1, unit1, exp1), k|
242
+ next if exp1 < 0
243
+ @unit.each_with_index do |(prefix2, unit2, exp2), j|
244
+ if exp2 < 0 && exp2 == -exp1
245
+ q, r = @system.prefix[prefix1][:value].divmod @system.prefix[prefix2][:value]
246
+ if r == 0 && new_prefix = @system.prefix_value[q]
247
+ @unit[k] = @unit[k].dup
248
+ @unit[j] = @unit[j].dup
249
+ @unit[k][0] = new_prefix
250
+ @unit[j][0] = :one
251
+ end
252
+ end
253
+ end
254
+ end
255
+
256
+ self
257
+ end
258
+
259
+ public
260
+
261
+ class System
262
+ attr_reader :name, :unit, :unit_symbol, :prefix, :prefix_symbol, :prefix_value
263
+
264
+ def initialize(name, &block)
265
+ @name = name
266
+ @unit = {}
267
+ @unit_symbol = {}
268
+
269
+ # one is internal trivial prefix
270
+ @prefix = {:one => {:symbol => 'one', :value => 1} }
271
+ @prefix_symbol = {'one' => :one}
272
+ @prefix_value = {1 => :one}
273
+
274
+ block.call(self) if block
275
+ end
276
+
277
+ def load(filename)
278
+ data = YAML.load_file(File.join(File.dirname(__FILE__), 'systems', "#{filename}.yml"))
279
+
280
+ (data['prefixes'] || {}).each do |name, prefix|
281
+ name = name.to_sym
282
+ symbols = [prefix['sym'] || []].flatten
283
+ base = prefix['base']
284
+ exp = prefix['exp']
285
+ value = base ** exp
286
+ $stderr.puts "Prefix #{name} already defined" if @prefix[name]
287
+ @prefix[name] = { :symbol => symbols.first, :value => value }
288
+ symbols.each do |sym|
289
+ $stderr.puts "Prefix symbol #{sym} for #{name} already defined" if @prefix_symbol[name]
290
+ @prefix_symbol[sym] = name
291
+ end
292
+ @prefix_symbol[name.to_s] = @prefix_value[value] = name
293
+ end
294
+
295
+ (data['units'] || {}).each do |name, unit|
296
+ name = name.to_sym
297
+ symbols = [unit['sym'] || []].flatten
298
+ $stderr.puts "Unit #{name} already defined" if @unit[name]
299
+ @unit[name] = { :symbol => symbols.first, :def => parse_unit(unit['def']) }
300
+ symbols.each do |sym|
301
+ $stderr.puts "Unit symbol #{sym} for #{name} already defined" if @unit_symbol[name]
302
+ @unit_symbol[sym] = name
303
+ end
304
+ @unit_symbol[name.to_s] = name
305
+ end
306
+
307
+ @unit.each {|name, unit| validate_unit(unit[:def]) }
308
+ end
309
+
310
+ def validate_unit(units)
311
+ units.each do |prefix, unit, exp|
312
+ #raise TypeError, 'Prefix must be symbol' if !(Symbol === prefix)
313
+ #raise TypeError, 'Unit must be symbol' if !(Numeric === unit || Symbol === unit)
314
+ #raise TypeError, 'Exponent must be numeric' if !(Numeric === exp)
315
+ raise TypeError, "Undefined prefix #{prefix}" if !@prefix[prefix]
316
+ raise TypeError, "Undefined unit #{unit}" if !(Numeric === unit || @unit[unit])
317
+ end
318
+ end
319
+
320
+ def parse_unit(expr)
321
+ stack, result, implicit_mul = [], [], false
322
+ expr.to_s.scan(TOKENIZER).each do |tok|
323
+ if tok == '('
324
+ stack << '('
325
+ implicit_mul = false
326
+ elsif tok == ')'
327
+ compute(result, stack.pop) while !stack.empty? && stack.last != '('
328
+ raise(SyntaxError, 'Unexpected token )') if stack.empty?
329
+ stack.pop
330
+ implicit_mul = true
331
+ elsif OPERATOR.key?(tok)
332
+ compute(result, stack.pop) while !stack.empty? && stack.last != '(' && OPERATOR[stack.last][1] >= OPERATOR[tok][1]
333
+ stack << OPERATOR[tok][0]
334
+ implicit_mul = false
335
+ else
336
+ val = case tok
337
+ when REAL then [[:one, tok.to_f, 1]]
338
+ when DEC then [[:one, tok.to_i, 1]]
339
+ when SYMBOL then symbol_to_unit(tok)
340
+ end
341
+ stack << '*' if implicit_mul
342
+ implicit_mul = true
343
+ result << val
344
+ end
345
+ end
346
+ compute(result, stack.pop) while !stack.empty?
347
+ result.last
348
+ end
349
+
350
+ private
351
+
352
+ REAL = /^-?(?:(?:\d*\.\d+|\d+\.\d*)(?:[eE][-+]?\d+)?|\d+[eE][-+]?\d+)$/
353
+ DEC = /^-?\d+$/
354
+ SYMBOL = /^[a-zA-Z_][\w_]*$/
355
+ OPERATOR = { '/' => ['/', 1], '*' => ['*', 1], '·' => ['*', 1], '^' => ['^', 2] }
356
+ OPERATOR_TOKENS = OPERATOR.keys.map {|x| Regexp.quote(x) }
357
+ VALUE_TOKENS = [REAL.source[1..-2], DEC.source[1..-2], SYMBOL.source[1..-2]]
358
+ TOKENIZER = Regexp.new((OPERATOR_TOKENS + VALUE_TOKENS + ['\\(', '\\)']).join('|'))
359
+
360
+ def lookup_symbol(symbol)
361
+ if unit_symbol[symbol]
362
+ [[:one, unit_symbol[symbol], 1]]
363
+ else
364
+ found = prefix_symbol.keys.find do |sym|
365
+ symbol[0..sym.size-1] == sym && unit_symbol[symbol[sym.size..-1]]
366
+ end
367
+ [[prefix_symbol[found], unit_symbol[symbol[found.size..-1]], 1]] if found
368
+ end
369
+ end
370
+
371
+ def symbol_to_unit(symbol)
372
+ lookup_symbol(symbol) ||
373
+ (symbol[-1..-1] == 's' ? lookup_symbol(symbol[0..-2]) : nil) || # Try english plural
374
+ [[:one, symbol.to_sym, 1]]
375
+ end
376
+
377
+ def compute(result, op)
378
+ b = result.pop
379
+ a = result.pop
380
+ result << case op
381
+ when '*' then a + b
382
+ when '/' then a + Unit.power_unit(b, -1)
383
+ when '^' then Unit.power_unit(a, b[0][1])
384
+ else raise SyntaxError, "Unexpected token #{op}"
385
+ end
386
+ end
387
+
388
+ public
389
+
390
+ DEFAULT = new('SI') do |system|
391
+ system.load(:si)
392
+ system.load(:binary)
393
+ system.load(:degree)
394
+ system.load(:time)
395
+ end
396
+ end
397
+ end
398
+
399
+ def Unit(*args)
400
+ numerator = args.find {|x| Numeric === x }
401
+ args.delete(numerator) if numerator
402
+ numerator ||= 1
403
+
404
+ denominator = args.find {|x| Numeric === x }
405
+ args.delete(denominator) if denominator
406
+ denominator ||= 1
407
+
408
+ system = args.find {|x| Unit::System === x } || Unit::System::DEFAULT
409
+ args.delete(system) if system
410
+
411
+ unit = args.find {|x| String === x }
412
+ if unit
413
+ args.delete(unit)
414
+ unit = system.parse_unit(unit)
415
+ end
416
+
417
+ if !unit
418
+ unit = args.find {|x| Array === x }
419
+ args.delete(unit) if unit
420
+ end
421
+
422
+ unit ||= []
423
+ system.validate_unit(unit)
424
+
425
+ raise ArgumentError, 'wrong number of arguments' if !args.empty?
426
+
427
+ Unit.new(numerator, denominator, unit, system)
428
+ end
429
+
430
+ class Numeric
431
+ def to_unit(system = nil)
432
+ system ||= Unit::System::DEFAULT
433
+ Unit.new(self, 1, [], system)
434
+ end
435
+
436
+ def method_missing(name, *args)
437
+ Unit.method_name_to_unit(name).to_unit(*args) * self
438
+ rescue TypeError => ex
439
+ super
440
+ end
441
+
442
+ def unit(unit, system = nil)
443
+ unit.to_unit(system) * self
444
+ end
445
+ end
446
+
447
+ class Rational
448
+ def to_unit(system = nil)
449
+ system ||= Unit::System::DEFAULT
450
+ Unit(numerator, denominator, [], system)
451
+ end
452
+ end
453
+
454
+ class String
455
+ def to_unit(system = nil)
456
+ system ||= Unit::System::DEFAULT
457
+ unit = system.parse_unit(self)
458
+ system.validate_unit(unit)
459
+ Unit(1, 1, unit, system)
460
+ end
461
+ end
462
+
463
+ class Array
464
+ def to_unit(system = nil)
465
+ system ||= Unit::System::DEFAULT
466
+ system.validate_unit(self)
467
+ Unit(1, 1, self, system)
468
+ end
469
+ end
470
+
471
+ # Units use symbols which must be sortable (Fix for Ruby 1.8)
472
+ if !:test.respond_to? :<=>
473
+ class Symbol
474
+ include Comparable
475
+ def <=>(other)
476
+ self.to_i <=> other.to_i
477
+ end
478
+ end
479
+ end
@@ -0,0 +1,94 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
4
+
5
+ require 'test/spec'
6
+ require 'units'
7
+
8
+ Unit::System::DEFAULT.load(:scientific)
9
+ Unit::System::DEFAULT.load(:imperial)
10
+ Unit::System::DEFAULT.load(:misc)
11
+
12
+ describe 'Unit' do
13
+ it 'should support multiplication' do
14
+ (Unit(2, 'm') * Unit(3, 'm')).should.equal Unit(6, 'm^2')
15
+ (Unit(2, 'm') * 3).should.equal Unit(6, 'm')
16
+ (Unit(2, 'm') * Rational(3, 4)).should.equal Unit(3, 2, 'm')
17
+ (Unit(2, 'm') * 0.5).should.equal Unit(1.0, 'm')
18
+ end
19
+
20
+ it 'should support division' do
21
+ (Unit(2, 'm') / Unit(3, 'm^2')).should.equal Unit(2, 3, '1/m')
22
+ (Unit(2, 'm') / 3).should.equal Unit(2, 3, 'm')
23
+ (Unit(2, 'm') / Rational(3, 4)).should.equal Unit(8, 3, 'm')
24
+ (Unit(2, 'm') / 0.5).should.equal Unit(4.0, 'm')
25
+ end
26
+
27
+ it 'should support addition' do
28
+ (Unit(42, 'm') + Unit(1, 'km')).should.equal Unit(1042, 'm')
29
+ (Unit(1, 'm') - Unit(1, 'cm')).should.equal Unit(99, 100, 'm')
30
+ end
31
+
32
+ it 'should check unit compatiblity' do
33
+ should.raise TypeError do
34
+ (Unit(42, 'm') + Unit(1, 's'))
35
+ end
36
+ should.raise TypeError do
37
+ (Unit(42, 'g') + Unit(1, 'm'))
38
+ end
39
+ end
40
+
41
+ it 'should support exponentiation' do
42
+ (Unit(2, 'm') ** 3).should.equal Unit(8, 'm^3')
43
+ (Unit(9, 'm^2') ** 0.5).should.equal Unit(3.0, 'm')
44
+ (Unit(9, 'm^2') ** Rational(1, 2)).should.equal Unit(3, 'm')
45
+ (Unit(2, 'm') ** 1.3).should.equal Unit(2 ** 1.3, 'm^1.3')
46
+ end
47
+
48
+ it 'should not allow units as exponent' do
49
+ should.raise TypeError do
50
+ Unit(42, 'g') ** Unit(1, 'm')
51
+ end
52
+ end
53
+
54
+ it 'should provide method sugar' do
55
+ 1.meter.should.equal Unit('1 meter')
56
+ 1.meter_per_second.should.equal Unit('1 m/s')
57
+ 1.meter.in_kilometer.should.equal Unit('1 m').in('km')
58
+ 1.unit('°C').should.equal Unit(1, '°C')
59
+ end
60
+
61
+ it 'should have a normalizer' do
62
+ 1.joule.normalize.should.equal Unit(1000, 'gram meter^2 / second^2')
63
+ unit = 1.joule.normalize!
64
+ unit.should.equal unit.normalized
65
+ end
66
+
67
+ it 'should convert units' do
68
+ 1.MeV.in_joule.should.equal Unit(1.602176487e-13, 'joule')
69
+ 1.kilometer.in_meter.should.equal Unit(1000, 'meter')
70
+ 1.liter.in('meter^3').should.equal Unit(1, 1000, 'meter^3')
71
+ end
72
+
73
+ it 'should have a working compatible? method' do
74
+ 7.meter.compatible?('kilogram').should.be false
75
+ 3.parsec.compatible_with?('meter').should.be true
76
+ end
77
+
78
+ it 'should have a pretty string representation' do
79
+ 7.joule.normalize.to_s.should.equal '7000 g·m^2/s^2'
80
+ end
81
+
82
+ it 'should parse units' do
83
+ Unit(1, 'KiB s^-1').unit.should.equal [[:kibi, :byte, 1], [:one, :second, -1]].sort
84
+ Unit(1, 'KiB/s').unit.should.equal [[:kibi, :byte, 1], [:one, :second, -1]].sort
85
+ Unit(1, 'kilometer^2 / megaelectronvolt^7 * gram centiliter').unit.should.equal [[:kilo, :meter, 2], [:mega, :electronvolt, -7],
86
+ [:one, :gram, 1], [:centi, :liter, 1]].sort
87
+ end
88
+
89
+ it 'should reduce units' do
90
+ 1.joule_per_kilogram.normalize.unit.should.equal [[:one, :meter, 2], [:one, :second, -2]].sort
91
+ 1.megaton_per_kilometer.unit.should.equal [[:kilo, :ton, 1], [:one, :meter, -1]].sort
92
+ end
93
+
94
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minad-units
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Mendler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-17 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.8.3
24
+ version:
25
+ description:
26
+ email:
27
+ - mail@daniel-mendler.de
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - Manifest.txt
34
+ files:
35
+ - lib/systems/time.yml
36
+ - lib/systems/imperial.yml
37
+ - lib/systems/si.yml
38
+ - lib/systems/misc.yml
39
+ - lib/systems/degree.yml
40
+ - lib/systems/scientific.yml
41
+ - lib/systems/binary.yml
42
+ - lib/units.rb
43
+ - Rakefile
44
+ - README.markdown
45
+ - spec/spec_units.rb
46
+ - Manifest.txt
47
+ has_rdoc: true
48
+ homepage:
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --main
52
+ - README.txt
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project: units
70
+ rubygems_version: 1.2.0
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: Unit support for ruby
74
+ test_files:
75
+ - spec/spec_units.rb