dimensional 0.0.6 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dimensional
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Hapgood
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-09 00:00:00 -04:00
12
+ date: 2010-02-05 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -25,17 +25,16 @@ files:
25
25
  - lib/dimensional/configuration.rb
26
26
  - lib/dimensional/configurator.rb
27
27
  - lib/dimensional/dimension.rb
28
- - lib/dimensional/measure.rb
29
28
  - lib/dimensional/metric.rb
30
29
  - lib/dimensional/system.rb
31
30
  - lib/dimensional/unit.rb
32
31
  - lib/dimensional/version.rb
33
32
  - lib/dimensional.rb
33
+ - test/configuration_test.rb
34
34
  - test/configurator_test.rb
35
35
  - test/demo.rb
36
36
  - test/dimension_test.rb
37
37
  - test/dimensional_test.rb
38
- - test/measure_test.rb
39
38
  - test/metric_test.rb
40
39
  - test/system_test.rb
41
40
  - test/unit_test.rb
@@ -72,11 +71,11 @@ signing_key:
72
71
  specification_version: 3
73
72
  summary: Dimensional provides handling for numbers with units.
74
73
  test_files:
74
+ - test/configuration_test.rb
75
75
  - test/configurator_test.rb
76
76
  - test/demo.rb
77
77
  - test/dimension_test.rb
78
78
  - test/dimensional_test.rb
79
- - test/measure_test.rb
80
79
  - test/metric_test.rb
81
80
  - test/system_test.rb
82
81
  - test/unit_test.rb
@@ -1,122 +0,0 @@
1
- require 'dimensional/unit'
2
- require 'dimensional/metric'
3
- require 'delegate'
4
-
5
- module Dimensional
6
- # A numeric-like class used to represent the measure of a physical quantity. An instance of this
7
- # class represents both the unit and the numerical value of a measure. In turn, the (scale)
8
- # unit implies a dimension of the measure. Instances of this class are immutable (value objects)
9
- # Reference: http://en.wikipedia.org/wiki/Physical_quantity
10
- class Measure < DelegateClass(Numeric)
11
- # A Measure string is composed of a number followed by a unit separated by optional whitespace.
12
- # A unit (optional) is composed of a non-digit character followed by zero or more word characters and terminated by some stuff.
13
- # Scientific notation is not currently supported.
14
- NUMERIC_REGEXP = /((?=\d|\.\d)\d*(?:\.\d*)?)\s*(\D\w*?)?(?=\b|\d|\W|$)/
15
-
16
- # Parse a string into a Measure instance. The metric and system parameters may be keys for looking up the associated values.
17
- # Unrecognized strings return nil.
18
- def self.parse(str, metric, system = nil)
19
- metric = Metric[metric] unless metric.kind_of?(Metric)
20
- system = System[system] unless system.kind_of?(System)
21
- raise "Metric not specified" unless metric
22
- units = metric.units
23
- elements = str.to_s.scan(NUMERIC_REGEXP).map do |(v, us)|
24
- units = units.select{|u| system == u.system} if system
25
- unit = us.nil? ? units.first : units.detect{|u| metric.preferences(u)[:detector].match(us)}
26
- raise ArgumentError, "Unit cannot be determined (#{us})" unless unit
27
- system = unit.system
28
- value = unit.dimension.nil? ? v.to_i : v.to_f
29
- new(value, unit, metric)
30
- end
31
- # Coalesce the elements into a single Measure instance in "expression base" units.
32
- # The expression base is the first provided unit in an expression like "1 mile 200 feet"
33
- elements.inject do |t, e|
34
- converted_value = e.convert(t.unit)
35
- new(t + converted_value, t.unit, metric)
36
- end
37
- end
38
-
39
- attr_reader :unit, :metric
40
-
41
- def initialize(value, unit, metric = nil)
42
- @unit = unit
43
- metric = Metric[metric] if metric.kind_of?(Symbol)
44
- @metric = metric || Metric[unit.dimension]
45
- super(value)
46
- end
47
-
48
- # Convert this dimensional value to a different unit
49
- def convert(new_unit)
50
- new_value = self * unit.convert(new_unit)
51
- self.class.new(new_value, new_unit, metric)
52
- end
53
-
54
- # Convert this measure to the most appropriate unit in the given system
55
- # A heuristic approach is used that considers the resulting measure's order-of-magnitude (similar
56
- # is good) and membership in the preferred units of the measure's metric (membership is good).
57
- def change_system(system, fallback = false)
58
- system = System[system] unless system.kind_of?(System)
59
- units = metric.units.select{|u| system == u.system}
60
- if units.empty?
61
- if fallback
62
- units = metric.units
63
- else
64
- raise "No suitable units available in #{system}"
65
- end
66
- end
67
- target_oom = Math.log10(self.unit.factor)
68
- units = units.sort_by do |u|
69
- oom_delta = (Math.log10(u.factor) - target_oom).abs # == Math.log10(self.unit.factor / u.factor)
70
- magnitude_fit = Math.exp(-0.20 * oom_delta) # decay function
71
- 0.75 * magnitude_fit + 0.25 * metric.preference(u)
72
- end
73
- u = units.last
74
- convert(u)
75
- end
76
-
77
- # Return a new dimensional value expressed in the base unit
78
- # DEPRECATE: this method has dubious semantics for composed units as there may be no defined unit with
79
- # a matching dimension vector.
80
- def base
81
- raise "Composed units cannot be converted to a base unit" if unit.reference_unit.kind_of?(Enumerable)
82
- convert(unit.base)
83
- end
84
-
85
- def to_s
86
- strfmeasure(metric.preferences(unit)[:format] || "%s%U")
87
- end
88
-
89
- # Like Date, Time and DateTime, Measure represents both a value and a context. Like those built-in classes,
90
- # Measure needs this output method to control the context. The format string is identical to that used by
91
- # Kernel.sprintf with the addition of support for the U specifier:
92
- # %U replace with unit. This specifier supports the '#' flag to use the unit's name instead of abbreviation
93
- # In addition, this specifier supports the same width and precision modfiers as the '%s' specifier.
94
- # For example: %#10.10U
95
- # All other specifiers are applied to the numeric value of the measure.
96
- # TODO: Support modulo subordinate units with format hash -> {1 => "'", 12 => :inch} or {1 => "%d#", 16 => "%doz."}
97
- def strfmeasure(format)
98
- # We need the native value to prevent infinite recursion if the user specifies the %s specifier.
99
- v = if precision = metric.preferences(unit)[:precision]
100
- pfactor = 10**(-precision)
101
- ((self * pfactor).round / pfactor.to_f).to_s
102
- else
103
- native
104
- end
105
- format = format.gsub(/%(#)?([\d.\-\*]*)U/) do |s|
106
- us = ($1) ? unit.name : (unit.abbreviation || unit.name)
107
- Kernel.sprintf("%#{$2}s", us)
108
- end
109
- count = format.scan(/(?:\A|[^%])(%[^% ]*[A-Za-z])/).size
110
- Kernel.sprintf(format, *Array.new(count, v))
111
- end
112
-
113
- def inspect
114
- strfmeasure("<%p <%#U>>")
115
- end
116
-
117
- private
118
- def native
119
- metric.dimension ? to_f : to_i
120
- end
121
- end
122
- end
data/test/measure_test.rb DELETED
@@ -1,226 +0,0 @@
1
- require 'test/unit'
2
- require 'dimensional/measure'
3
- require 'dimensional/configurator'
4
- require 'rational'
5
-
6
- class MeasureTest < Test::Unit::TestCase
7
- include Dimensional
8
-
9
- def setup
10
- System.register('International System of Units', 'SI')
11
- System.register('United States Customary Units', 'US')
12
- System.register('British Admiralty', 'BA')
13
-
14
- Dimension.register('Length')
15
- Dimension.register('Mass')
16
-
17
- Metric.register('length', :L)
18
-
19
- Configurator.start do
20
- dimension(:L) do
21
- system(:SI) do
22
- base('meter', 'm', :detector => /\A(meters?|m)\Z/) do
23
- prefer(:length_over_all, :precision => 0.01)
24
- derive('centimeter', 'cm', 1e-2, :detector => /\A(centimeters?|cm)\Z/)
25
- derive('kilometer', 'km', 1e3, :detector => /\A(kilometers?|km)\Z/)
26
- end
27
- end
28
- system(:US) do # As of 1 July 1959 (http://en.wikipedia.org/wiki/United_States_customary_units#Units_of_length)
29
- reference('yard', 'yd', Unit[:L, :SI, 'meter'], 0.9144, :detector => /\A(yards?|yds?)\Z/) do
30
- derive('foot', 'ft', Rational(1,3), :detector => /\A(foot|feet|ft|')\Z/, :format => "%p'") do
31
- prefer(:length_over_all, :precision => Rational(1, 12))
32
- derive('inch', 'in', Rational(1,12), :detector => /\A(inch|inches|in|")\Z/, :format => "%p\"")
33
- end
34
- derive('furlong', nil, 220, :detector => /\A(furlongs?)\Z/) do
35
- derive('mile', 'mi', 8, :detector => /\Amiles?\Z/)
36
- end
37
- end
38
- end
39
- system(:BA) do
40
- base('mile', 'nm', :detector => /\A(miles?|nm|nmi)\Z/) do
41
- prefer(:distance, :precision => -2)
42
- derive('cable', nil, Rational(1,10), :detector => /\A(cables?|cbls?)\Z/) do
43
- derive('fathom', 'fm', Rational(1,10), :detector => /\A(fathoms?|fms?)\Z/) do
44
- derive('yard', 'yd', Rational(1,6), :detector => /\A(yards?|yds?)\Z/) do
45
- derive('foot', 'ft', Rational(1,3), :detector => /\A(foot|feet|ft|')\Z/) do
46
- prefer(:length_over_all, :precision => Rational(1, 12))
47
- derive('inch', 'in', Rational(1,12), :detector => /\A(inch|inches|in|")\Z/)
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
55
- dimension(:M) do
56
- system(:SI) do
57
- base('kilogram', 'kg', :detector => /\A(kilograms?|kg)\Z/) do
58
- derive('tonne', 't', 1000, :detector => /\A(tonnes?)\Z/) # metric ton
59
- derive('gram', 'g', Rational(1, 1000), :detector => /\A(grams?|g)\Z/)
60
- end
61
- end
62
- end
63
- # Dimensionless Units
64
- base('each', 'ea') do
65
- derive('dozen', 'dz', 12)
66
- end
67
- end
68
- end
69
-
70
- def teardown
71
- Dimension.reset!
72
- System.reset!
73
- Unit.reset!
74
- Metric.reset!
75
- end
76
-
77
- def test_create_new_measure
78
- u = Unit[:L, :BA, 'mile']
79
- assert m = Measure.new(3000, u, :length)
80
- assert_equal 3000, m
81
- assert_equal u, m.unit
82
- assert_same Metric[:length], m.metric
83
- end
84
-
85
- def test_to_f
86
- d = Measure.parse("1.85m", :L, :SI)
87
- assert_instance_of Float, d.to_f
88
- end
89
-
90
- def test_to_i
91
- d = Measure.parse("1 each", Metric[nil])
92
- assert_instance_of Fixnum, d.to_i
93
- end
94
-
95
- def test_convert
96
- old_unit = Unit[:L, :BA, 'cable']
97
- new_unit = Unit[:L, :BA, 'fathom']
98
- new = Measure.new(1, old_unit).convert(new_unit)
99
- assert_in_delta(10, new, 0.000001)
100
- assert_same new_unit, new.unit
101
- end
102
-
103
- def test_do_identity_conversion
104
- old_unit = Unit[:L, :BA, 'cable']
105
- new_unit = old_unit
106
- old_value = Measure.new(12, old_unit)
107
- new_value = old_value.convert(new_unit)
108
- assert_equal old_value, new_value
109
- end
110
-
111
- # These system-conversion tests rely on very specific constants in the heuristics of #change_system
112
- def test_change_system_yd
113
- u0 = Unit[:L, :US, 'yd']
114
- m0 = Measure.new(1, u0, :length_over_all)
115
- u1 = Unit[:L, :SI, 'm']
116
- assert m1 = m0.change_system(:SI)
117
- assert_same u1, m1.unit
118
- end
119
-
120
- def test_change_system_with_metric_override
121
- u0 = Unit[:L, :US, 'in']
122
- m0 = Measure.new(1, u0, :length_over_all)
123
- u1 = Unit[:L, :SI, 'm']
124
- assert m1 = m0.change_system(:SI)
125
- assert_same u1, m1.unit
126
- end
127
-
128
- def test_change_system_with_oom_dominance
129
- u0 = Unit[:L, :US, 'in']
130
- m0 = Measure.new(1, u0, :L)
131
- u1 = Unit[:L, :SI, 'cm']
132
- assert m1 = m0.change_system(:SI)
133
- assert_same u1, m1.unit
134
- end
135
-
136
- def test_change_system_ft
137
- u0 = Unit[:L, :US, 'ft']
138
- m0 = Measure.new(1, u0, :length_over_all)
139
- u1 = Unit[:L, :SI, 'm']
140
- assert m1 = m0.change_system(:SI)
141
- assert_same u1, m1.unit
142
- end
143
-
144
- def test_change_system_mile
145
- u0 = Unit[:L, :US, 'mile']
146
- m0 = Measure.new(1, u0, :length_over_all)
147
- u1 = Unit[:L, :SI, 'km']
148
- assert m1 = m0.change_system(:SI)
149
- assert_same u1, m1.unit
150
- end
151
-
152
- def test_return_base
153
- u = Unit[:L, :BA, 'fathom']
154
- b = Measure.new(1, u).base
155
- assert_in_delta(1e-2, b, 0.000001)
156
- assert_same u.base, b.unit
157
- end
158
-
159
- def test_parse
160
- assert m = Measure.parse("15'", :L, :BA)
161
- assert_same Unit[:L, :BA, 'foot'], m.unit
162
- assert_equal 15, m
163
- end
164
-
165
- def test_parse_with_whitespace
166
- m = Measure.parse("15 feet", :L, :BA)
167
- assert_same Unit[:L, :BA, 'foot'], m.unit
168
- assert_equal 15, m
169
- end
170
-
171
- def test_parse_compound
172
- d = Measure.parse("15'11\"", :L, :US)
173
- assert_in_delta(15 + Rational(11, 12), d, 0.000001)
174
- end
175
-
176
- def test_parse_compound_with_whitespace
177
- d = Measure.parse("1 foot 11 inches", :L, :US)
178
- assert_same d.unit, Unit[:L, :US, 'foot']
179
- assert_in_delta(1 + Rational(11, 12).to_f, d, 0.000001)
180
- end
181
-
182
- def test_raise_on_parse_of_mixed_compound
183
- assert_raises ArgumentError do
184
- Measure.parse("1 foot 11cm", :L)
185
- end
186
- end
187
-
188
- def test_parse_with_default_unit
189
- metric = Metric[:L]
190
- du = metric.units.first
191
- assert_instance_of Measure, m = Measure.parse("10", :L)
192
- assert_equal du, m.unit
193
- end
194
-
195
- def test_parse_dimensionless_units
196
- assert m = Measure.parse('2 dozen', nil)
197
- assert_instance_of Measure, m
198
- assert_equal 2, m
199
- assert_equal Unit[nil, nil, 'dozen'], m.unit
200
- assert_equal 12, m.unit.factor
201
- assert_nil m.unit.dimension
202
- end
203
-
204
- def test_stringify_with_abbreviation
205
- assert_equal "1.85nm", Measure.parse('1.85 miles', :L, :BA).to_s
206
- end
207
-
208
- def test_parse_gibberish_as_nil
209
- assert_nil Measure.parse("gibberish", :L)
210
- end
211
-
212
- def test_format_output
213
- m = Measure.parse("15'3\"", :L, :BA)
214
- assert_equal "15.25 (ft)", m.strfmeasure("%4.2f (%U)")
215
- end
216
-
217
- def test_format_output_with_multiple_substitutions
218
- m = Measure.parse("15'4\"", :L, :BA)
219
- assert_equal "15.33 (ft)\t%\t<15.3333333ft>", m.strfmeasure("%4.2f (%U)\t%%\t<%.10s%U>")
220
- end
221
-
222
- def test_precision_recognition
223
- assert_equal "1.8600nm", Measure.parse('1.8565454 miles', :distance, :BA).strfmeasure("%.4f%U")
224
- assert_equal "1.86", Measure.parse('1.8565454 miles', :distance, :BA).strfmeasure("%s")
225
- end
226
- end