unit 0.3.0 → 0.4.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/.gitignore +1 -0
- data/Rakefile +16 -4
- data/lib/unit/class.rb +140 -59
- data/lib/unit/dsl.rb +5 -4
- data/lib/unit/system.rb +3 -0
- data/lib/unit/systems/si.yml +1 -1
- data/lib/unit/version.rb +1 -1
- data/spec/error_spec.rb +27 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/unit_one.rb +46 -0
- data/{test/system_test.rb → spec/system_spec.rb} +13 -5
- data/spec/unit_spec.rb +225 -0
- data/{test → spec}/yml/filename.yml +0 -0
- data/{test → spec}/yml/io.yml +0 -0
- data/unit.gemspec +2 -2
- metadata +43 -50
- data/test/error_test.rb +0 -16
- data/test/unit_test.rb +0 -144
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gemfile.lock
|
data/Rakefile
CHANGED
@@ -1,6 +1,18 @@
|
|
1
|
-
task :default => :test
|
2
1
|
|
3
|
-
|
4
|
-
task :
|
5
|
-
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
task :default => :spec
|
4
|
+
|
5
|
+
desc "Run specs"
|
6
|
+
task :spec => ["spec:no_dsl", "spec:dsl"]
|
7
|
+
|
8
|
+
namespace :spec do
|
9
|
+
desc "Run specs without DSL"
|
10
|
+
RSpec::Core::RakeTask.new(:no_dsl) do |t|
|
11
|
+
t.rspec_opts = "-t ~dsl"
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Run specs with DSL"
|
15
|
+
RSpec::Core::RakeTask.new(:dsl) do |t|
|
16
|
+
# Run all tests
|
17
|
+
end
|
6
18
|
end
|
data/lib/unit/class.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
class Unit < Numeric
|
3
3
|
attr_reader :value, :normalized, :unit, :system
|
4
4
|
|
5
|
+
class IncompatibleUnitError < TypeError; end
|
6
|
+
|
5
7
|
def initialize(value, unit, system)
|
6
8
|
@system = system
|
7
9
|
@value = value
|
@@ -44,21 +46,33 @@ class Unit < Numeric
|
|
44
46
|
end
|
45
47
|
|
46
48
|
def *(other)
|
47
|
-
|
48
|
-
|
49
|
+
if Numeric === other
|
50
|
+
other = coerce_numeric(other)
|
51
|
+
Unit.new(other.value * self.value, other.unit + self.unit, system)
|
52
|
+
else
|
53
|
+
apply_through_coercion(other, __method__)
|
54
|
+
end
|
49
55
|
end
|
50
56
|
|
51
57
|
def /(other)
|
52
|
-
|
53
|
-
|
54
|
-
|
58
|
+
if Numeric === other
|
59
|
+
other = coerce_numeric(other)
|
60
|
+
Unit.new(Integer === value && Integer === other.value ?
|
61
|
+
Rational(value, other.value) : value / other.value,
|
62
|
+
unit + Unit.power_unit(other.unit, -1), system)
|
63
|
+
else
|
64
|
+
apply_through_coercion(other, __method__)
|
65
|
+
end
|
55
66
|
end
|
56
67
|
|
57
68
|
def +(other)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
69
|
+
if Numeric === other
|
70
|
+
other = coerce_numeric_compatible(other)
|
71
|
+
a, b = self.normalize, other.normalize
|
72
|
+
Unit.new(a.value + b.value, b.unit, system).in(self)
|
73
|
+
else
|
74
|
+
apply_through_coercion(other, __method__)
|
75
|
+
end
|
62
76
|
end
|
63
77
|
|
64
78
|
def **(exp)
|
@@ -67,23 +81,49 @@ class Unit < Numeric
|
|
67
81
|
end
|
68
82
|
|
69
83
|
def -(other)
|
70
|
-
|
84
|
+
if Numeric === other
|
85
|
+
other = coerce_numeric_compatible(other)
|
86
|
+
a, b = self.normalize, other.normalize
|
87
|
+
Unit.new(a.value - b.value, b.unit, system).in(self)
|
88
|
+
else
|
89
|
+
apply_through_coercion(other, __method__)
|
90
|
+
end
|
71
91
|
end
|
72
92
|
|
73
93
|
def -@
|
74
94
|
Unit.new(-value, unit, system)
|
75
95
|
end
|
76
96
|
|
97
|
+
def abs
|
98
|
+
Unit.new(value.abs, unit, system)
|
99
|
+
end
|
100
|
+
|
101
|
+
def zero?
|
102
|
+
value.zero?
|
103
|
+
end
|
104
|
+
|
77
105
|
def ==(other)
|
78
|
-
|
79
|
-
|
80
|
-
|
106
|
+
if Numeric === other
|
107
|
+
other = coerce_numeric(other)
|
108
|
+
a, b = self.normalize, other.normalize
|
109
|
+
a.value == b.value && a.unit == b.unit
|
110
|
+
else
|
111
|
+
apply_through_coercion(other, __method__)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def eql?(other)
|
116
|
+
Unit === other && value.eql?(other.value) && unit == other.unit
|
81
117
|
end
|
82
118
|
|
83
119
|
def <=>(other)
|
84
|
-
|
85
|
-
|
86
|
-
|
120
|
+
if Numeric === other
|
121
|
+
other = coerce_numeric_compatible(other)
|
122
|
+
a, b = self.normalize, other.normalize
|
123
|
+
a.value <=> b.value
|
124
|
+
else
|
125
|
+
apply_through_coercion(other, __method__)
|
126
|
+
end
|
87
127
|
end
|
88
128
|
|
89
129
|
# Number without dimension
|
@@ -91,22 +131,28 @@ class Unit < Numeric
|
|
91
131
|
normalize.unit.empty?
|
92
132
|
end
|
93
133
|
|
94
|
-
|
134
|
+
alias_method :unitless?, :dimensionless?
|
95
135
|
|
96
136
|
# Compatible units can be added
|
97
137
|
def compatible?(other)
|
98
|
-
|
99
|
-
a, b = a.normalize, b.normalize
|
100
|
-
a.unit == b.unit
|
138
|
+
self.normalize.unit == Unit.to_unit(other, system).normalize.unit
|
101
139
|
end
|
102
140
|
|
103
|
-
|
141
|
+
alias_method :compatible_with?, :compatible?
|
104
142
|
|
105
143
|
# Convert to other unit
|
106
144
|
def in(unit)
|
107
|
-
|
108
|
-
|
109
|
-
|
145
|
+
conversion = Unit.new(1, Unit.to_unit(unit, system).unit, system)
|
146
|
+
(self / conversion).normalize * conversion
|
147
|
+
end
|
148
|
+
|
149
|
+
def in!(unit)
|
150
|
+
unit = coerce_object(unit)
|
151
|
+
result = self.in(unit)
|
152
|
+
unless result.unit == unit.unit
|
153
|
+
raise TypeError, "Unexpected #{result.inspect}, expected to be in #{other_unit.unit_string}"
|
154
|
+
end
|
155
|
+
result
|
110
156
|
end
|
111
157
|
|
112
158
|
def inspect
|
@@ -114,7 +160,7 @@ class Unit < Numeric
|
|
114
160
|
end
|
115
161
|
|
116
162
|
def to_s
|
117
|
-
unit.empty? ? value.to_s : "#{value} #{unit_string
|
163
|
+
unit.empty? ? value.to_s : "#{value} #{unit_string}"
|
118
164
|
end
|
119
165
|
|
120
166
|
def to_tex
|
@@ -130,40 +176,20 @@ class Unit < Numeric
|
|
130
176
|
end
|
131
177
|
|
132
178
|
def approx
|
133
|
-
|
134
|
-
end
|
135
|
-
|
136
|
-
def coerce(val)
|
137
|
-
[self, Unit.to_unit(val, system)]
|
138
|
-
end
|
139
|
-
|
140
|
-
def self.to_unit(object, system = nil)
|
141
|
-
system ||= Unit.default_system
|
142
|
-
case object
|
143
|
-
when Unit
|
144
|
-
raise TypeError, 'Different unit system' if object.system != system
|
145
|
-
object
|
146
|
-
when Array
|
147
|
-
system.validate_unit(object)
|
148
|
-
Unit.new(1, object, system)
|
149
|
-
when String, Symbol
|
150
|
-
unit = system.parse_unit(object.to_s)
|
151
|
-
system.validate_unit(unit)
|
152
|
-
Unit.new(1, unit, system)
|
153
|
-
when Numeric
|
154
|
-
Unit.new(object, [], system)
|
155
|
-
else
|
156
|
-
raise TypeError, "#{object.class} has no unit support"
|
157
|
-
end
|
179
|
+
Unit.new(self.to_f, unit, system)
|
158
180
|
end
|
159
181
|
|
160
|
-
|
182
|
+
def coerce(other)
|
183
|
+
[coerce_numeric(other), self]
|
184
|
+
end
|
161
185
|
|
162
|
-
def unit_string(sep)
|
186
|
+
def unit_string(sep = '·')
|
163
187
|
(unit_list(@unit.select {|factor, name, exp| exp >= 0 }) +
|
164
188
|
unit_list(@unit.select {|factor, name, exp| exp < 0 })).join(sep)
|
165
189
|
end
|
166
190
|
|
191
|
+
private
|
192
|
+
|
167
193
|
def unit_list(list)
|
168
194
|
units = []
|
169
195
|
list.each do |factor, name, exp|
|
@@ -176,18 +202,14 @@ class Unit < Numeric
|
|
176
202
|
units.sort
|
177
203
|
end
|
178
204
|
|
179
|
-
def self.power_unit(unit, pow)
|
180
|
-
unit.map {|factor, name, exp| [factor, name, exp * pow] }
|
181
|
-
end
|
182
|
-
|
183
205
|
# Reduce units and factors
|
184
206
|
def reduce!
|
185
207
|
# Remove numbers from units
|
186
208
|
numbers = @unit.select {|factor, unit, exp| Numeric === unit }
|
187
209
|
@unit -= numbers
|
188
210
|
numbers.each do |factor, number, exp|
|
189
|
-
|
190
|
-
|
211
|
+
raise RuntimeError, 'Numeric unit with factor' if factor != :one
|
212
|
+
@value *= number ** exp
|
191
213
|
end
|
192
214
|
|
193
215
|
# Reduce units
|
@@ -227,7 +249,66 @@ class Unit < Numeric
|
|
227
249
|
self
|
228
250
|
end
|
229
251
|
|
230
|
-
|
252
|
+
# Given another object and an operator, use the other object's #coerce method
|
253
|
+
# to perform the operation.
|
254
|
+
#
|
255
|
+
# Based on Matrix#apply_through_coercion
|
256
|
+
def apply_through_coercion(obj, oper)
|
257
|
+
coercion = obj.coerce(self)
|
258
|
+
raise TypeError unless coercion.is_a?(Array) && coercion.length == 2
|
259
|
+
first, last = coercion
|
260
|
+
first.send(oper, last)
|
261
|
+
rescue
|
262
|
+
raise TypeError, "#{obj.class} can't be coerced into #{self.class}"
|
263
|
+
end
|
264
|
+
|
265
|
+
def coerce_numeric_compatible(object)
|
266
|
+
object = coerce_numeric(object)
|
267
|
+
raise IncompatibleUnitError, "#{inspect} and #{object.inspect} are incompatible" if !compatible?(object)
|
268
|
+
object
|
269
|
+
end
|
270
|
+
|
271
|
+
def coerce_numeric(object)
|
272
|
+
Unit.numeric_to_unit(object, system)
|
273
|
+
end
|
274
|
+
|
275
|
+
def coerce_object(object)
|
276
|
+
Unit.to_unit(object, system)
|
277
|
+
end
|
278
|
+
|
279
|
+
class << self
|
231
280
|
attr_accessor :default_system
|
281
|
+
|
282
|
+
def power_unit(unit, pow)
|
283
|
+
unit.map {|factor, name, exp| [factor, name, exp * pow] }
|
284
|
+
end
|
285
|
+
|
286
|
+
def numeric_to_unit(object, system = nil)
|
287
|
+
system ||= Unit.default_system
|
288
|
+
case object
|
289
|
+
when Unit
|
290
|
+
raise IncompatibleUnitError, "Unit system of #{object.inspect} is incompatible with #{system.name}" if object.system != system
|
291
|
+
object
|
292
|
+
when Numeric
|
293
|
+
Unit.new(object, [], system)
|
294
|
+
else
|
295
|
+
raise TypeError, "#{object.class} can't be coerced into Unit"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def to_unit(object, system = nil)
|
300
|
+
system ||= Unit.default_system
|
301
|
+
case object
|
302
|
+
when String, Symbol
|
303
|
+
unit = system.parse_unit(object.to_s)
|
304
|
+
system.validate_unit(unit)
|
305
|
+
Unit.new(1, unit, system)
|
306
|
+
when Array
|
307
|
+
system.validate_unit(object)
|
308
|
+
Unit.new(1, object, system)
|
309
|
+
else
|
310
|
+
numeric_to_unit(object, system)
|
311
|
+
end
|
312
|
+
end
|
232
313
|
end
|
233
314
|
end
|
data/lib/unit/dsl.rb
CHANGED
@@ -13,11 +13,12 @@ class Unit < Numeric
|
|
13
13
|
name.to_s.sub(/^per_/, '1/').gsub('_per_', '/').gsub('_', ' ')
|
14
14
|
end
|
15
15
|
|
16
|
-
def method_missing(name)
|
17
|
-
if name.to_s =~ /^in_
|
18
|
-
|
16
|
+
def method_missing(name, system = nil)
|
17
|
+
if name.to_s =~ /^in_(.*?)(!?)$/
|
18
|
+
unit = Unit.method_name_to_unit($1)
|
19
|
+
$2.empty? ? self.in(unit) : self.in!(unit)
|
19
20
|
else
|
20
|
-
|
21
|
+
super(name, system || @system)
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
data/lib/unit/system.rb
CHANGED
@@ -20,6 +20,9 @@ class Unit < Numeric
|
|
20
20
|
|
21
21
|
def load(input)
|
22
22
|
case input
|
23
|
+
when Hash
|
24
|
+
raise "Invalid hash format to load system" unless (input["units"] && input["units"].first.last['def']) || input.first.last['def']
|
25
|
+
data = input['units'] || input
|
23
26
|
when IO
|
24
27
|
data = YAML.load(input.read)
|
25
28
|
when String
|
data/lib/unit/systems/si.yml
CHANGED
data/lib/unit/version.rb
CHANGED
data/spec/error_spec.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe "Errors" do
|
5
|
+
describe "TypeError when adding incompatible units" do
|
6
|
+
it "should have a nice error message" do
|
7
|
+
a = Unit(1, "meter")
|
8
|
+
b = Unit(1, "second")
|
9
|
+
lambda { a + b }.should raise_error(TypeError, "#{a.inspect} and #{b.inspect} are incompatible")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "TypeError when trying to convert incompatible unit using #in!" do
|
14
|
+
it "should have a nice error message" do
|
15
|
+
unit = Unit(1000, "m / s")
|
16
|
+
lambda { unit.in!("seconds") }.should
|
17
|
+
raise_error(TypeError, %{Unexpected Unit("1000/1 m.s^-1"), expected to be in s})
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should have a nice error message using the DSL", :dsl => true do
|
21
|
+
unit = Unit(1000, "m / s")
|
22
|
+
lambda { unit.in_seconds! }.should
|
23
|
+
raise_error(TypeError, %{Unexpected Unit("1000/1 m.s^-1"), expected to be in s})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Example test class for coercion
|
2
|
+
#
|
3
|
+
# UnitOne behaves as Unit(1) when added to a Unit, and as 1 when added to anything else.
|
4
|
+
class UnitOne
|
5
|
+
def coerce(other)
|
6
|
+
case other
|
7
|
+
when Unit
|
8
|
+
[other, Unit(1)]
|
9
|
+
else
|
10
|
+
[other, 1]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def +(other)
|
15
|
+
apply_through_coercion(other, __method__)
|
16
|
+
end
|
17
|
+
|
18
|
+
def -(other)
|
19
|
+
apply_through_coercion(other, __method__)
|
20
|
+
end
|
21
|
+
|
22
|
+
def /(other)
|
23
|
+
apply_through_coercion(other, __method__)
|
24
|
+
end
|
25
|
+
|
26
|
+
def *(other)
|
27
|
+
apply_through_coercion(other, __method__)
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
apply_through_coercion(other, __method__)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def apply_through_coercion(other, operation)
|
37
|
+
case other
|
38
|
+
when Unit
|
39
|
+
a, b = other.coerce(Unit(1))
|
40
|
+
else
|
41
|
+
a, b = other.coerce(1)
|
42
|
+
end
|
43
|
+
|
44
|
+
a.send(operation, b)
|
45
|
+
end
|
46
|
+
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
-
require '
|
3
|
-
require 'unit'
|
4
|
-
require 'unit/dsl'
|
2
|
+
require 'spec_helper'
|
5
3
|
|
6
4
|
describe "Unit" do
|
7
5
|
describe "#default_system" do
|
@@ -11,13 +9,23 @@ describe "Unit" do
|
|
11
9
|
File.open(test_file) do |file|
|
12
10
|
Unit.default_system.load(file)
|
13
11
|
end
|
14
|
-
Unit(1, "pim").should
|
12
|
+
Unit(1, "pim").should == Unit(3.14159, "m")
|
15
13
|
end
|
16
14
|
|
17
15
|
it "should load a file" do
|
18
16
|
test_file = File.join(File.dirname(__FILE__), "yml", "filename.yml")
|
19
17
|
Unit.default_system.load(test_file)
|
20
|
-
Unit(2, "dzm").should
|
18
|
+
Unit(2, "dzm").should == Unit(24, "m")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should load a hash" do
|
22
|
+
Unit.default_system.load({
|
23
|
+
'dozen_meter' => {
|
24
|
+
'sym' => 'dzm',
|
25
|
+
'def' => '12 m'
|
26
|
+
}
|
27
|
+
})
|
28
|
+
Unit(2, "dzm").should == Unit(24, "m")
|
21
29
|
end
|
22
30
|
end
|
23
31
|
end
|
data/spec/unit_spec.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
Unit.default_system.load(:scientific)
|
5
|
+
Unit.default_system.load(:imperial)
|
6
|
+
Unit.default_system.load(:misc)
|
7
|
+
|
8
|
+
describe 'Unit' do
|
9
|
+
it 'should support multiplication' do
|
10
|
+
(Unit(2, 'm') * Unit(3, 'm')).should == Unit(6, 'm^2')
|
11
|
+
(Unit(2, 'm') * 3).should == Unit(6, 'm')
|
12
|
+
(Unit(2, 'm') * Rational(3, 4)).should == Unit(3, 2, 'm')
|
13
|
+
(Unit(2, 'm') * 0.5).should == Unit(1.0, 'm')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should support division' do
|
17
|
+
(Unit(2, 'm') / Unit(3, 'm^2')).should == Unit(2, 3, '1/m')
|
18
|
+
(Unit(2, 'm') / 3).should == Unit(2, 3, 'm')
|
19
|
+
(Unit(2, 'm') / Rational(3, 4)).should == Unit(8, 3, 'm')
|
20
|
+
(Unit(2, 'm') / 0.5).should == Unit(4.0, 'm')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should support addition' do
|
24
|
+
(Unit(42, 'm') + Unit(1, 'km')).should == Unit(1042, 'm')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should support subtraction' do
|
28
|
+
(Unit(1, 'm') - Unit(1, 'cm')).should == Unit(99, 100, 'm')
|
29
|
+
(Unit(2, 'm') - Unit(1, 'm')).should == Unit(1, 'm')
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should support arithmetic with Integers when appropriate" do
|
33
|
+
(1 + Unit(1)).should == Unit(2)
|
34
|
+
(2 - Unit(1)).should == Unit(1)
|
35
|
+
(Unit(2) - 1).should == Unit(1)
|
36
|
+
(2 - Unit(-1)).should == Unit(3)
|
37
|
+
(Unit(2) - -1).should == Unit(3)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should support arithmetic with other classes using #coerce" do
|
41
|
+
(Unit(2) + UnitOne.new).should == Unit(3)
|
42
|
+
(2 + UnitOne.new).should == 3
|
43
|
+
(Unit(2) - UnitOne.new).should == Unit(1)
|
44
|
+
(2 - UnitOne.new).should == 1
|
45
|
+
(Unit(2) * UnitOne.new).should == Unit(2)
|
46
|
+
(2 * UnitOne.new).should == 2
|
47
|
+
(Unit(2) / UnitOne.new).should == Unit(2)
|
48
|
+
(2 / UnitOne.new).should == 2
|
49
|
+
|
50
|
+
(UnitOne.new + Unit(4)).should == Unit(5)
|
51
|
+
(UnitOne.new + 4).should == 5
|
52
|
+
(UnitOne.new - Unit(4)).should == Unit(-3)
|
53
|
+
(UnitOne.new - 4).should == -3
|
54
|
+
(UnitOne.new * Unit(4)).should == Unit(4)
|
55
|
+
(UnitOne.new * 4).should == 4
|
56
|
+
(UnitOne.new / Unit(4)).should == Unit(1, 4)
|
57
|
+
(UnitOne.new / 4).should == 0
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should support logic with other classes using #coerce" do
|
61
|
+
Unit(1).should == UnitOne.new
|
62
|
+
Unit(2).should > UnitOne.new
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should support eql comparison" do
|
66
|
+
Unit(1).should eql(Unit(1))
|
67
|
+
Unit(1.0).should_not eql(Unit(1))
|
68
|
+
|
69
|
+
Unit(1).should_not eql(UnitOne.new)
|
70
|
+
Unit(1.0).should_not eql(UnitOne.new)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should not support adding anything but numeric unless object is coerceable" do
|
74
|
+
expect { Unit(1) + 'string'}.to raise_error(TypeError)
|
75
|
+
expect { Unit(1) + []}.to raise_error(TypeError)
|
76
|
+
expect { Unit(1) + :symbol }.to raise_error(TypeError)
|
77
|
+
expect { Unit(1) + {}}.to raise_error(TypeError)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should support adding through zero" do
|
81
|
+
(Unit(0, "m") + Unit(1, "m")).should == Unit(1, "m")
|
82
|
+
(Unit(1, "m") + Unit(-1, "m") + Unit(1, "m")).should == Unit(1, "m")
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should check unit compatiblity' do
|
86
|
+
expect {Unit(42, 'm') + Unit(1, 's')}.to raise_error(TypeError)
|
87
|
+
expect {Unit(42, 'g') + Unit(1, 'm')}.to raise_error(TypeError)
|
88
|
+
expect {Unit(0, 'g') + Unit(1, 'm')}.to raise_error(TypeError)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should support exponentiation' do
|
92
|
+
(Unit(2, 'm') ** 3).should == Unit(8, 'm^3')
|
93
|
+
(Unit(9, 'm^2') ** 0.5).should == Unit(3.0, 'm')
|
94
|
+
(Unit(9, 'm^2') ** Rational(1, 2)).should == Unit(3, 'm')
|
95
|
+
(Unit(2, 'm') ** 1.3).should == Unit(2 ** 1.3, 'm^1.3')
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should not allow units as exponent' do
|
99
|
+
expect { Unit(42, 'g') ** Unit(1, 'm') }.to raise_error(TypeError)
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "#normalize" do
|
103
|
+
it "should return a normalized unit" do
|
104
|
+
unit = Unit(1, 'joule')
|
105
|
+
normalized_unit = Unit(1000, 'gram meter^2 / second^2')
|
106
|
+
|
107
|
+
unit.normalize.should eql normalized_unit
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should not modify the receiver" do
|
111
|
+
unit = Unit(1, 'joule')
|
112
|
+
normalized_unit = Unit(1000, 'gram meter^2 / second^2')
|
113
|
+
|
114
|
+
unit.normalize
|
115
|
+
unit.should_not eql normalized_unit
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#normalize!" do
|
120
|
+
it "should return a normalized unit" do
|
121
|
+
unit = Unit(1, 'joule')
|
122
|
+
normalized_unit = Unit(1000, 'gram meter^2 / second^2')
|
123
|
+
|
124
|
+
unit.normalize!.should eql normalized_unit
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should modify the receiver" do
|
128
|
+
unit = Unit(1, 'joule')
|
129
|
+
normalized_unit = Unit(1000, 'gram meter^2 / second^2')
|
130
|
+
|
131
|
+
unit.normalize!
|
132
|
+
unit.should eql normalized_unit
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should convert units' do
|
137
|
+
Unit(1, "MeV").in("joule").should == Unit(1.602176487e-13, 'joule')
|
138
|
+
Unit(1, "kilometer").in("meter").should == Unit(1000, 'meter')
|
139
|
+
Unit(1, "liter").in('meter^3').should == Unit(1, 1000, 'meter^3')
|
140
|
+
Unit(1, "kilometer/hour").in("meter/second").should == Unit(5, 18, 'meter/second')
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should have a working compatible? method' do
|
144
|
+
Unit(7, "meter").compatible?('kilogram').should == false
|
145
|
+
Unit(3, "parsec").compatible_with?('meter').should == true
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should have a pretty string representation' do
|
149
|
+
Unit(7, "joule").normalize.to_s.should == '7000 g·m^2·s^-2'
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should parse units' do
|
153
|
+
Unit(1, 'KiB s^-1').unit.should == [[:kibi, :byte, 1], [:one, :second, -1]].sort
|
154
|
+
Unit(1, 'KiB/s').unit.should == [[:kibi, :byte, 1], [:one, :second, -1]].sort
|
155
|
+
Unit(1, 'kilometer^2 / megaelectronvolt^7 * gram centiliter').unit.should == [[:kilo, :meter, 2], [:mega, :electronvolt, -7],
|
156
|
+
[:one, :gram, 1], [:centi, :liter, 1]].sort
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'should reduce units' do
|
160
|
+
Unit(1, "joule/kilogram").normalize.unit.should == [[:one, :meter, 2], [:one, :second, -2]].sort
|
161
|
+
Unit(1, "megaton/kilometer").unit.should == [[:kilo, :ton, 1], [:one, :meter, -1]].reverse
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should work with floating point values' do
|
165
|
+
w = 5.2 * Unit('kilogram')
|
166
|
+
w.in("pounds").to_int.should == 11
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should have dimensionless? method' do
|
170
|
+
Unit(100, "m/km").should be_dimensionless
|
171
|
+
Unit(42, "meter/second").should_not be_unitless
|
172
|
+
Unit(100, "meter/km").should == Unit(Rational(1, 10))
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'should be equal to rational if dimensionless' do
|
176
|
+
Unit(100, "meter/km").should == Rational(1, 10)
|
177
|
+
Unit(100, "meter/km").approx.should == 0.1
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'should be comparable' do
|
181
|
+
Unit(1, 'm').should < Unit(2, 'm')
|
182
|
+
Unit(1, 'm').should <= Unit(2, 'm')
|
183
|
+
|
184
|
+
Unit(1, 'm').should <= Unit(1, 'm')
|
185
|
+
Unit(1, 'm').should >= Unit(1, 'm')
|
186
|
+
|
187
|
+
Unit(1, 'm').should > Unit(0, 'm')
|
188
|
+
Unit(1, 'm').should >= Unit(0, 'm')
|
189
|
+
|
190
|
+
Unit(100, "m").should < Unit(1, "km")
|
191
|
+
Unit(100, "m").should > Unit(0.0001, "km")
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should fail comparison on differing units" do
|
195
|
+
expect { Unit(1, "second") > Unit(1, "meter") }.to raise_error(Unit::IncompatibleUnitError)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should keep units when the value is zero" do
|
199
|
+
Unit(0, "m").unit.should == [[:one, :meter, 1]]
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should support absolute value" do
|
203
|
+
Unit(1, "m").abs.should == Unit(1, "m")
|
204
|
+
Unit(-1, "m").abs.should == Unit(1, "m")
|
205
|
+
end
|
206
|
+
|
207
|
+
it "should have #zero?" do
|
208
|
+
Unit(0, "m").zero?.should == true
|
209
|
+
Unit(1, "m").zero?.should == false
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should produce an approximation" do
|
213
|
+
Unit(Rational(1,3), "m").approx.should == Unit(1.0/3.0, "m")
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
describe "Unit DSL", :dsl => true do
|
219
|
+
it 'should provide method sugar' do
|
220
|
+
1.meter.should == Unit('1 meter')
|
221
|
+
1.meter_per_second.should == Unit('1 m/s')
|
222
|
+
1.meter.in_kilometer.should == Unit('1 m').in('km')
|
223
|
+
1.unit('°C').should == Unit(1, '°C')
|
224
|
+
end
|
225
|
+
end
|
File without changes
|
data/{test → spec}/yml/io.yml
RENAMED
File without changes
|
data/unit.gemspec
CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.version = Unit::VERSION
|
7
7
|
|
8
8
|
s.authors = ["Daniel Mendler"]
|
9
|
-
s.date
|
9
|
+
s.date = Date.today.to_s
|
10
10
|
s.email = ["mail@daniel-mendler.de"]
|
11
11
|
|
12
12
|
s.files = `git ls-files`.split("\n")
|
@@ -19,5 +19,5 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.homepage = %q{http://github.com/minad/unit}
|
20
20
|
|
21
21
|
s.add_development_dependency('rake', ['>= 0.8.7'])
|
22
|
-
s.add_development_dependency('
|
22
|
+
s.add_development_dependency('rspec')
|
23
23
|
end
|
metadata
CHANGED
@@ -1,50 +1,46 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: unit
|
3
|
-
version: !ruby/object:Gem::Version
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
4
5
|
prerelease:
|
5
|
-
version: 0.3.0
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Daniel Mendler
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
dependencies:
|
16
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-01-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
17
15
|
name: rake
|
18
|
-
|
19
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: &7079200 !ruby/object:Gem::Requirement
|
20
17
|
none: false
|
21
|
-
requirements:
|
22
|
-
- -
|
23
|
-
- !ruby/object:Gem::Version
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
24
21
|
version: 0.8.7
|
25
22
|
type: :development
|
26
|
-
version_requirements: *id001
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: bacon
|
29
23
|
prerelease: false
|
30
|
-
|
24
|
+
version_requirements: *7079200
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &7078560 !ruby/object:Gem::Requirement
|
31
28
|
none: false
|
32
|
-
requirements:
|
33
|
-
- -
|
34
|
-
- !ruby/object:Gem::Version
|
35
|
-
version:
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
36
33
|
type: :development
|
37
|
-
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *7078560
|
38
36
|
description:
|
39
|
-
email:
|
37
|
+
email:
|
40
38
|
- mail@daniel-mendler.de
|
41
39
|
executables: []
|
42
|
-
|
43
40
|
extensions: []
|
44
|
-
|
45
41
|
extra_rdoc_files: []
|
46
|
-
|
47
|
-
|
42
|
+
files:
|
43
|
+
- .gitignore
|
48
44
|
- .travis.yml
|
49
45
|
- Gemfile
|
50
46
|
- LICENSE
|
@@ -64,39 +60,36 @@ files:
|
|
64
60
|
- lib/unit/systems/si.yml
|
65
61
|
- lib/unit/systems/time.yml
|
66
62
|
- lib/unit/version.rb
|
67
|
-
-
|
68
|
-
-
|
69
|
-
-
|
70
|
-
-
|
71
|
-
-
|
63
|
+
- spec/error_spec.rb
|
64
|
+
- spec/spec_helper.rb
|
65
|
+
- spec/support/unit_one.rb
|
66
|
+
- spec/system_spec.rb
|
67
|
+
- spec/unit_spec.rb
|
68
|
+
- spec/yml/filename.yml
|
69
|
+
- spec/yml/io.yml
|
72
70
|
- unit.gemspec
|
73
|
-
has_rdoc: true
|
74
71
|
homepage: http://github.com/minad/unit
|
75
72
|
licenses: []
|
76
|
-
|
77
73
|
post_install_message:
|
78
74
|
rdoc_options: []
|
79
|
-
|
80
|
-
require_paths:
|
75
|
+
require_paths:
|
81
76
|
- lib
|
82
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
78
|
none: false
|
84
|
-
requirements:
|
85
|
-
- -
|
86
|
-
- !ruby/object:Gem::Version
|
87
|
-
version:
|
88
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
84
|
none: false
|
90
|
-
requirements:
|
91
|
-
- -
|
92
|
-
- !ruby/object:Gem::Version
|
93
|
-
version:
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
94
89
|
requirements: []
|
95
|
-
|
96
90
|
rubyforge_project: unit
|
97
|
-
rubygems_version: 1.
|
91
|
+
rubygems_version: 1.8.11
|
98
92
|
signing_key:
|
99
93
|
specification_version: 3
|
100
94
|
summary: Scientific unit support for ruby for calculations
|
101
95
|
test_files: []
|
102
|
-
|
data/test/error_test.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
require 'bacon'
|
3
|
-
require 'unit'
|
4
|
-
require 'unit/dsl'
|
5
|
-
|
6
|
-
describe "Errors" do
|
7
|
-
describe "TypeError when adding incompatible units" do
|
8
|
-
it "should have a nice error message" do
|
9
|
-
unit_1 = Unit(1, "meter")
|
10
|
-
unit_2 = Unit(1, "second")
|
11
|
-
lambda {
|
12
|
-
unit_1 + unit_2
|
13
|
-
}.should.raise(TypeError).message.should.equal("Incompatible units: #{unit_1.inspect} and #{unit_2.inspect}")
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
data/test/unit_test.rb
DELETED
@@ -1,144 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
require 'bacon'
|
3
|
-
require 'unit'
|
4
|
-
require 'unit/dsl'
|
5
|
-
|
6
|
-
Unit.default_system.load(:scientific)
|
7
|
-
Unit.default_system.load(:imperial)
|
8
|
-
Unit.default_system.load(:misc)
|
9
|
-
|
10
|
-
describe 'Unit' do
|
11
|
-
it 'should support multiplication' do
|
12
|
-
(Unit(2, 'm') * Unit(3, 'm')).should.equal Unit(6, 'm^2')
|
13
|
-
(Unit(2, 'm') * 3).should.equal Unit(6, 'm')
|
14
|
-
(Unit(2, 'm') * Rational(3, 4)).should.equal Unit(3, 2, 'm')
|
15
|
-
(Unit(2, 'm') * 0.5).should.equal Unit(1.0, 'm')
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'should support division' do
|
19
|
-
(Unit(2, 'm') / Unit(3, 'm^2')).should.equal Unit(2, 3, '1/m')
|
20
|
-
(Unit(2, 'm') / 3).should.equal Unit(2, 3, 'm')
|
21
|
-
(Unit(2, 'm') / Rational(3, 4)).should.equal Unit(8, 3, 'm')
|
22
|
-
(Unit(2, 'm') / 0.5).should.equal Unit(4.0, 'm')
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'should support addition' do
|
26
|
-
(Unit(42, 'm') + Unit(1, 'km')).should.equal Unit(1042, 'm')
|
27
|
-
(Unit(1, 'm') - Unit(1, 'cm')).should.equal Unit(99, 100, 'm')
|
28
|
-
end
|
29
|
-
|
30
|
-
it "should support adding through zero" do
|
31
|
-
(Unit(0, "m") + Unit(1, "m")).should.equal Unit(1, "m")
|
32
|
-
(Unit(1, "m") + Unit(-1, "m") + Unit(1, "m")).should.equal Unit(1, "m")
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'should check unit compatiblity' do
|
36
|
-
should.raise TypeError do
|
37
|
-
(Unit(42, 'm') + Unit(1, 's'))
|
38
|
-
end
|
39
|
-
should.raise TypeError do
|
40
|
-
(Unit(42, 'g') + Unit(1, 'm'))
|
41
|
-
end
|
42
|
-
should.raise TypeError do
|
43
|
-
(Unit(0, 'g') + Unit(1, 'm'))
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
it 'should support exponentiation' do
|
48
|
-
(Unit(2, 'm') ** 3).should.equal Unit(8, 'm^3')
|
49
|
-
(Unit(9, 'm^2') ** 0.5).should.equal Unit(3.0, 'm')
|
50
|
-
(Unit(9, 'm^2') ** Rational(1, 2)).should.equal Unit(3, 'm')
|
51
|
-
(Unit(2, 'm') ** 1.3).should.equal Unit(2 ** 1.3, 'm^1.3')
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'should not allow units as exponent' do
|
55
|
-
should.raise TypeError do
|
56
|
-
Unit(42, 'g') ** Unit(1, 'm')
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
it 'should provide method sugar' do
|
61
|
-
1.meter.should.equal Unit('1 meter')
|
62
|
-
1.meter_per_second.should.equal Unit('1 m/s')
|
63
|
-
1.meter.in_kilometer.should.equal Unit('1 m').in('km')
|
64
|
-
1.unit('°C').should.equal Unit(1, '°C')
|
65
|
-
end
|
66
|
-
|
67
|
-
it 'should have a normalizer' do
|
68
|
-
1.joule.normalize.should.equal Unit(1000, 'gram meter^2 / second^2')
|
69
|
-
unit = 1.joule.normalize!
|
70
|
-
unit.should.equal unit.normalized
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'should convert units' do
|
74
|
-
1.MeV.in_joule.should.equal Unit(1.602176487e-13, 'joule')
|
75
|
-
1.kilometer.in_meter.should.equal Unit(1000, 'meter')
|
76
|
-
1.liter.in('meter^3').should.equal Unit(1, 1000, 'meter^3')
|
77
|
-
1.kilometer_per_hour.in_meter_per_second.should.equal Unit(5, 18, 'meter/second')
|
78
|
-
end
|
79
|
-
|
80
|
-
it 'should have a working compatible? method' do
|
81
|
-
7.meter.compatible?('kilogram').should.equal false
|
82
|
-
3.parsec.compatible_with?('meter').should.equal true
|
83
|
-
end
|
84
|
-
|
85
|
-
it 'should have a pretty string representation' do
|
86
|
-
7.joule.normalize.to_s.should.equal '7000 g·m^2·s^-2'
|
87
|
-
end
|
88
|
-
|
89
|
-
it 'should parse units' do
|
90
|
-
Unit(1, 'KiB s^-1').unit.should.equal [[:kibi, :byte, 1], [:one, :second, -1]].sort
|
91
|
-
Unit(1, 'KiB/s').unit.should.equal [[:kibi, :byte, 1], [:one, :second, -1]].sort
|
92
|
-
Unit(1, 'kilometer^2 / megaelectronvolt^7 * gram centiliter').unit.should.equal [[:kilo, :meter, 2], [:mega, :electronvolt, -7],
|
93
|
-
[:one, :gram, 1], [:centi, :liter, 1]].sort
|
94
|
-
end
|
95
|
-
|
96
|
-
it 'should reduce units' do
|
97
|
-
1.joule_per_kilogram.normalize.unit.should.equal [[:one, :meter, 2], [:one, :second, -2]].sort
|
98
|
-
1.megaton_per_kilometer.unit.should.equal [[:kilo, :ton, 1], [:one, :meter, -1]].sort
|
99
|
-
end
|
100
|
-
|
101
|
-
it 'should work with floating point values' do
|
102
|
-
#w = (5.2).kilogram
|
103
|
-
w = 5.2 * Unit('kilogram')
|
104
|
-
w.in_pounds.to_int.should.equal 11
|
105
|
-
end
|
106
|
-
|
107
|
-
it 'should have dimensionless? method' do
|
108
|
-
100.meter_per_km.should.be.dimensionless
|
109
|
-
100.meter.per_km.should.be.dimensionless
|
110
|
-
100.meter.per_km.should.be.unitless
|
111
|
-
42.meter.per_second.should.not.be.unitless
|
112
|
-
100.meter.per_km.should.equal Unit(Rational(1, 10))
|
113
|
-
end
|
114
|
-
|
115
|
-
it 'should be equal to rational if dimensionless' do
|
116
|
-
100.meter.per_km.should.equal Rational(1, 10)
|
117
|
-
100.meter.per_km.approx.should.equal 0.1
|
118
|
-
end
|
119
|
-
|
120
|
-
it 'should be comparable' do
|
121
|
-
Unit(1,'m').should < Unit(2,'m')
|
122
|
-
Unit(1,'m').should <= Unit(2,'m')
|
123
|
-
|
124
|
-
Unit(1,'m').should <= Unit(1,'m')
|
125
|
-
Unit(1,'m').should >= Unit(1,'m')
|
126
|
-
|
127
|
-
Unit(1,'m').should > Unit(0,'m')
|
128
|
-
Unit(1,'m').should >= Unit(0,'m')
|
129
|
-
|
130
|
-
Unit(100, "m").should < Unit(1, "km")
|
131
|
-
Unit(100, "m").should > Unit(0.0001, "km")
|
132
|
-
end
|
133
|
-
|
134
|
-
it "should fail comparison on differing units" do
|
135
|
-
lambda do
|
136
|
-
Unit(1, "second") > Unit(1, "meter")
|
137
|
-
end.should.raise(ArgumentError)
|
138
|
-
end
|
139
|
-
|
140
|
-
it "should keep units when the value is zero" do
|
141
|
-
Unit(0, "m").unit.should.equal [[:one, :meter, 1]]
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|