dimensional 0.0.6 → 0.1.1

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.
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