phys-units 0.9.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.
@@ -0,0 +1,123 @@
1
+ # parse.y
2
+ #
3
+ # Copyright (c) 2001-2013 Masahiro Tanaka <masa16.tanaka@gmail.com>
4
+ #
5
+ # This program is free software.
6
+ # You can distribute/modify this program under the terms of
7
+ # the GNU General Public License version 3 or later.
8
+
9
+ class Parse
10
+
11
+ prechigh
12
+ left '|'
13
+ right POW
14
+ nonassoc '(' WORD NUMBER UFUNC
15
+ left '*' MULTIPLY
16
+ left DIV
17
+ left UNARY
18
+ left '+' '-'
19
+ preclow
20
+
21
+ rule
22
+
23
+ target: expr
24
+ | DIV list { result = Unit.inv(val[1]) }
25
+ ;
26
+
27
+ expr: list
28
+ | '-' list = UNARY { result = -val[1] }
29
+ | expr '+' expr { result = val[0] + val[2] }
30
+ | expr '-' expr { result = val[0] - val[2] }
31
+ | expr '*' expr { result = val[0] * val[2] }
32
+ | expr DIV expr { result = val[0] / val[2] }
33
+ ;
34
+
35
+ numexpr: NUMBER
36
+ | numexpr '|' numexpr { result = Unit.rdiv(val[0],val[2]) }
37
+ ;
38
+
39
+ pexpr: '(' expr ')' { result = val[1] }
40
+ ;
41
+
42
+ list: numexpr
43
+ | pexpr
44
+ | WORD { result = Unit.word(val[0]) }
45
+ | list list = MULTIPLY { result = val[0] * val[1] }
46
+ | list POW list { result = val[0]** val[2] }
47
+ | list POW '-' list = POW { result = val[0]**(-val[3]) }
48
+ | UFUNC pexpr { result = Unit.func(val[0],val[1]) }
49
+ ;
50
+
51
+ end
52
+
53
+ ---- header ----
54
+
55
+ # -*- coding: utf-8 -*-
56
+ # parse.y, parse.rb
57
+ #
58
+ # by Masahiro Tanaka <masa16.tanaka@gmail.com>
59
+ #
60
+ module Phys
61
+ class Unit
62
+ ---- inner ----
63
+
64
+ def build_num(ov,ud,pw)
65
+ if ud.nil? && pw.nil?
66
+ #ov.to_i
67
+ Rational(ov.to_i)
68
+ else
69
+ m1 = ud ? ud.size : 0
70
+ pw = pw ? pw.to_i : 0
71
+ m2 = pw-m1
72
+ ov = ov ? ov.to_i : 0
73
+ ud = ud ? ud.to_i : 0
74
+ a = ov*10**m1 + ud
75
+ b = 1
76
+ a *= 10**m2 if m2>0
77
+ b *= 10**(-m2) if m2<0
78
+ Rational(a,b)
79
+ end
80
+ end
81
+
82
+ def parse( str )
83
+ return Unit.new(str) if str.empty?
84
+ #p str
85
+ @q = []
86
+
87
+ c = Unit.unit_chars
88
+
89
+ while str.size > 0 do
90
+ case str
91
+ when /\A[\s]+/o
92
+ when /\A(\d+)(?:(?:\.(\d*))?(?:[eE]([+-]?\d+))?)/o
93
+ @q.push [:NUMBER, build_num($1,$2,$3)]
94
+ when /\A(sin|cos|tan|log|ln|log2)\b/o
95
+ @q.push [:UFUNC, $&]
96
+ when /\A\//o
97
+ @q.push [:DIV, $&]
98
+ when /\Aper\b/o
99
+ @q.push [:DIV, $&]
100
+ when /\A[^#{c}0-9,.-]+([^#{c}$-]*[^#{c}1-9,.])?/o
101
+ @q.push [:WORD, $&]
102
+ when /\A[%'"]'?/o
103
+ @q.push [:WORD, $&]
104
+ when /\A\^|\A\*\*/o
105
+ @q.push [:POW, $&]
106
+ when /\A./o
107
+ @q.push [$&,$&]
108
+ end
109
+ str = $' #'
110
+ end
111
+ @q.push [false, '$end']
112
+
113
+ do_parse
114
+ end
115
+
116
+ def next_token
117
+ @q.shift
118
+ end
119
+
120
+ ---- footer ----
121
+
122
+ end
123
+ end
@@ -0,0 +1,252 @@
1
+ #
2
+ # phys/units/quantity.rb
3
+ #
4
+ # Copyright (c) 2001-2013 Masahiro Tanaka <masa16.tanaka@gmail.com>
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the terms of
8
+ # the GNU General Public License version 3 or later.
9
+
10
+ module Phys
11
+
12
+ def Quantity(*a)
13
+ Quantity.new(*a)
14
+ end
15
+
16
+ #== Usage
17
+ # require 'phys/units'
18
+ # Q=Phys::Quantity
19
+ # Q[1.23,'km'] + Q[4.56,'m'] #=> Phys::Quanty[1.23456,'km']
20
+ # Q[123,'mile'] / Q[2,'hr'] #=> Phys::Quanty[61,'mile/hr']
21
+ # Q[61,'miles/hr'].want('m/s') #=> Phys::Quanty[27.26944,'m/s']
22
+ # Q[1.0,'are'] == Q[10,'m']**2 #=> true
23
+ # Q[70,'tempF'] + Q[10,'tempC'] #=> Phys::Quantity[88,'tempF']
24
+ # Q[20,'tempC'].want('tempF') #=> Phys::Quantity[68,'tempF']
25
+ # Math.cos(Q[60,'degree'].to_f) #=> 0.5
26
+ class Quantity
27
+
28
+ class << self
29
+ # Same as Quantity.new.
30
+ def [](*a)
31
+ self.new(*a)
32
+ end
33
+ end
34
+
35
+ # Initialize a new quantity.
36
+ # _value_: Numeric value of quantity.
37
+ # _expr_: Unit string. Result of Unit.parse(_expr_) is used as a unit.
38
+ # _unit_: (optional) Unit of quantity instead of parsing _expr_.
39
+ def initialize(value,expr=nil,unit=nil)
40
+ @val = value
41
+ expr = expr.to_s if Symbol===expr
42
+ @expr = (expr=='') ? nil : expr
43
+ @unit = unit
44
+ if @unit.nil?
45
+ @unit = Unit.parse(@expr||1)
46
+ elsif !@unit.kind_of? Unit
47
+ raise ArgumentError, "third arg must be Phys::Unit"
48
+ end
49
+ end
50
+
51
+ attr_reader :val
52
+ attr_reader :expr
53
+ attr_reader :unit
54
+
55
+ # Returns the value of the quantity.
56
+ alias value val
57
+
58
+ # Conversion to a quantity in another _expr_ unit.
59
+ def want(expr)
60
+ unit = Unit.parse(expr)
61
+ val = unit.convert(self)
62
+ self.class.new( val, expr, unit )
63
+ end
64
+ alias convert want
65
+
66
+ # Addition of two quantities.
67
+ # Operation is made after the unit of _other_ is
68
+ # converted to the unit of _self_.
69
+ # Exception is raised if unit conversion is failed.
70
+ # Returns an instance of Quantity class in the unit of former quantity.
71
+ def +(other)
72
+ val = @val + @unit.convert_scale(other)
73
+ self.class.new( val, @expr, @unit )
74
+ end
75
+
76
+ # Subtraction of two quantities.
77
+ # Operation is made after the unit of _other_ is
78
+ # converted to the unit of _self_.
79
+ # Exception is raised if unit conversion is failed.
80
+ # Returns an instance of Quantity class in the unit of former quantity.
81
+ def -(other)
82
+ val = @val - @unit.convert_scale(other)
83
+ self.class.new( val, @expr, @unit )
84
+ end
85
+
86
+ %w[abs ceil round floor truncate].each do |s|
87
+ define_method(s) do
88
+ self.class.new( @val.send(s), @expr, @unit )
89
+ end
90
+ end
91
+
92
+ # Unary Plus. Returns self.
93
+ def +@ ; self.class.new( @val, @expr, @unit ) end
94
+
95
+ # Unary Minus. Returns the negated quantity.
96
+ def -@ ; self.class.new( -@val, @expr, @unit ) end
97
+
98
+ # Comparison of quantities. Comparison is made after
99
+ # converting _other_ to a quantity in the unit of _self_.
100
+ def <=> (other); @val <=> @unit.convert(other) end
101
+
102
+ # Comparison of quantities. Comparison is made after
103
+ # converting _other_ to a quantity in the unit of _self_.
104
+ def == (other); @val == @unit.convert(other) end
105
+
106
+ # Comparison of quantities. Comparison is made after
107
+ # converting _other_ to a quantity in the unit of _self_.
108
+ def >= (other); @val >= @unit.convert(other) end
109
+
110
+ # Comparison of quantities. Comparison is made after
111
+ # converting _other_ to a quantity in the unit of _self_.
112
+ def <= (other); @val <= @unit.convert(other) end
113
+
114
+ # Comparison of quantities. Comparison is made after
115
+ # converting _other_ to a quantity in the unit of _self_.
116
+ def < (other); @val < @unit.convert(other) end
117
+
118
+ # Comparison of quantities. Comparison is made after
119
+ # converting _other_ to a quantity in the unit of _self_.
120
+ def > (other); @val > @unit.convert(other) end
121
+
122
+ # Power of a quantity.
123
+ # Returns an instance of Quantity class in a powerd unit.
124
+ def **(n)
125
+ if @expr.nil?
126
+ expr = nil
127
+ elsif /^[A-Za-z_]+&/o =~ @expr
128
+ expr = @expr+'^'+n.to_s
129
+ else
130
+ expr = '('+@expr+')^'+n.to_s+''
131
+ end
132
+ self.class.new( @val**n, expr, @unit**n )
133
+ end
134
+
135
+ def enclose_expr #:nodoc:
136
+ return nil if @expr.nil?
137
+ if /\/|\||per/o =~ @expr
138
+ '('+@expr+')'
139
+ else
140
+ @expr
141
+ end
142
+ end
143
+
144
+ def enclose_expr_div #:nodoc:
145
+ return nil if @expr.nil?
146
+ if /\w[^\w]+\w/o =~ @expr
147
+ '/('+@expr+')'
148
+ else
149
+ '/'+@expr
150
+ end
151
+ end
152
+
153
+ # Multiplication of two quantities.
154
+ # Returns an instance of Quantity class in a multiplied unit.
155
+ def *(other)
156
+ if Quantity===other
157
+ a = [self.enclose_expr, other.enclose_expr]
158
+ a.delete(nil)
159
+ self.class.new( @val*other.val, a.join(' '), @unit*other.unit )
160
+ else
161
+ self.class.new( @val*other, @expr, @unit )
162
+ end
163
+ end
164
+
165
+ # Division of two quantities.
166
+ # Returns an instance of Quantity class in a divided unit.
167
+ %w[/ div quo].each do |s|
168
+ define_method(s) do |other|
169
+ if Quantity===other
170
+ a = [self.enclose_expr, other.enclose_expr_div]
171
+ a.delete(nil)
172
+ self.class.new( @val.send(s,other.val), a.join, @unit/other.unit )
173
+ else
174
+ self.class.new( @val.send(s,other), @expr, @unit )
175
+ end
176
+ end
177
+ end
178
+ alias fdiv quo
179
+
180
+ %w[% remainder].each do |s|
181
+ define_method(s) do |other|
182
+ other = (Quantity===other) ? other.val : other
183
+ self.class.new( @val.send(s,other), @expr, @unit )
184
+ end
185
+ end
186
+ alias modulo %
187
+
188
+ def coerce(other)
189
+ [ self.class.new(other), self ]
190
+ end
191
+
192
+ def abs
193
+ self.class.new( @val.abs, @expr, @unit )
194
+ end
195
+
196
+ def abs2
197
+ self**2
198
+ end
199
+
200
+ # Conversion to base unit.
201
+ # Returns the quantity converted to a base unit.
202
+ def to_base_unit
203
+ unit = @unit.base_unit
204
+ val = unit.convert(self)
205
+ expr = unit.unit_string
206
+ self.class.new( val, expr, unit )
207
+ end
208
+ alias to_si to_base_unit
209
+ alias to_SI to_base_unit
210
+
211
+ # Conversion to Numeric.
212
+ # Returns Numeric if the unit is dimensionless.
213
+ # Raises an Error if the unit is non-dminensionless.
214
+ def to_numeric
215
+ @unit.convert_to_numeric(@val)
216
+ end
217
+
218
+ def to_f
219
+ to_numeric.to_f
220
+ end
221
+ alias to_float to_f
222
+
223
+ def to_i
224
+ to_numeric.to_i
225
+ end
226
+ alias to_int to_i
227
+ alias to_integer to_i
228
+
229
+ def to_r
230
+ to_numeric.to_r
231
+ end
232
+ alias to_rational to_r
233
+
234
+ def to_s
235
+ if @expr
236
+ expr = ",'" +@expr+"'"
237
+ else
238
+ expr = ""
239
+ end
240
+ self.class.to_s+"["+Unit::Utils.num_inspect(@val)+expr+"]"
241
+ end
242
+
243
+ def inspect
244
+ if @expr
245
+ expr = "," +@expr.inspect
246
+ else
247
+ expr = ""
248
+ end
249
+ "#<"+self.class.to_s+" "+Unit::Utils.num_inspect(@val)+expr+", "+@unit.inspect+">"
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,446 @@
1
+ #
2
+ # phys/units/unit.rb
3
+ #
4
+ # Copyright (c) 2001-2013 Masahiro Tanaka <masa16.tanaka@gmail.com>
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the terms of
8
+ # the GNU General Public License version 3 or later.
9
+
10
+ module Phys
11
+
12
+ class Unit
13
+
14
+ LIST = {}
15
+ PREFIX = {}
16
+
17
+ def self.prefix_regex
18
+ @@prefix_regex
19
+ end
20
+
21
+ def initialize(arg,expr=nil,offset=nil)
22
+ case arg
23
+ when Numeric
24
+ arg = Rational(arg) if Integer===arg
25
+ @factor = arg
26
+ alloc_dim(expr)
27
+ when Phys::Unit
28
+ replace(arg)
29
+ when String
30
+ @name = arg
31
+ if expr.kind_of? Phys::Unit
32
+ @factor = expr.factor
33
+ @offset = expr.offset
34
+ alloc_dim expr.dim
35
+ else
36
+ @expr = expr
37
+ end
38
+ else
39
+ raise TypeError,"invalid argument : #{arg.inspect}"
40
+ end
41
+ end
42
+
43
+ attr_reader :name, :offset, :expr
44
+
45
+ def dim
46
+ use_dimension
47
+ @dim
48
+ end
49
+ alias dimension dim
50
+
51
+ def factor
52
+ use_dimension
53
+ @factor
54
+ end
55
+
56
+ def dimension_value
57
+ 1
58
+ end
59
+
60
+ def replace(x)
61
+ @name = x.name.dup if x.name
62
+ @factor = x.factor
63
+ @offset = x.offset
64
+ alloc_dim x.dim
65
+ end
66
+
67
+ def alloc_dim(hash=nil)
68
+ case hash
69
+ when Hash
70
+ @dim = hash.dup
71
+ else
72
+ @dim = {}
73
+ end
74
+ @dim.default = 0
75
+ end
76
+
77
+ def use_dimension
78
+ return if @dim && @factor
79
+ if @expr && @dim.nil?
80
+ puts "unit='#{@name}', parsing '#{@expr}'..." if Unit.debug
81
+ unit = Unit.parse(@expr)
82
+ case unit
83
+ when Unit
84
+ @dim = unit.dim
85
+ @factor = unit.factor
86
+ if @dim.nil? || @factor.nil?
87
+ raise UnitParseError,"parse error : #{unit.inspect}"
88
+ end
89
+ when Numeric
90
+ @factor = unit
91
+ alloc_dim
92
+ else
93
+ raise UnitParseError,"parse error : #{self.inspect}"
94
+ end
95
+ else
96
+ raise UnitParseError,"undefined unit?: #{self.inspect}"
97
+ end
98
+ end
99
+
100
+ def inspect
101
+ a = [Utils.num_inspect(@factor), @dim.inspect]
102
+ a << "@name="+@name.inspect if @name
103
+ a << "@expr="+@expr.inspect if @expr
104
+ a << "@offset="+@offset.inspect if @offset
105
+ a << "@dimensionless=true" if @dimensionless
106
+ if @dimension_value && @dimension_value!=1
107
+ a << "@dimension_value="+@dimension_value.inspect
108
+ end
109
+ s = a.join(",")
110
+ "#<#{self.class} #{s}>"
111
+ end
112
+
113
+ def unit_string
114
+ use_dimension
115
+ a = []
116
+ a << Utils.num_inspect(@factor) if @factor!=1
117
+ a += @dim.map do |k,d|
118
+ if d==1
119
+ k
120
+ else
121
+ "#{k}^#{d}"
122
+ end
123
+ end
124
+ a.join(" ")
125
+ end
126
+ alias string_form unit_string
127
+
128
+ # Unit conversion
129
+
130
+ def conversion_factor
131
+ use_dimension
132
+ f = @factor
133
+ @dim.each do |k,d|
134
+ if d != 0
135
+ u = LIST[k]
136
+ if u.dimensionless?
137
+ f *= u.dimension_value**d
138
+ end
139
+ end
140
+ end
141
+ f
142
+ end
143
+
144
+ def scalar?
145
+ use_dimension
146
+ (@dim.nil? || @dim.empty?) && @factor==1
147
+ end
148
+
149
+ def dimensionless_deleted
150
+ use_dimension
151
+ hash = @dim.dup
152
+ hash.delete_if{|k,v| LIST[k].dimensionless?}
153
+ end
154
+
155
+ def dimensionless?
156
+ use_dimension
157
+ @dim.each_key.all?{|k| LIST[k].dimensionless?}
158
+ end
159
+
160
+ def same_dimension?(x)
161
+ dimensionless_deleted == x.dimensionless_deleted
162
+ end
163
+ alias same_dim? same_dimension?
164
+
165
+ def assert_dimensionless
166
+ if !dimensionless?
167
+ raise UnitConversionError,"Not dimensionless: #{self.inspect}"
168
+ end
169
+ end
170
+
171
+ def assert_same_dimension(x)
172
+ if !same_dimension?(x)
173
+ raise UnitConversionError,"Different dimension: #{self.inspect} and #{x.inspect}"
174
+ end
175
+ end
176
+
177
+ def convert(q)
178
+ if Quantity===q
179
+ assert_same_dimension(q.unit)
180
+ v = q.unit.convert_to_base(q.value)
181
+ convert_from_base(v)
182
+ else
183
+ q / to_num
184
+ end
185
+ end
186
+
187
+ def convert_scale(q)
188
+ convert(q)
189
+ end
190
+
191
+ def convert_to_base(x)
192
+ x * conversion_factor
193
+ end
194
+
195
+ def convert_from_base(x)
196
+ x / conversion_factor
197
+ end
198
+
199
+ def convert_to_numeric(x)
200
+ assert_dimensionless
201
+ x * conversion_factor
202
+ end
203
+
204
+ def to_num
205
+ assert_dimensionless
206
+ conversion_factor
207
+ end
208
+
209
+ def convert_to_float(x)
210
+ convert_to_numeric(x).to_f
211
+ end
212
+
213
+ def base_unit
214
+ Unit.new(1,dim)
215
+ end
216
+
217
+ # Unit operation
218
+
219
+ def operable?
220
+ true
221
+ end
222
+
223
+ def check_operable
224
+ if !operable?
225
+ raise UnitOperationError,"non-operable for #{inspect}"
226
+ end
227
+ end
228
+
229
+ def check_operable2(x)
230
+ if !(operable? && x.operable?)
231
+ raise UnitOperationError,"non-operable: #{inspect} and #{x.inspect}"
232
+ end
233
+ end
234
+
235
+ def dimension_binop(other)
236
+ x = self.dim
237
+ y = other.dim
238
+ if Hash===x
239
+ if Hash===y
240
+ keys = x.keys | y.keys
241
+ dims = {}
242
+ dims.default = 0
243
+ keys.each do |k|
244
+ v = yield( x[k]||0, y[k]||0 )
245
+ dims[k] = v if v!=0
246
+ end
247
+ dims
248
+ else
249
+ x.dup
250
+ end
251
+ else
252
+ raise "dimensin not defined"
253
+ end
254
+ end
255
+
256
+ def dimension_uop
257
+ x = self.dim
258
+ if Hash===x
259
+ dims = {}
260
+ dims.default = 0
261
+ x.each do |k,d|
262
+ v = yield( d )
263
+ dims[k] = v if v!=0
264
+ end
265
+ dims
266
+ else
267
+ raise "dimensin not defined"
268
+ end
269
+ end
270
+
271
+ def +(x)
272
+ x = Unit.cast(x)
273
+ check_operable2(x)
274
+ assert_same_dimension(x)
275
+ Unit.new(@factor+x.factor,@dim.dup)
276
+ end
277
+
278
+ def -(x)
279
+ x = Unit.cast(x)
280
+ check_operable2(x)
281
+ assert_same_dimension(x)
282
+ Unit.new(@factor-x.factor,@dim.dup)
283
+ end
284
+
285
+ def -@
286
+ check_operable
287
+ use_dimension
288
+ Unit.new(-@factor,@dim.dup)
289
+ end
290
+
291
+ def +@
292
+ self
293
+ end
294
+
295
+ def *(x)
296
+ y = Unit.cast(x)
297
+ if scalar?
298
+ return y
299
+ elsif y.scalar?
300
+ return self
301
+ end
302
+ check_operable2(y)
303
+ dims = dimension_binop(y){|a,b| a+b}
304
+ factor = self.factor * y.factor
305
+ Unit.new(factor,dims)
306
+ end
307
+
308
+ def /(x)
309
+ y = Unit.cast(x)
310
+ if scalar?
311
+ return y.inv
312
+ elsif y.scalar?
313
+ return self
314
+ end
315
+ check_operable2(y)
316
+ dims = dimension_binop(y){|a,b| a-b}
317
+ factor = self.factor / y.factor
318
+ Unit.new(factor,dims)
319
+ end
320
+
321
+ def rdiv(x)
322
+ y = Unit.cast(x)
323
+ if scalar?
324
+ return y.inv
325
+ elsif y.scalar?
326
+ return self
327
+ end
328
+ check_operable2(y)
329
+ dims = dimension_binop(y){|a,b| a-b}
330
+ factor = Rational(self.factor,x.factor)
331
+ Unit.new(factor,dims)
332
+ end
333
+
334
+ def self.rdiv(x,y)
335
+ Unit.cast(x).rdiv(y)
336
+ end
337
+
338
+ def inv
339
+ check_operable
340
+ dims = dimension_uop{|a| -a}
341
+ Unit.new(Rational(1,self.factor), dims)
342
+ end
343
+
344
+ def **(x)
345
+ check_operable
346
+ m = Utils.as_numeric(x)
347
+ dims = dimension_uop{|a| a*m}
348
+ Unit.new(@factor**m,dims)
349
+ end
350
+
351
+ def self.func(fn, x)
352
+ fn = 'log' if fn == 'ln'
353
+ m = Unit.new(x).to_num
354
+ Unit.new( Math.send(fn,m) )
355
+ end
356
+
357
+ def ==(x)
358
+ use_dimension
359
+ @factor == x.factor && @dim == x.dim &&
360
+ offset == x.offset && dimension_value == x.dimension_value
361
+ end
362
+
363
+ def coerce(x)
364
+ [Unit.find_unit(x), self]
365
+ end
366
+
367
+ end # Unit
368
+
369
+
370
+ class BaseUnit < Unit
371
+ def initialize(s,dimless=false,v=nil)
372
+ case s
373
+ when String
374
+ @name = s
375
+ @factor = 1
376
+ @dim = {s=>1}
377
+ @dim.default = 0
378
+ @dimensionless = dimless
379
+ @dimension_value = v || 1
380
+ else
381
+ raise ArgumentError "BaseUnit#initialize: arg must be string: #{s}"
382
+ end
383
+ end
384
+
385
+ def replace(x)
386
+ super(x)
387
+ @dimensionless = x.dimensionless
388
+ @dimension_value = x.dimension_value
389
+ end
390
+
391
+ def use_dimension
392
+ end
393
+
394
+ def dimensionless?
395
+ @dimensionless
396
+ end
397
+
398
+ def dimensionless_deleted
399
+ if @dimensionless
400
+ {}
401
+ else
402
+ @dim.dup
403
+ end
404
+ end
405
+
406
+ attr_reader :dimension_value
407
+ end
408
+
409
+
410
+ class OffsetUnit < Unit
411
+
412
+ def self.define(name,unit,offset=nil)
413
+ LIST[name] = self.new(name,unit,offset)
414
+ end
415
+
416
+ def initialize(name,arg,offset)
417
+ super(name,arg)
418
+ @offset = offset
419
+ if offset.nil?
420
+ raise ArgumentError,"offset is not supplied"
421
+ end
422
+ end
423
+
424
+ def convert_to_base(x)
425
+ x * conversion_factor + @offset
426
+ end
427
+
428
+ def convert_from_base(x)
429
+ (x - @offset) / conversion_factor
430
+ end
431
+
432
+ def convert_scale(q)
433
+ if Quantity===q
434
+ assert_same_dimension(q.unit)
435
+ v = q.value * q.unit.conversion_factor
436
+ v = v / self.conversion_factor
437
+ else
438
+ raise TypeError,"not Quantitiy: #{q.inspect}"
439
+ end
440
+ end
441
+
442
+ def operable?
443
+ false
444
+ end
445
+ end
446
+ end