unit 0.2.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/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 'unit'
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,6 @@
1
+ task :default => :test
2
+
3
+ desc 'Run tests with bacon'
4
+ task :test => FileList['test/*_test.rb'] do |t|
5
+ sh "bacon -q -Ilib:test #{t.prerequisites.join(' ')}"
6
+ 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,13 @@
1
+ units:
2
+ pi:
3
+ sym: π
4
+ def: 3.14159265358979323846
5
+ degree:
6
+ sym: [°, deg]
7
+ def: pi radiant / 180
8
+ arcminute:
9
+ sym: \'
10
+ def: degree / 60
11
+ arcsecond:
12
+ sym: \"
13
+ 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,22 @@
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
19
+
20
+ fahrenheit:
21
+ sym: [°F, ℉]
22
+ def: 1.8 celsius
@@ -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, radian]
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,15 @@
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
+ week:
12
+ def: 7 * day
13
+ year:
14
+ sym: a
15
+ def: 365 * day
data/lib/unit.rb ADDED
@@ -0,0 +1,482 @@
1
+ # encoding: utf-8
2
+ require 'yaml'
3
+
4
+ class Unit < Numeric
5
+ VERSION = '0.2.0'
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
+ end
312
+
313
+ def validate_unit(units)
314
+ units.each do |prefix, unit, exp|
315
+ #raise TypeError, 'Prefix must be symbol' if !(Symbol === prefix)
316
+ #raise TypeError, 'Unit must be symbol' if !(Numeric === unit || Symbol === unit)
317
+ #raise TypeError, 'Exponent must be numeric' if !(Numeric === exp)
318
+ raise TypeError, "Undefined prefix #{prefix}" if !@prefix[prefix]
319
+ raise TypeError, "Undefined unit #{unit}" if !(Numeric === unit || @unit[unit])
320
+ end
321
+ end
322
+
323
+ def parse_unit(expr)
324
+ stack, result, implicit_mul = [], [], false
325
+ expr.to_s.scan(TOKENIZER).each do |tok|
326
+ if tok == '('
327
+ stack << '('
328
+ implicit_mul = false
329
+ elsif tok == ')'
330
+ compute(result, stack.pop) while !stack.empty? && stack.last != '('
331
+ raise(SyntaxError, 'Unexpected token )') if stack.empty?
332
+ stack.pop
333
+ implicit_mul = true
334
+ elsif OPERATOR.key?(tok)
335
+ compute(result, stack.pop) while !stack.empty? && stack.last != '(' && OPERATOR[stack.last][1] >= OPERATOR[tok][1]
336
+ stack << OPERATOR[tok][0]
337
+ implicit_mul = false
338
+ else
339
+ val = case tok
340
+ when REAL then [[:one, tok.to_f, 1]]
341
+ when DEC then [[:one, tok.to_i, 1]]
342
+ when SYMBOL then symbol_to_unit(tok)
343
+ end
344
+ stack << '*' if implicit_mul
345
+ implicit_mul = true
346
+ result << val
347
+ end
348
+ end
349
+ compute(result, stack.pop) while !stack.empty?
350
+ result.last
351
+ end
352
+
353
+ private
354
+
355
+ REAL = /^-?(?:(?:\d*\.\d+|\d+\.\d*)(?:[eE][-+]?\d+)?|\d+[eE][-+]?\d+)$/
356
+ DEC = /^-?\d+$/
357
+ SYMBOL = /^[a-zA-Z_°'"][\w_°'"]*$/
358
+ OPERATOR = { '/' => ['/', 1], '*' => ['*', 1], '·' => ['*', 1], '^' => ['^', 2], '**' => ['^', 2] }
359
+ OPERATOR_TOKENS = OPERATOR.keys.sort_by {|x| -x.size }. map {|x| Regexp.quote(x) }
360
+ VALUE_TOKENS = [REAL.source[1..-2], DEC.source[1..-2], SYMBOL.source[1..-2]]
361
+ TOKENIZER = Regexp.new((OPERATOR_TOKENS + VALUE_TOKENS + ['\\(', '\\)']).join('|'))
362
+
363
+ def lookup_symbol(symbol)
364
+ if unit_symbol[symbol]
365
+ [[:one, unit_symbol[symbol], 1]]
366
+ else
367
+ found = prefix_symbol.keys.find do |sym|
368
+ symbol[0..sym.size-1] == sym && unit_symbol[symbol[sym.size..-1]]
369
+ end
370
+ [[prefix_symbol[found], unit_symbol[symbol[found.size..-1]], 1]] if found
371
+ end
372
+ end
373
+
374
+ def symbol_to_unit(symbol)
375
+ lookup_symbol(symbol) ||
376
+ (symbol[-1..-1] == 's' ? lookup_symbol(symbol[0..-2]) : nil) || # Try english plural
377
+ [[:one, symbol.to_sym, 1]]
378
+ end
379
+
380
+ def compute(result, op)
381
+ b = result.pop
382
+ a = result.pop
383
+ result << case op
384
+ when '*' then a + b
385
+ when '/' then a + Unit.power_unit(b, -1)
386
+ when '^' then Unit.power_unit(a, b[0][1])
387
+ else raise SyntaxError, "Unexpected token #{op}"
388
+ end
389
+ end
390
+
391
+ public
392
+
393
+ SI = new('SI') do |system|
394
+ system.load(:si)
395
+ system.load(:binary)
396
+ system.load(:degree)
397
+ system.load(:time)
398
+ end
399
+
400
+ DEFAULT = SI
401
+ end
402
+ end
403
+
404
+ def Unit(*args)
405
+ numerator = Numeric === args.first ? args.shift : 1
406
+ denominator = Numeric === args.first ? args.shift : 1
407
+
408
+ system = args.index {|x| Unit::System === x }
409
+ system = system ? args.delete_at(system) : Unit::System::DEFAULT
410
+
411
+ unit = args.index {|x| String === x }
412
+ unit = system.parse_unit(args.delete_at(unit)) if unit
413
+
414
+ unless unit
415
+ unit = args.index {|x| Array === x }
416
+ unit = args.delete_at(unit) if unit
417
+ end
418
+
419
+ unit ||= []
420
+ system.validate_unit(unit)
421
+
422
+ raise ArgumentError, 'wrong number of arguments' unless args.empty?
423
+
424
+ Unit.new(numerator, denominator, unit, system)
425
+ end
426
+
427
+ class Numeric
428
+ def to_unit(system = nil)
429
+ system ||= Unit::System::DEFAULT
430
+ Unit.new(self, 1, [], system)
431
+ end
432
+
433
+ def method_missing(name, *args)
434
+ Unit.method_name_to_unit(name).to_unit(*args) * self
435
+ rescue TypeError => ex
436
+ super
437
+ end
438
+
439
+ def unit(unit, system = nil)
440
+ unit.to_unit(system) * self
441
+ end
442
+ end
443
+
444
+ class Rational
445
+ def to_unit(system = nil)
446
+ system ||= Unit::System::DEFAULT
447
+ Unit.new(numerator, denominator, [], system)
448
+ end
449
+ end
450
+
451
+ class String
452
+ def to_unit(system = nil)
453
+ system ||= Unit::System::DEFAULT
454
+ unit = system.parse_unit(self)
455
+ system.validate_unit(unit)
456
+ Unit.new(1, 1, unit, system)
457
+ end
458
+ end
459
+
460
+ class Symbol
461
+ def to_unit(system = nil)
462
+ to_s.to_unit(system)
463
+ end
464
+ end
465
+
466
+ class Array
467
+ def to_unit(system = nil)
468
+ system ||= Unit::System::DEFAULT
469
+ system.validate_unit(self)
470
+ Unit.new(1, 1, self, system)
471
+ end
472
+ end
473
+
474
+ # Units use symbols which must be sortable (Fix for Ruby 1.8)
475
+ if !:test.respond_to? :<=>
476
+ class Symbol
477
+ include Comparable
478
+ def <=>(other)
479
+ self.to_i <=> other.to_i
480
+ end
481
+ end
482
+ end
data/lib/units.rb ADDED
@@ -0,0 +1 @@
1
+ require 'unit'
data/test/unit_test.rb ADDED
@@ -0,0 +1,109 @@
1
+ # encoding: utf-8
2
+ require 'unit'
3
+
4
+ Unit::System::DEFAULT.load(:scientific)
5
+ Unit::System::DEFAULT.load(:imperial)
6
+ Unit::System::DEFAULT.load(:misc)
7
+
8
+ describe 'Unit' do
9
+ it 'should support multiplication' do
10
+ (Unit(2, 'm') * Unit(3, 'm')).should.equal Unit(6, 'm^2')
11
+ (Unit(2, 'm') * 3).should.equal Unit(6, 'm')
12
+ (Unit(2, 'm') * Rational(3, 4)).should.equal Unit(3, 2, 'm')
13
+ (Unit(2, 'm') * 0.5).should.equal Unit(1.0, 'm')
14
+ end
15
+
16
+ it 'should support division' do
17
+ (Unit(2, 'm') / Unit(3, 'm^2')).should.equal Unit(2, 3, '1/m')
18
+ (Unit(2, 'm') / 3).should.equal Unit(2, 3, 'm')
19
+ (Unit(2, 'm') / Rational(3, 4)).should.equal Unit(8, 3, 'm')
20
+ (Unit(2, 'm') / 0.5).should.equal Unit(4.0, 'm')
21
+ end
22
+
23
+ it 'should support addition' do
24
+ (Unit(42, 'm') + Unit(1, 'km')).should.equal Unit(1042, 'm')
25
+ (Unit(1, 'm') - Unit(1, 'cm')).should.equal Unit(99, 100, 'm')
26
+ end
27
+
28
+ it 'should check unit compatiblity' do
29
+ should.raise TypeError do
30
+ (Unit(42, 'm') + Unit(1, 's'))
31
+ end
32
+ should.raise TypeError do
33
+ (Unit(42, 'g') + Unit(1, 'm'))
34
+ end
35
+ end
36
+
37
+ it 'should support exponentiation' do
38
+ (Unit(2, 'm') ** 3).should.equal Unit(8, 'm^3')
39
+ (Unit(9, 'm^2') ** 0.5).should.equal Unit(3.0, 'm')
40
+ (Unit(9, 'm^2') ** Rational(1, 2)).should.equal Unit(3, 'm')
41
+ (Unit(2, 'm') ** 1.3).should.equal Unit(2 ** 1.3, 'm^1.3')
42
+ end
43
+
44
+ it 'should not allow units as exponent' do
45
+ should.raise TypeError do
46
+ Unit(42, 'g') ** Unit(1, 'm')
47
+ end
48
+ end
49
+
50
+ it 'should provide method sugar' do
51
+ 1.meter.should.equal Unit('1 meter')
52
+ 1.meter_per_second.should.equal Unit('1 m/s')
53
+ 1.meter.in_kilometer.should.equal Unit('1 m').in('km')
54
+ 1.unit('°C').should.equal Unit(1, '°C')
55
+ end
56
+
57
+ it 'should have a normalizer' do
58
+ 1.joule.normalize.should.equal Unit(1000, 'gram meter^2 / second^2')
59
+ unit = 1.joule.normalize!
60
+ unit.should.equal unit.normalized
61
+ end
62
+
63
+ it 'should convert units' do
64
+ 1.MeV.in_joule.should.equal Unit(1.602176487e-13, 'joule')
65
+ 1.kilometer.in_meter.should.equal Unit(1000, 'meter')
66
+ 1.liter.in('meter^3').should.equal Unit(1, 1000, 'meter^3')
67
+ 1.kilometer_per_hour.in_meter_per_second.should.equal Unit(5, 18, 'meter/second')
68
+ end
69
+
70
+ it 'should have a working compatible? method' do
71
+ 7.meter.compatible?('kilogram').should.equal false
72
+ 3.parsec.compatible_with?('meter').should.equal true
73
+ end
74
+
75
+ it 'should have a pretty string representation' do
76
+ 7.joule.normalize.to_s.should.equal '7000 g·m^2·s^-2'
77
+ end
78
+
79
+ it 'should parse units' do
80
+ Unit(1, 'KiB s^-1').unit.should.equal [[:kibi, :byte, 1], [:one, :second, -1]].sort
81
+ Unit(1, 'KiB/s').unit.should.equal [[:kibi, :byte, 1], [:one, :second, -1]].sort
82
+ Unit(1, 'kilometer^2 / megaelectronvolt^7 * gram centiliter').unit.should.equal [[:kilo, :meter, 2], [:mega, :electronvolt, -7],
83
+ [:one, :gram, 1], [:centi, :liter, 1]].sort
84
+ end
85
+
86
+ it 'should reduce units' do
87
+ 1.joule_per_kilogram.normalize.unit.should.equal [[:one, :meter, 2], [:one, :second, -2]].sort
88
+ 1.megaton_per_kilometer.unit.should.equal [[:kilo, :ton, 1], [:one, :meter, -1]].sort
89
+ end
90
+
91
+ it 'should work with floating point values' do
92
+ #w = (5.2).kilogram
93
+ w = 5.2 * Unit('kilogram')
94
+ w.in_pounds.to_int.should.equal 11
95
+ end
96
+
97
+ it 'should have dimensionless? method' do
98
+ 100.meter.per_km.should.be.dimensionless
99
+ 100.meter.per_km.should.be.unitless
100
+ 42.meter.per_second.should.not.be.unitless
101
+ 100.meter.per_km.should.equal Rational(1, 10).to_unit
102
+ end
103
+
104
+ it 'should be equal to rational if dimensionless' do
105
+ 100.meter.per_km.should.equal Rational(1, 10)
106
+ 100.meter.per_km.approx.should.equal 0.1
107
+ end
108
+ end
109
+
data/unit.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.dirname(__FILE__) + '/lib/unit'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = %q{unit}
6
+ s.version = Unit::VERSION
7
+
8
+ s.authors = ["Daniel Mendler"]
9
+ s.date = %q{2009-05-17}
10
+ s.email = ["mail@daniel-mendler.de"]
11
+ s.extra_rdoc_files = []
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.require_paths = %w(lib)
15
+
16
+ s.has_rdoc = true
17
+ s.rdoc_options = ["--main", "README.txt"]
18
+ s.require_paths = ["lib"]
19
+ s.rubyforge_project = s.name
20
+ s.summary = 'Scientific unit support for ruby for calculations'
21
+ s.homepage = %q{http://github.com/minad/si}
22
+
23
+ s.add_development_dependency('bacon')
24
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unit
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.2.0
6
+ platform: ruby
7
+ authors:
8
+ - Daniel Mendler
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-05-17 00:00:00 +02:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: bacon
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :development
26
+ version_requirements: *id001
27
+ description:
28
+ email:
29
+ - mail@daniel-mendler.de
30
+ executables: []
31
+
32
+ extensions: []
33
+
34
+ extra_rdoc_files: []
35
+
36
+ files:
37
+ - README.markdown
38
+ - Rakefile
39
+ - lib/systems/binary.yml
40
+ - lib/systems/degree.yml
41
+ - lib/systems/imperial.yml
42
+ - lib/systems/misc.yml
43
+ - lib/systems/scientific.yml
44
+ - lib/systems/si.yml
45
+ - lib/systems/time.yml
46
+ - lib/unit.rb
47
+ - lib/units.rb
48
+ - test/unit_test.rb
49
+ - unit.gemspec
50
+ has_rdoc: true
51
+ homepage: http://github.com/minad/si
52
+ licenses: []
53
+
54
+ post_install_message:
55
+ rdoc_options:
56
+ - --main
57
+ - README.txt
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ requirements: []
73
+
74
+ rubyforge_project: unit
75
+ rubygems_version: 1.6.2
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: Scientific unit support for ruby for calculations
79
+ test_files: []
80
+