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.
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/Rakefile CHANGED
@@ -1,6 +1,18 @@
1
- task :default => :test
2
1
 
3
- desc 'Run tests with bacon'
4
- task :test => FileList['test/*_test.rb'] do |t|
5
- sh "bacon -q -Ilib:test #{t.prerequisites.join(' ')}"
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
@@ -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
- a, b = coerce(other)
48
- Unit.new(a.value * b.value, a.unit + b.unit, system)
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
- a, b = coerce(other)
53
- Unit.new(Integer === a.value && Integer === b.value ? Rational(a.value, b.value) : a.value / b.value,
54
- a.unit + Unit.power_unit(b.unit, -1), system)
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
- raise TypeError, "Incompatible units: #{self.inspect} and #{other.inspect}" if !compatible?(other)
59
- a, b = coerce(other)
60
- a, b = a.normalize, b.normalize
61
- Unit.new(a.value + b.value, a.unit, system).in(self)
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
- self + (-other)
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
- a, b = coerce(other)
79
- a, b = a.normalize, b.normalize
80
- a.value == b.value && a.unit == b.unit
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
- a, b = coerce(other)
85
- a, b = a.normalize, b.normalize
86
- a.value <=> b.value if a.unit == b.unit
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
- alias unitless? dimensionless?
134
+ alias_method :unitless?, :dimensionless?
95
135
 
96
136
  # Compatible units can be added
97
137
  def compatible?(other)
98
- a, b = coerce(other)
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
- alias compatible_with? compatible?
141
+ alias_method :compatible_with?, :compatible?
104
142
 
105
143
  # Convert to other unit
106
144
  def in(unit)
107
- a, b = coerce(unit)
108
- conversion = Unit.new(1, b.unit, system)
109
- (a / conversion).normalize * conversion
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
- to_f.unit(unit)
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
- private
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
- raise RuntimeError, 'Numeric unit with factor' if factor != :one
190
- @value *= number ** exp
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
- class<< self
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
@@ -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
- self.in(Unit.method_name_to_unit($'))
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
- Unit.to_unit(Unit.method_name_to_unit(name), system) * self
21
+ super(name, system || @system)
21
22
  end
22
23
  end
23
24
  end
@@ -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
@@ -44,7 +44,7 @@ units:
44
44
  def: newton * meter
45
45
  watt:
46
46
  sym: W
47
- def: joule * second
47
+ def: joule / second
48
48
  coloumb:
49
49
  sym: C
50
50
  def: ampere * second
@@ -1,3 +1,3 @@
1
1
  class Unit < Numeric
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -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
@@ -0,0 +1,12 @@
1
+ require 'rspec'
2
+ require 'unit'
3
+ require './spec/support/unit_one'
4
+
5
+ RSpec.configure do |config|
6
+ unless config.exclusion_filter[:dsl]
7
+ config.before(:suite) do
8
+ require "unit/dsl"
9
+ end
10
+ end
11
+ end
12
+
@@ -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 'bacon'
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.equal Unit(3.14159, "m")
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.equal Unit(24, "m")
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
@@ -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
File without changes
@@ -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 = %q{2009-05-17}
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('bacon')
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
- date: 2009-05-17 00:00:00 +02:00
14
- default_executable:
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
- prerelease: false
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
- requirement: &id002 !ruby/object:Gem::Requirement
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: "0"
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
36
33
  type: :development
37
- version_requirements: *id002
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
- files:
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
- - test/error_test.rb
68
- - test/system_test.rb
69
- - test/unit_test.rb
70
- - test/yml/filename.yml
71
- - test/yml/io.yml
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: "0"
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: "0"
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
94
89
  requirements: []
95
-
96
90
  rubyforge_project: unit
97
- rubygems_version: 1.6.2
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
-
@@ -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
@@ -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
-