dimensional 0.0.6 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/Rakefile +1 -1
- data/lib/dimensional.rb +1 -1
- data/lib/dimensional/configuration.rb +90 -17
- data/lib/dimensional/configurator.rb +7 -34
- data/lib/dimensional/dimension.rb +24 -8
- data/lib/dimensional/metric.rb +115 -41
- data/lib/dimensional/system.rb +3 -2
- data/lib/dimensional/unit.rb +55 -17
- data/lib/dimensional/version.rb +1 -1
- data/test/configuration_test.rb +217 -0
- data/test/configurator_test.rb +25 -30
- data/test/demo.rb +240 -51
- data/test/dimension_test.rb +28 -3
- data/test/dimensional_test.rb +47 -2
- data/test/metric_test.rb +250 -78
- data/test/unit_test.rb +83 -19
- metadata +4 -5
- data/lib/dimensional/measure.rb +0 -122
- data/test/measure_test.rb +0 -226
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.
|
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:
|
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
|
data/lib/dimensional/measure.rb
DELETED
@@ -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
|