ruby-units 2.0.1 → 2.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.txt +3 -0
- data/README.md +48 -1
- data/{Rakefile.rb → Rakefile} +0 -0
- data/VERSION +1 -1
- data/lib/ruby_units/cache.rb +17 -16
- data/lib/ruby_units/configuration.rb +41 -0
- data/lib/ruby_units/date.rb +3 -3
- data/lib/ruby_units/definition.rb +22 -24
- data/lib/ruby_units/math.rb +30 -34
- data/lib/ruby_units/namespaced.rb +1 -0
- data/lib/ruby_units/string.rb +3 -4
- data/lib/ruby_units/time.rb +9 -10
- data/lib/ruby_units/unit.rb +778 -765
- data/lib/ruby_units/unit_definitions/base.rb +42 -45
- data/lib/ruby_units/unit_definitions/prefix.rb +30 -30
- data/lib/ruby_units/unit_definitions/standard.rb +138 -135
- data/lib/ruby_units/version.rb +1 -1
- data/ruby-units.gemspec +34 -27
- metadata +34 -5
data/lib/ruby_units/unit.rb
CHANGED
@@ -25,14 +25,14 @@ module RubyUnits
|
|
25
25
|
class Unit < Numeric
|
26
26
|
VERSION = Unit::Version::STRING
|
27
27
|
@@definitions = {}
|
28
|
-
@@
|
29
|
-
@@
|
30
|
-
@@
|
31
|
-
@@
|
32
|
-
@@
|
33
|
-
@@
|
34
|
-
UNITY = '<1>'
|
35
|
-
UNITY_ARRAY = [UNITY]
|
28
|
+
@@prefix_values = {}
|
29
|
+
@@prefix_map = {}
|
30
|
+
@@unit_map = {}
|
31
|
+
@@unit_values = {}
|
32
|
+
@@unit_regex = nil
|
33
|
+
@@unit_match_regex = nil
|
34
|
+
UNITY = '<1>'.freeze
|
35
|
+
UNITY_ARRAY = [UNITY].freeze
|
36
36
|
# ideally we would like to generate this regex from the alias for a 'feet' and 'inches', but they aren't
|
37
37
|
# defined at the point in the code where we need this regex.
|
38
38
|
FEET_INCH_UNITS_REGEX = /(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inch(?:es)?)/
|
@@ -41,127 +41,133 @@ module RubyUnits
|
|
41
41
|
# defined at the point in the code where we need this regex.
|
42
42
|
LBS_OZ_UNIT_REGEX = /(?:#|lbs?|pounds?|pound-mass)+[\s,]*(\d+)\s*(?:ozs?|ounces?)/
|
43
43
|
LBS_OZ_REGEX = /(\d+)\s*#{LBS_OZ_UNIT_REGEX}/
|
44
|
+
# ideally we would like to generate this regex from the alias for a 'stone' and 'pound', but they aren't
|
45
|
+
# defined at the point in the code where we need this regex.
|
46
|
+
# also note that the plural of 'stone' is still 'stone', but we accept 'stones' anyway.
|
47
|
+
STONE_LB_UNIT_REGEX = /(?:sts?|stones?)+[\s,]*(\d+)\s*(?:#|lbs?|pounds?|pound-mass)*/
|
48
|
+
STONE_LB_REGEX = /(\d+)\s*#{STONE_LB_UNIT_REGEX}/
|
44
49
|
TIME_REGEX = /(\d+)*:(\d+)*:*(\d+)*[:,]*(\d+)*/
|
45
|
-
SCI_NUMBER =
|
46
|
-
RATIONAL_NUMBER =
|
50
|
+
SCI_NUMBER = /([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)/
|
51
|
+
RATIONAL_NUMBER = %r{\(?([+-])?(\d+[ -])?(\d+)\/(\d+)\)?}
|
47
52
|
COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/
|
48
53
|
NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/
|
49
|
-
UNIT_STRING_REGEX =
|
54
|
+
UNIT_STRING_REGEX = %r{#{SCI_NUMBER}*\s*([^\/]*)\/*(.+)*}
|
50
55
|
TOP_REGEX = /([^ \*]+)(?:\^|\*\*)([\d-]+)/
|
51
56
|
BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/
|
52
57
|
NUMBER_UNIT_REGEX = /#{SCI_NUMBER}?(.*)/
|
53
58
|
COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/
|
54
59
|
RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(.+)?/
|
55
|
-
KELVIN = ['<kelvin>']
|
56
|
-
FAHRENHEIT = ['<fahrenheit>']
|
57
|
-
RANKINE = ['<rankine>']
|
58
|
-
CELSIUS = ['<celsius>']
|
59
|
-
@@
|
60
|
-
SIGNATURE_VECTOR =
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
@@
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
}
|
60
|
+
KELVIN = ['<kelvin>'].freeze
|
61
|
+
FAHRENHEIT = ['<fahrenheit>'].freeze
|
62
|
+
RANKINE = ['<rankine>'].freeze
|
63
|
+
CELSIUS = ['<celsius>'].freeze
|
64
|
+
@@temp_regex = nil
|
65
|
+
SIGNATURE_VECTOR = %i(
|
66
|
+
length
|
67
|
+
time
|
68
|
+
temperature
|
69
|
+
mass
|
70
|
+
current
|
71
|
+
substance
|
72
|
+
luminosity
|
73
|
+
currency
|
74
|
+
information
|
75
|
+
angle
|
76
|
+
).freeze
|
77
|
+
@@kinds = {
|
78
|
+
-312_078 => :elastance,
|
79
|
+
-312_058 => :resistance,
|
80
|
+
-312_038 => :inductance,
|
81
|
+
-152_040 => :magnetism,
|
82
|
+
-152_038 => :magnetism,
|
83
|
+
-152_058 => :potential,
|
84
|
+
-7997 => :specific_volume,
|
85
|
+
-79 => :snap,
|
86
|
+
-59 => :jolt,
|
87
|
+
-39 => :acceleration,
|
88
|
+
-38 => :radiation,
|
89
|
+
-20 => :frequency,
|
90
|
+
-19 => :speed,
|
91
|
+
-18 => :viscosity,
|
92
|
+
-17 => :volumetric_flow,
|
93
|
+
-1 => :wavenumber,
|
94
|
+
0 => :unitless,
|
95
|
+
1 => :length,
|
96
|
+
2 => :area,
|
97
|
+
3 => :volume,
|
98
|
+
20 => :time,
|
99
|
+
400 => :temperature,
|
100
|
+
7941 => :yank,
|
101
|
+
7942 => :power,
|
102
|
+
7959 => :pressure,
|
103
|
+
7962 => :energy,
|
104
|
+
7979 => :viscosity,
|
105
|
+
7961 => :force,
|
106
|
+
7981 => :momentum,
|
107
|
+
7982 => :angular_momentum,
|
108
|
+
7997 => :density,
|
109
|
+
7998 => :area_density,
|
110
|
+
8000 => :mass,
|
111
|
+
152_020 => :radiation_exposure,
|
112
|
+
159_999 => :magnetism,
|
113
|
+
160_000 => :current,
|
114
|
+
160_020 => :charge,
|
115
|
+
312_058 => :conductance,
|
116
|
+
312_078 => :capacitance,
|
117
|
+
3_199_980 => :activity,
|
118
|
+
3_199_997 => :molar_concentration,
|
119
|
+
3_200_000 => :substance,
|
120
|
+
63_999_998 => :illuminance,
|
121
|
+
64_000_000 => :luminous_power,
|
122
|
+
1_280_000_000 => :currency,
|
123
|
+
25_600_000_000 => :information,
|
124
|
+
511_999_999_980 => :angular_velocity,
|
125
|
+
512_000_000_000 => :angle
|
126
|
+
}.freeze
|
122
127
|
@@cached_units = {}
|
123
128
|
@@base_unit_cache = {}
|
124
129
|
|
130
|
+
# Class Methods
|
131
|
+
|
125
132
|
# setup internal arrays and hashes
|
126
133
|
# @return [true]
|
127
134
|
def self.setup
|
128
|
-
|
129
|
-
@@
|
130
|
-
@@
|
131
|
-
@@
|
132
|
-
@@
|
133
|
-
@@
|
134
|
-
@@
|
135
|
-
@@
|
136
|
-
|
137
|
-
@@definitions.each do |
|
138
|
-
|
135
|
+
clear_cache
|
136
|
+
@@prefix_values = {}
|
137
|
+
@@prefix_map = {}
|
138
|
+
@@unit_values = {}
|
139
|
+
@@unit_map = {}
|
140
|
+
@@unit_regex = nil
|
141
|
+
@@unit_match_regex = nil
|
142
|
+
@@prefix_regex = nil
|
143
|
+
|
144
|
+
@@definitions.each do |_name, definition|
|
145
|
+
use_definition(definition)
|
139
146
|
end
|
140
147
|
|
141
148
|
RubyUnits::Unit.new(1)
|
142
|
-
|
149
|
+
true
|
143
150
|
end
|
144
151
|
|
145
|
-
|
146
152
|
# determine if a unit is already defined
|
147
153
|
# @param [String] unit
|
148
154
|
# @return [Boolean]
|
149
155
|
def self.defined?(unit)
|
150
|
-
|
156
|
+
definitions.values.any? { |d| d.aliases.include?(unit) }
|
151
157
|
end
|
152
158
|
|
153
159
|
# return the unit definition for a unit
|
154
160
|
# @param [String] unit
|
155
161
|
# @return [RubyUnits::Unit::Definition, nil]
|
156
|
-
def self.definition(
|
157
|
-
unit =
|
158
|
-
|
162
|
+
def self.definition(unit_name)
|
163
|
+
unit = unit_name =~ /^<.+>$/ ? unit_name : "<#{unit_name}>"
|
164
|
+
@@definitions[unit]
|
159
165
|
end
|
160
166
|
|
161
167
|
# return a list of all defined units
|
162
168
|
# @return [Array]
|
163
169
|
def self.definitions
|
164
|
-
|
170
|
+
@@definitions
|
165
171
|
end
|
166
172
|
|
167
173
|
# @param [RubyUnits::Unit::Definition|String] unit_definition
|
@@ -180,12 +186,12 @@ module RubyUnits
|
|
180
186
|
# RubyUnits::Unit.define(unit_definition)
|
181
187
|
def self.define(unit_definition, &block)
|
182
188
|
if block_given?
|
183
|
-
raise ArgumentError,
|
189
|
+
raise ArgumentError, 'When using the block form of RubyUnits::Unit.define, pass the name of the unit' unless unit_definition.instance_of?(String)
|
184
190
|
unit_definition = RubyUnits::Unit::Definition.new(unit_definition, &block)
|
185
191
|
end
|
186
192
|
RubyUnits::Unit.definitions[unit_definition.name] = unit_definition
|
187
193
|
RubyUnits::Unit.use_definition(unit_definition)
|
188
|
-
|
194
|
+
unit_definition
|
189
195
|
end
|
190
196
|
|
191
197
|
# @param [String] name Name of unit to redefine
|
@@ -195,8 +201,9 @@ module RubyUnits
|
|
195
201
|
# @return (see RubyUnits::Unit.define)
|
196
202
|
# Get the definition for a unit and allow it to be redefined
|
197
203
|
def self.redefine!(name)
|
198
|
-
|
204
|
+
raise ArgumentError, 'A block is required to redefine a unit' unless block_given?
|
199
205
|
unit_definition = definition(name)
|
206
|
+
raise(ArgumentError, "'#{name}' Unit not recognized") unless unit_definition
|
200
207
|
yield unit_definition
|
201
208
|
@@definitions.delete("<#{name}>")
|
202
209
|
define(unit_definition)
|
@@ -211,6 +218,185 @@ module RubyUnits
|
|
211
218
|
RubyUnits::Unit.setup
|
212
219
|
end
|
213
220
|
|
221
|
+
# @return [Hash]
|
222
|
+
def self.cached
|
223
|
+
@@cached_units
|
224
|
+
end
|
225
|
+
|
226
|
+
# @return [true]
|
227
|
+
def self.clear_cache
|
228
|
+
@@cached_units = {}
|
229
|
+
@@base_unit_cache = {}
|
230
|
+
RubyUnits::Unit.new(1)
|
231
|
+
true
|
232
|
+
end
|
233
|
+
|
234
|
+
# @return [Hash]
|
235
|
+
def self.base_unit_cache
|
236
|
+
@@base_unit_cache
|
237
|
+
end
|
238
|
+
|
239
|
+
# @example parse strings
|
240
|
+
# "1 minute in seconds"
|
241
|
+
# @param [String] input
|
242
|
+
# @return [Unit]
|
243
|
+
def self.parse(input)
|
244
|
+
first, second = input.scan(/(.+)\s(?:in|to|as)\s(.+)/i).first
|
245
|
+
second.nil? ? RubyUnits::Unit.new(first) : RubyUnits::Unit.new(first).convert_to(second)
|
246
|
+
end
|
247
|
+
|
248
|
+
# @param [Numeric] q quantity
|
249
|
+
# @param [Array] n numerator
|
250
|
+
# @param [Array] d denominator
|
251
|
+
# @return [Hash]
|
252
|
+
def self.eliminate_terms(q, n, d)
|
253
|
+
num = n.dup
|
254
|
+
den = d.dup
|
255
|
+
|
256
|
+
num.delete_if { |v| v == UNITY }
|
257
|
+
den.delete_if { |v| v == UNITY }
|
258
|
+
combined = Hash.new(0)
|
259
|
+
|
260
|
+
i = 0
|
261
|
+
loop do
|
262
|
+
break if i > num.size
|
263
|
+
if @@prefix_values.key? num[i]
|
264
|
+
k = [num[i], num[i + 1]]
|
265
|
+
i += 2
|
266
|
+
else
|
267
|
+
k = num[i]
|
268
|
+
i += 1
|
269
|
+
end
|
270
|
+
combined[k] += 1 unless k.nil? || k == UNITY
|
271
|
+
end
|
272
|
+
|
273
|
+
j = 0
|
274
|
+
loop do
|
275
|
+
break if j > den.size
|
276
|
+
if @@prefix_values.key? den[j]
|
277
|
+
k = [den[j], den[j + 1]]
|
278
|
+
j += 2
|
279
|
+
else
|
280
|
+
k = den[j]
|
281
|
+
j += 1
|
282
|
+
end
|
283
|
+
combined[k] -= 1 unless k.nil? || k == UNITY
|
284
|
+
end
|
285
|
+
|
286
|
+
num = []
|
287
|
+
den = []
|
288
|
+
combined.each do |key, value|
|
289
|
+
if value >= 0
|
290
|
+
value.times { num << key }
|
291
|
+
elsif value < 0
|
292
|
+
value.abs.times { den << key }
|
293
|
+
end
|
294
|
+
end
|
295
|
+
num = UNITY_ARRAY if num.empty?
|
296
|
+
den = UNITY_ARRAY if den.empty?
|
297
|
+
{ scalar: q, numerator: num.flatten.compact, denominator: den.flatten.compact }
|
298
|
+
end
|
299
|
+
|
300
|
+
# return an array of base units
|
301
|
+
# @return [Array]
|
302
|
+
def self.base_units
|
303
|
+
@@base_units ||= @@definitions.dup.delete_if { |_, defn| !defn.base? }.keys.map { |u| RubyUnits::Unit.new(u) }
|
304
|
+
end
|
305
|
+
|
306
|
+
# parse a string consisting of a number and a unit string
|
307
|
+
# NOTE: This does not properly handle units formatted like '12mg/6ml'
|
308
|
+
# @param [String] string
|
309
|
+
# @return [Array] consisting of [Numeric, "unit"]
|
310
|
+
def self.parse_into_numbers_and_units(string)
|
311
|
+
# scientific notation.... 123.234E22, -123.456e-10
|
312
|
+
sci = /[+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*/
|
313
|
+
# rational numbers.... -1/3, 1/5, 20/100, -6 1/2, -6-1/2
|
314
|
+
rational = %r{\(?[+-]?(?:\d+[ -])?\d+\/\d+\)?}
|
315
|
+
# complex numbers... -1.2+3i, +1.2-3.3i
|
316
|
+
complex = /#{sci}{2,2}i/
|
317
|
+
anynumber = /(?:(#{complex}|#{rational}|#{sci}))?\s?([^-\d\.].*)?/
|
318
|
+
|
319
|
+
num, unit = string.scan(anynumber).first
|
320
|
+
|
321
|
+
[
|
322
|
+
case num
|
323
|
+
when NilClass
|
324
|
+
1
|
325
|
+
when complex
|
326
|
+
if num.respond_to?(:to_c)
|
327
|
+
num.to_c
|
328
|
+
else
|
329
|
+
#:nocov_19:
|
330
|
+
Complex(*num.scan(/(#{sci})(#{sci})i/).flatten.map(&:to_i))
|
331
|
+
#:nocov_19:
|
332
|
+
end
|
333
|
+
when rational
|
334
|
+
# if it has whitespace, it will be of the form '6 1/2'
|
335
|
+
if num =~ RATIONAL_NUMBER
|
336
|
+
sign = $1 == '-' ? -1 : 1
|
337
|
+
n = $2.to_i
|
338
|
+
f = Rational($3.to_i, $4.to_i)
|
339
|
+
sign * (n + f)
|
340
|
+
else
|
341
|
+
Rational(*num.split('/').map(&:to_i))
|
342
|
+
end
|
343
|
+
else
|
344
|
+
num.to_f
|
345
|
+
end,
|
346
|
+
unit.to_s.strip
|
347
|
+
]
|
348
|
+
end
|
349
|
+
|
350
|
+
# return a fragment of a regex to be used for matching units or reconstruct it if hasn't been used yet.
|
351
|
+
# Unit names are reverse sorted by length so the regexp matcher will prefer longer and more specific names
|
352
|
+
# @return [String]
|
353
|
+
def self.unit_regex
|
354
|
+
@@unit_regex ||= @@unit_map.keys.sort_by { |unit_name| [unit_name.length, unit_name] }.reverse.join('|')
|
355
|
+
end
|
356
|
+
|
357
|
+
# return a regex used to match units
|
358
|
+
# @return [RegExp]
|
359
|
+
def self.unit_match_regex
|
360
|
+
@@unit_match_regex ||= /(#{RubyUnits::Unit.prefix_regex})??(#{RubyUnits::Unit.unit_regex})\b/
|
361
|
+
end
|
362
|
+
|
363
|
+
# return a regexp fragment used to match prefixes
|
364
|
+
# @return [String]
|
365
|
+
# @private
|
366
|
+
def self.prefix_regex
|
367
|
+
@@prefix_regex ||= @@prefix_map.keys.sort_by { |prefix| [prefix.length, prefix] }.reverse.join('|')
|
368
|
+
end
|
369
|
+
|
370
|
+
def self.temp_regex
|
371
|
+
@@temp_regex ||= begin
|
372
|
+
temp_units = %w(tempK tempC tempF tempR degK degC degF degR)
|
373
|
+
aliases = temp_units.map do |unit|
|
374
|
+
d = RubyUnits::Unit.definition(unit)
|
375
|
+
d && d.aliases
|
376
|
+
end.flatten.compact
|
377
|
+
regex_str = aliases.empty? ? '(?!x)x' : aliases.join('|')
|
378
|
+
Regexp.new "(?:#{regex_str})"
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# inject a definition into the internal array and set it up for use
|
383
|
+
def self.use_definition(definition)
|
384
|
+
@@unit_match_regex = nil # invalidate the unit match regex
|
385
|
+
@@temp_regex = nil # invalidate the temp regex
|
386
|
+
if definition.prefix?
|
387
|
+
@@prefix_values[definition.name] = definition.scalar
|
388
|
+
definition.aliases.each { |alias_name| @@prefix_map[alias_name] = definition.name }
|
389
|
+
@@prefix_regex = nil # invalidate the prefix regex
|
390
|
+
else
|
391
|
+
@@unit_values[definition.name] = {}
|
392
|
+
@@unit_values[definition.name][:scalar] = definition.scalar
|
393
|
+
@@unit_values[definition.name][:numerator] = definition.numerator if definition.numerator
|
394
|
+
@@unit_values[definition.name][:denominator] = definition.denominator if definition.denominator
|
395
|
+
definition.aliases.each { |alias_name| @@unit_map[alias_name] = definition.name }
|
396
|
+
@@unit_regex = nil # invalidate the unit regex
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
214
400
|
include Comparable
|
215
401
|
|
216
402
|
# @return [Numeric]
|
@@ -240,13 +426,6 @@ module RubyUnits
|
|
240
426
|
# @return [String]
|
241
427
|
attr_accessor :unit_name
|
242
428
|
|
243
|
-
# needed to make complex units play nice -- otherwise not detected as a complex_generic
|
244
|
-
# @param [Class]
|
245
|
-
# @return [Boolean]
|
246
|
-
def kind_of?(klass)
|
247
|
-
self.scalar.kind_of?(klass)
|
248
|
-
end
|
249
|
-
|
250
429
|
# Used to copy one unit to another
|
251
430
|
# @param [Unit] from Unit to copy definition from
|
252
431
|
# @return [Unit]
|
@@ -254,11 +433,11 @@ module RubyUnits
|
|
254
433
|
@scalar = from.scalar
|
255
434
|
@numerator = from.numerator
|
256
435
|
@denominator = from.denominator
|
257
|
-
@
|
436
|
+
@base = from.base?
|
258
437
|
@signature = from.signature
|
259
438
|
@base_scalar = from.base_scalar
|
260
439
|
@unit_name = from.unit_name rescue nil
|
261
|
-
|
440
|
+
self
|
262
441
|
end
|
263
442
|
|
264
443
|
# Create a new Unit object. Can be initialized using a String, a Hash, an Array, Time, DateTime
|
@@ -274,7 +453,7 @@ module RubyUnits
|
|
274
453
|
# "6'4\""" -- recognized as 6 feet + 4 inches
|
275
454
|
# "8 lbs 8 oz" -- recognized as 8 lbs + 8 ounces
|
276
455
|
# [1, 'kg']
|
277
|
-
# {:
|
456
|
+
# {scalar: 1, numerator: 'kg'}
|
278
457
|
#
|
279
458
|
# @param [Unit,String,Hash,Array,Date,Time,DateTime] options
|
280
459
|
# @return [Unit]
|
@@ -287,7 +466,7 @@ module RubyUnits
|
|
287
466
|
@unit_name = nil
|
288
467
|
@signature = nil
|
289
468
|
@output = {}
|
290
|
-
raise ArgumentError,
|
469
|
+
raise ArgumentError, 'Invalid Unit Format' if options[0].nil?
|
291
470
|
if options.size == 2
|
292
471
|
# options[0] is the scalar
|
293
472
|
# options[1] is a unit string
|
@@ -300,8 +479,8 @@ module RubyUnits
|
|
300
479
|
return
|
301
480
|
end
|
302
481
|
if options.size == 3
|
303
|
-
options[1] = options[1].join if options[1].
|
304
|
-
options[2] = options[2].join if options[2].
|
482
|
+
options[1] = options[1].join if options[1].is_a?(Array)
|
483
|
+
options[2] = options[2].join if options[2].is_a?(Array)
|
305
484
|
begin
|
306
485
|
cached = @@cached_units["#{options[1]}/#{options[2]}"] * options[0]
|
307
486
|
copy(cached)
|
@@ -312,88 +491,58 @@ module RubyUnits
|
|
312
491
|
end
|
313
492
|
|
314
493
|
case options[0]
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
494
|
+
when Unit
|
495
|
+
copy(options[0])
|
496
|
+
return
|
497
|
+
when Hash
|
498
|
+
@scalar = options[0][:scalar] || 1
|
499
|
+
@numerator = options[0][:numerator] || UNITY_ARRAY
|
500
|
+
@denominator = options[0][:denominator] || UNITY_ARRAY
|
501
|
+
@signature = options[0][:signature]
|
502
|
+
when Array
|
503
|
+
initialize(*options[0])
|
504
|
+
return
|
505
|
+
when Numeric
|
506
|
+
@scalar = options[0]
|
507
|
+
@numerator = @denominator = UNITY_ARRAY
|
508
|
+
when Time
|
509
|
+
@scalar = options[0].to_f
|
510
|
+
@numerator = ['<second>']
|
511
|
+
@denominator = UNITY_ARRAY
|
512
|
+
when DateTime, Date
|
513
|
+
@scalar = options[0].ajd
|
514
|
+
@numerator = ['<day>']
|
515
|
+
@denominator = UNITY_ARRAY
|
516
|
+
when /^\s*$/
|
517
|
+
raise ArgumentError, 'No Unit Specified'
|
518
|
+
when String
|
519
|
+
parse(options[0])
|
520
|
+
else
|
521
|
+
raise ArgumentError, 'Invalid Unit Format'
|
343
522
|
end
|
344
|
-
|
345
|
-
raise ArgumentError,
|
346
|
-
unary_unit =
|
523
|
+
update_base_scalar
|
524
|
+
raise ArgumentError, 'Temperatures must not be less than absolute zero' if temperature? && base_scalar < 0
|
525
|
+
unary_unit = units || ''
|
347
526
|
if options.first.instance_of?(String)
|
348
|
-
|
349
|
-
unless
|
350
|
-
|
351
|
-
|
352
|
-
@@cached_units[opt_units] = (
|
527
|
+
_opt_scalar, opt_units = RubyUnits::Unit.parse_into_numbers_and_units(options[0])
|
528
|
+
unless @@cached_units.keys.include?(opt_units) ||
|
529
|
+
(opt_units =~ %r{\D/[\d+\.]+}) ||
|
530
|
+
(opt_units =~ %r{(#{RubyUnits::Unit.temp_regex})|(#{STONE_LB_UNIT_REGEX})|(#{LBS_OZ_UNIT_REGEX})|(#{FEET_INCH_UNITS_REGEX})|%|(#{TIME_REGEX})|i\s?(.+)?|±|\+\/-})
|
531
|
+
@@cached_units[opt_units] = (scalar == 1 ? self : opt_units.to_unit) if opt_units && !opt_units.empty?
|
353
532
|
end
|
354
533
|
end
|
355
|
-
unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /#{RubyUnits::Unit.temp_regex}/)
|
356
|
-
@@cached_units[unary_unit] = (
|
534
|
+
unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /#{RubyUnits::Unit.temp_regex}/)
|
535
|
+
@@cached_units[unary_unit] = (scalar == 1 ? self : unary_unit.to_unit)
|
357
536
|
end
|
358
|
-
[@scalar, @numerator, @denominator, @base_scalar, @signature, @
|
359
|
-
|
537
|
+
[@scalar, @numerator, @denominator, @base_scalar, @signature, @base].each(&:freeze)
|
538
|
+
self
|
360
539
|
end
|
361
540
|
|
362
541
|
# @todo: figure out how to handle :counting units. This method should probably return :counting instead of :unitless for 'each'
|
363
542
|
# return the kind of the unit (:mass, :length, etc...)
|
364
543
|
# @return [Symbol]
|
365
544
|
def kind
|
366
|
-
|
367
|
-
end
|
368
|
-
|
369
|
-
# @private
|
370
|
-
# @return [Hash]
|
371
|
-
def self.cached
|
372
|
-
return @@cached_units
|
373
|
-
end
|
374
|
-
|
375
|
-
# @private
|
376
|
-
# @return [true]
|
377
|
-
def self.clear_cache
|
378
|
-
@@cached_units = {}
|
379
|
-
@@base_unit_cache = {}
|
380
|
-
RubyUnits::Unit.new(1)
|
381
|
-
return true
|
382
|
-
end
|
383
|
-
|
384
|
-
# @private
|
385
|
-
# @return [Hash]
|
386
|
-
def self.base_unit_cache
|
387
|
-
return @@base_unit_cache
|
388
|
-
end
|
389
|
-
|
390
|
-
# @example parse strings
|
391
|
-
# "1 minute in seconds"
|
392
|
-
# @param [String] input
|
393
|
-
# @return [Unit]
|
394
|
-
def self.parse(input)
|
395
|
-
first, second = input.scan(/(.+)\s(?:in|to|as)\s(.+)/i).first
|
396
|
-
second.nil? ? RubyUnits::Unit.new(first) : RubyUnits::Unit.new(first).convert_to(second)
|
545
|
+
@@kinds[signature]
|
397
546
|
end
|
398
547
|
|
399
548
|
# @return [Unit]
|
@@ -401,59 +550,60 @@ module RubyUnits
|
|
401
550
|
self
|
402
551
|
end
|
403
552
|
|
404
|
-
alias
|
553
|
+
alias unit to_unit
|
405
554
|
|
406
555
|
# Is this unit in base form?
|
407
556
|
# @return [Boolean]
|
408
|
-
def
|
409
|
-
return @
|
410
|
-
@
|
411
|
-
|
412
|
-
|
413
|
-
|
557
|
+
def base?
|
558
|
+
return @base if defined? @base
|
559
|
+
@base = (@numerator + @denominator)
|
560
|
+
.compact
|
561
|
+
.uniq
|
562
|
+
.map { |unit| RubyUnits::Unit.definition(unit) }
|
563
|
+
.all? { |element| element.unity? || element.base? }
|
564
|
+
@base
|
414
565
|
end
|
415
566
|
|
416
|
-
alias
|
567
|
+
alias is_base? base?
|
417
568
|
|
418
569
|
# convert to base SI units
|
419
570
|
# results of the conversion are cached so subsequent calls to this will be fast
|
420
571
|
# @return [Unit]
|
421
572
|
# @todo this is brittle as it depends on the display_name of a unit, which can be changed
|
422
573
|
def to_base
|
423
|
-
return self if
|
424
|
-
if @@
|
425
|
-
@signature = @@
|
426
|
-
base =
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
self.convert_to('degK')
|
574
|
+
return self if base?
|
575
|
+
if @@unit_map[units] =~ /\A<(?:temp|deg)[CRF]>\Z/
|
576
|
+
@signature = @@kinds.key(:temperature)
|
577
|
+
base = if temperature?
|
578
|
+
convert_to('tempK')
|
579
|
+
elsif degree?
|
580
|
+
convert_to('degK')
|
431
581
|
end
|
432
582
|
return base
|
433
583
|
end
|
434
584
|
|
435
|
-
cached = ((@@base_unit_cache[
|
585
|
+
cached = ((@@base_unit_cache[units] * scalar) rescue nil)
|
436
586
|
return cached if cached
|
437
587
|
|
438
588
|
num = []
|
439
589
|
den = []
|
440
590
|
q = 1
|
441
|
-
|
442
|
-
if @@
|
443
|
-
q *= @@
|
591
|
+
@numerator.compact.each do |unit|
|
592
|
+
if @@prefix_values[unit]
|
593
|
+
q *= @@prefix_values[unit]
|
444
594
|
else
|
445
|
-
q *= @@
|
446
|
-
num << @@
|
447
|
-
den << @@
|
595
|
+
q *= @@unit_values[unit][:scalar] if @@unit_values[unit]
|
596
|
+
num << @@unit_values[unit][:numerator] if @@unit_values[unit] && @@unit_values[unit][:numerator]
|
597
|
+
den << @@unit_values[unit][:denominator] if @@unit_values[unit] && @@unit_values[unit][:denominator]
|
448
598
|
end
|
449
599
|
end
|
450
|
-
|
451
|
-
if @@
|
452
|
-
q /= @@
|
600
|
+
@denominator.compact.each do |unit|
|
601
|
+
if @@prefix_values[unit]
|
602
|
+
q /= @@prefix_values[unit]
|
453
603
|
else
|
454
|
-
q /= @@
|
455
|
-
den << @@
|
456
|
-
num << @@
|
604
|
+
q /= @@unit_values[unit][:scalar] if @@unit_values[unit]
|
605
|
+
den << @@unit_values[unit][:numerator] if @@unit_values[unit] && @@unit_values[unit][:numerator]
|
606
|
+
num << @@unit_values[unit][:denominator] if @@unit_values[unit] && @@unit_values[unit][:denominator]
|
457
607
|
end
|
458
608
|
end
|
459
609
|
|
@@ -461,11 +611,11 @@ module RubyUnits
|
|
461
611
|
den = den.flatten.compact
|
462
612
|
num = UNITY_ARRAY if num.empty?
|
463
613
|
base = RubyUnits::Unit.new(RubyUnits::Unit.eliminate_terms(q, num, den))
|
464
|
-
@@base_unit_cache[
|
465
|
-
|
614
|
+
@@base_unit_cache[units] = base
|
615
|
+
base * @scalar
|
466
616
|
end
|
467
617
|
|
468
|
-
alias
|
618
|
+
alias base to_base
|
469
619
|
|
470
620
|
# Generate human readable output.
|
471
621
|
# If the name of a unit is passed, the unit will first be converted to the target unit before output.
|
@@ -482,48 +632,49 @@ module RubyUnits
|
|
482
632
|
#
|
483
633
|
# @param [Symbol] target_units
|
484
634
|
# @return [String]
|
485
|
-
def to_s(target_units=nil)
|
635
|
+
def to_s(target_units = nil)
|
486
636
|
out = @output[target_units]
|
487
|
-
if out
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
(DateTime.new(0) + self).strftime(target_units)
|
637
|
+
return out if out
|
638
|
+
separator = RubyUnits.configuration.separator
|
639
|
+
case target_units
|
640
|
+
when :ft
|
641
|
+
inches = convert_to('in').scalar.to_int
|
642
|
+
out = "#{(inches / 12).truncate}\'#{(inches % 12).round}\""
|
643
|
+
when :lbs
|
644
|
+
ounces = convert_to('oz').scalar.to_int
|
645
|
+
out = "#{(ounces / 16).truncate}#{separator}lbs, #{(ounces % 16).round}#{separator}oz"
|
646
|
+
when :stone
|
647
|
+
pounds = convert_to('lbs').scalar.to_int
|
648
|
+
out = "#{(pounds / 14).truncate}#{separator}stone, #{(pounds % 14).round}#{separator}lb"
|
649
|
+
when String
|
650
|
+
out = case target_units.strip
|
651
|
+
when /\A\s*\Z/ # whitespace only
|
652
|
+
''
|
653
|
+
when /(%[\-+\.\w#]+)\s*(.+)*/ # format string like '%0.2f in'
|
654
|
+
begin
|
655
|
+
if $2 # unit specified, need to convert
|
656
|
+
convert_to($2).to_s($1)
|
657
|
+
else
|
658
|
+
"#{$1 % @scalar}#{separator}#{$2 || units}".strip
|
510
659
|
end
|
511
|
-
|
512
|
-
self.
|
513
|
-
else
|
514
|
-
raise "unhandled case"
|
660
|
+
rescue # parse it like a strftime format string
|
661
|
+
(DateTime.new(0) + self).strftime(target_units)
|
515
662
|
end
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
663
|
+
when /(\S+)/ # unit only 'mm' or '1/mm'
|
664
|
+
convert_to($1).to_s
|
665
|
+
else
|
666
|
+
raise 'unhandled case'
|
667
|
+
end
|
668
|
+
else
|
669
|
+
out = case @scalar
|
670
|
+
when Rational, Complex
|
671
|
+
"#{@scalar}#{separator}#{units}"
|
672
|
+
else
|
673
|
+
"#{'%g' % @scalar}#{separator}#{units}"
|
674
|
+
end.strip
|
526
675
|
end
|
676
|
+
@output[target_units] = out
|
677
|
+
out
|
527
678
|
end
|
528
679
|
|
529
680
|
# Normally pretty prints the unit, but if you really want to see the guts of it, pass ':dump'
|
@@ -537,33 +688,33 @@ module RubyUnits
|
|
537
688
|
# true if unit is a 'temperature', false if a 'degree' or anything else
|
538
689
|
# @return [Boolean]
|
539
690
|
# @todo use unit definition to determine if it's a temperature instead of a regex
|
540
|
-
def
|
541
|
-
|
691
|
+
def temperature?
|
692
|
+
degree? && !(@@unit_map[units] =~ /temp[CFRK]/).nil?
|
542
693
|
end
|
543
694
|
|
544
|
-
alias
|
695
|
+
alias is_temperature? temperature?
|
545
696
|
|
546
697
|
# true if a degree unit or equivalent.
|
547
698
|
# @return [Boolean]
|
548
|
-
def
|
549
|
-
|
699
|
+
def degree?
|
700
|
+
kind == :temperature
|
550
701
|
end
|
551
702
|
|
552
|
-
alias
|
703
|
+
alias is_degree? degree?
|
553
704
|
|
554
705
|
# returns the 'degree' unit associated with a temperature unit
|
555
706
|
# @example '100 tempC'.to_unit.temperature_scale #=> 'degC'
|
556
707
|
# @return [String] possible values: degC, degF, degR, or degK
|
557
708
|
def temperature_scale
|
558
|
-
return nil unless
|
559
|
-
|
709
|
+
return nil unless temperature?
|
710
|
+
"deg#{@@unit_map[units][/temp([CFRK])/, 1]}"
|
560
711
|
end
|
561
712
|
|
562
713
|
# returns true if no associated units
|
563
714
|
# false, even if the units are "unitless" like 'radians, each, etc'
|
564
715
|
# @return [Boolean]
|
565
716
|
def unitless?
|
566
|
-
|
717
|
+
(@numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY)
|
567
718
|
end
|
568
719
|
|
569
720
|
# Compare two Unit objects. Throws an exception if they are not of compatible types.
|
@@ -573,19 +724,17 @@ module RubyUnits
|
|
573
724
|
# @raise [NoMethodError] when other does not define <=>
|
574
725
|
# @raise [ArgumentError] when units are not compatible
|
575
726
|
def <=>(other)
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
x, y = coerce(other)
|
588
|
-
return y <=> x
|
727
|
+
raise NoMethodError, "undefined method `<=>' for #{base_scalar.inspect}" unless base_scalar.respond_to?(:<=>)
|
728
|
+
if other.nil?
|
729
|
+
base_scalar <=> nil
|
730
|
+
elsif !temperature? && other.respond_to?(:zero?) && other.zero?
|
731
|
+
base_scalar <=> 0
|
732
|
+
elsif other.instance_of?(Unit)
|
733
|
+
raise ArgumentError, "Incompatible Units ('#{units}' not compatible with '#{other.units}')" unless self =~ other
|
734
|
+
base_scalar <=> other.base_scalar
|
735
|
+
else
|
736
|
+
x, y = coerce(other)
|
737
|
+
y <=> x
|
589
738
|
end
|
590
739
|
end
|
591
740
|
|
@@ -598,19 +747,18 @@ module RubyUnits
|
|
598
747
|
# @param [Object] other
|
599
748
|
# @return [Boolean]
|
600
749
|
def ==(other)
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
end
|
750
|
+
if other.respond_to?(:zero?) && other.zero?
|
751
|
+
zero?
|
752
|
+
elsif other.instance_of?(Unit)
|
753
|
+
return false unless self =~ other
|
754
|
+
base_scalar == other.base_scalar
|
755
|
+
else
|
756
|
+
begin
|
757
|
+
x, y = coerce(other)
|
758
|
+
x == y
|
759
|
+
rescue ArgumentError # return false when object cannot be coerced
|
760
|
+
false
|
761
|
+
end
|
614
762
|
end
|
615
763
|
end
|
616
764
|
|
@@ -625,20 +773,20 @@ module RubyUnits
|
|
625
773
|
# @return [Boolean]
|
626
774
|
def =~(other)
|
627
775
|
case other
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
776
|
+
when Unit
|
777
|
+
signature == other.signature
|
778
|
+
else
|
779
|
+
begin
|
780
|
+
x, y = coerce(other)
|
781
|
+
return x =~ y
|
782
|
+
rescue ArgumentError
|
783
|
+
return false
|
784
|
+
end
|
637
785
|
end
|
638
786
|
end
|
639
787
|
|
640
|
-
alias
|
641
|
-
alias
|
788
|
+
alias compatible? =~
|
789
|
+
alias compatible_with? =~
|
642
790
|
|
643
791
|
# Compare two units. Returns true if quantities and units match
|
644
792
|
# @example
|
@@ -648,20 +796,20 @@ module RubyUnits
|
|
648
796
|
# @return [Boolean]
|
649
797
|
def ===(other)
|
650
798
|
case other
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
799
|
+
when Unit
|
800
|
+
(scalar == other.scalar) && (units == other.units)
|
801
|
+
else
|
802
|
+
begin
|
803
|
+
x, y = coerce(other)
|
804
|
+
return x === y
|
805
|
+
rescue ArgumentError
|
806
|
+
return false
|
807
|
+
end
|
660
808
|
end
|
661
809
|
end
|
662
810
|
|
663
|
-
alias
|
664
|
-
alias
|
811
|
+
alias same? ===
|
812
|
+
alias same_as? ===
|
665
813
|
|
666
814
|
# Add two units together. Result is same units as receiver and scalar and base_scalar are updated appropriately
|
667
815
|
# throws an exception if the units are not compatible.
|
@@ -673,30 +821,29 @@ module RubyUnits
|
|
673
821
|
# @raise [ArgumentError] when adding a fixed time or date to a time span
|
674
822
|
def +(other)
|
675
823
|
case other
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
RubyUnits::Unit.new(:scalar => (self.scalar + other.convert_to(self.temperature_scale).scalar), :numerator => @numerator, :denominator => @denominator, :signature => @signature)
|
685
|
-
else
|
686
|
-
RubyUnits::Unit.new(:scalar => (other.scalar + self.convert_to(other.temperature_scale).scalar), :numerator => other.numerator, :denominator => other.denominator, :signature => other.signature)
|
687
|
-
end
|
688
|
-
else
|
689
|
-
@q ||= ((@@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar) rescue (self.units.to_unit.to_base.scalar))
|
690
|
-
RubyUnits::Unit.new(:scalar => (self.base_scalar + other.base_scalar)*@q, :numerator => @numerator, :denominator => @denominator, :signature => @signature)
|
691
|
-
end
|
824
|
+
when Unit
|
825
|
+
if zero?
|
826
|
+
other.dup
|
827
|
+
elsif self =~ other
|
828
|
+
raise ArgumentError, 'Cannot add two temperatures' if [self, other].all?(&:temperature?)
|
829
|
+
if [self, other].any?(&:temperature?)
|
830
|
+
if temperature?
|
831
|
+
RubyUnits::Unit.new(scalar: (scalar + other.convert_to(temperature_scale).scalar), numerator: @numerator, denominator: @denominator, signature: @signature)
|
692
832
|
else
|
693
|
-
|
833
|
+
RubyUnits::Unit.new(scalar: (other.scalar + convert_to(other.temperature_scale).scalar), numerator: other.numerator, denominator: other.denominator, signature: other.signature)
|
834
|
+
end
|
835
|
+
else
|
836
|
+
@q ||= ((@@cached_units[units].scalar / @@cached_units[units].base_scalar) rescue units.to_unit.to_base.scalar)
|
837
|
+
RubyUnits::Unit.new(scalar: (base_scalar + other.base_scalar) * @q, numerator: @numerator, denominator: @denominator, signature: @signature)
|
694
838
|
end
|
695
|
-
when Date, Time
|
696
|
-
raise ArgumentError, "Date and Time objects represent fixed points in time and cannot be added to a Unit"
|
697
839
|
else
|
698
|
-
|
699
|
-
|
840
|
+
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
|
841
|
+
end
|
842
|
+
when Date, Time
|
843
|
+
raise ArgumentError, 'Date and Time objects represent fixed points in time and cannot be added to a Unit'
|
844
|
+
else
|
845
|
+
x, y = coerce(other)
|
846
|
+
y + x
|
700
847
|
end
|
701
848
|
end
|
702
849
|
|
@@ -708,34 +855,32 @@ module RubyUnits
|
|
708
855
|
# @raise [ArgumentError] when subtracting a fixed time from a time span
|
709
856
|
def -(other)
|
710
857
|
case other
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
@q ||= ((@@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar) rescue (self.units.to_unit.scalar/self.units.to_unit.to_base.scalar))
|
729
|
-
RubyUnits::Unit.new(:scalar => (self.base_scalar - other.base_scalar)*@q, :numerator => @numerator, :denominator => @denominator, :signature => @signature)
|
730
|
-
end
|
731
|
-
else
|
732
|
-
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
|
858
|
+
when Unit
|
859
|
+
if zero?
|
860
|
+
if other.zero?
|
861
|
+
other.dup * -1 # preserve Units class
|
862
|
+
else
|
863
|
+
-other.dup
|
864
|
+
end
|
865
|
+
elsif self =~ other
|
866
|
+
if [self, other].all?(&:temperature?)
|
867
|
+
RubyUnits::Unit.new(scalar: (base_scalar - other.base_scalar), numerator: KELVIN, denominator: UNITY_ARRAY, signature: @signature).convert_to(temperature_scale)
|
868
|
+
elsif temperature?
|
869
|
+
RubyUnits::Unit.new(scalar: (base_scalar - other.base_scalar), numerator: ['<tempK>'], denominator: UNITY_ARRAY, signature: @signature).convert_to(self)
|
870
|
+
elsif other.temperature?
|
871
|
+
raise ArgumentError, 'Cannot subtract a temperature from a differential degree unit'
|
872
|
+
else
|
873
|
+
@q ||= ((@@cached_units[units].scalar / @@cached_units[units].base_scalar) rescue (units.to_unit.scalar / units.to_unit.to_base.scalar))
|
874
|
+
RubyUnits::Unit.new(scalar: (base_scalar - other.base_scalar) * @q, numerator: @numerator, denominator: @denominator, signature: @signature)
|
733
875
|
end
|
734
|
-
when Time
|
735
|
-
raise ArgumentError, "Date and Time objects represent fixed points in time and cannot be subtracted from to a Unit, which can only represent time spans"
|
736
876
|
else
|
737
|
-
|
738
|
-
|
877
|
+
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
|
878
|
+
end
|
879
|
+
when Time
|
880
|
+
raise ArgumentError, 'Date and Time objects represent fixed points in time and cannot be subtracted from to a Unit, which can only represent time spans'
|
881
|
+
else
|
882
|
+
x, y = coerce(other)
|
883
|
+
y - x
|
739
884
|
end
|
740
885
|
end
|
741
886
|
|
@@ -745,16 +890,16 @@ module RubyUnits
|
|
745
890
|
# @raise [ArgumentError] when attempting to multiply two temperatures
|
746
891
|
def *(other)
|
747
892
|
case other
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
893
|
+
when Unit
|
894
|
+
raise ArgumentError, 'Cannot multiply by temperatures' if [other, self].any?(&:temperature?)
|
895
|
+
opts = RubyUnits::Unit.eliminate_terms(@scalar * other.scalar, @numerator + other.numerator, @denominator + other.denominator)
|
896
|
+
opts[:signature] = @signature + other.signature
|
897
|
+
RubyUnits::Unit.new(opts)
|
898
|
+
when Numeric
|
899
|
+
RubyUnits::Unit.new(scalar: @scalar * other, numerator: @numerator, denominator: @denominator, signature: @signature)
|
900
|
+
else
|
901
|
+
x, y = coerce(other)
|
902
|
+
x * y
|
758
903
|
end
|
759
904
|
end
|
760
905
|
|
@@ -766,18 +911,18 @@ module RubyUnits
|
|
766
911
|
# @raise [ArgumentError] if attempting to divide a temperature by another temperature
|
767
912
|
def /(other)
|
768
913
|
case other
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
914
|
+
when Unit
|
915
|
+
raise ZeroDivisionError if other.zero?
|
916
|
+
raise ArgumentError, 'Cannot divide with temperatures' if [other, self].any?(&:temperature?)
|
917
|
+
opts = RubyUnits::Unit.eliminate_terms(@scalar / other.scalar, @numerator + other.denominator, @denominator + other.numerator)
|
918
|
+
opts[:signature] = @signature - other.signature
|
919
|
+
RubyUnits::Unit.new(opts)
|
920
|
+
when Numeric
|
921
|
+
raise ZeroDivisionError if other.zero?
|
922
|
+
RubyUnits::Unit.new(scalar: @scalar / other, numerator: @numerator, denominator: @denominator, signature: @signature)
|
923
|
+
else
|
924
|
+
x, y = coerce(other)
|
925
|
+
y / x
|
781
926
|
end
|
782
927
|
end
|
783
928
|
|
@@ -788,18 +933,15 @@ module RubyUnits
|
|
788
933
|
# @return [Array]
|
789
934
|
def divmod(other)
|
790
935
|
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ other
|
791
|
-
if
|
792
|
-
|
793
|
-
else
|
794
|
-
return self.to_base.scalar.divmod(other.to_base.scalar)
|
795
|
-
end
|
936
|
+
return scalar.divmod(other.scalar) if units == other.units
|
937
|
+
to_base.scalar.divmod(other.to_base.scalar)
|
796
938
|
end
|
797
939
|
|
798
940
|
# perform a modulo on a unit, will raise an exception if the units are not compatible
|
799
941
|
# @param [Object] other
|
800
942
|
# @return [Integer]
|
801
943
|
def %(other)
|
802
|
-
|
944
|
+
divmod(other).last
|
803
945
|
end
|
804
946
|
|
805
947
|
# Exponentiate. Only takes integer powers.
|
@@ -817,26 +959,26 @@ module RubyUnits
|
|
817
959
|
# @raise [ArgumentError] when attempting to raise to a complex number
|
818
960
|
# @raise [ArgumentError] when an invalid exponent is passed
|
819
961
|
def **(other)
|
820
|
-
raise ArgumentError,
|
821
|
-
if other.
|
822
|
-
return
|
962
|
+
raise ArgumentError, 'Cannot raise a temperature to a power' if temperature?
|
963
|
+
if other.is_a?(Numeric)
|
964
|
+
return inverse if other == -1
|
823
965
|
return self if other == 1
|
824
966
|
return 1 if other.zero?
|
825
967
|
end
|
826
968
|
case other
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
969
|
+
when Rational
|
970
|
+
return power(other.numerator).root(other.denominator)
|
971
|
+
when Integer
|
972
|
+
return power(other)
|
973
|
+
when Float
|
974
|
+
return self**other.to_i if other == other.to_i
|
975
|
+
valid = (1..9).map { |x| 1 / x }
|
976
|
+
raise ArgumentError, 'Not a n-th root (1..9), use 1/n' unless valid.include? other.abs
|
977
|
+
return root((1 / other).to_int)
|
978
|
+
when Complex
|
979
|
+
raise ArgumentError, 'exponentiation of complex numbers is not yet supported.'
|
980
|
+
else
|
981
|
+
raise ArgumentError, 'Invalid Exponent'
|
840
982
|
end
|
841
983
|
end
|
842
984
|
|
@@ -846,16 +988,13 @@ module RubyUnits
|
|
846
988
|
# @raise [ArgumentError] when attempting to raise a temperature to a power
|
847
989
|
# @raise [ArgumentError] when n is not an integer
|
848
990
|
def power(n)
|
849
|
-
raise ArgumentError,
|
850
|
-
raise ArgumentError,
|
851
|
-
return
|
991
|
+
raise ArgumentError, 'Cannot raise a temperature to a power' if temperature?
|
992
|
+
raise ArgumentError, 'Exponent must an Integer' unless n.is_a?(Integer)
|
993
|
+
return inverse if n == -1
|
852
994
|
return 1 if n.zero?
|
853
995
|
return self if n == 1
|
854
|
-
if n
|
855
|
-
|
856
|
-
else
|
857
|
-
return (1..-(n-1).to_i).inject(self) { |product, x| product / self }
|
858
|
-
end
|
996
|
+
return (1..(n - 1).to_i).inject(self) { |acc, _elem| acc * self } if n >= 0
|
997
|
+
(1..-(n - 1).to_i).inject(self) { |acc, _elem| acc / self }
|
859
998
|
end
|
860
999
|
|
861
1000
|
# Calculates the n-th root of a unit
|
@@ -866,37 +1005,36 @@ module RubyUnits
|
|
866
1005
|
# @raise [ArgumentError] when n is not an integer
|
867
1006
|
# @raise [ArgumentError] when n is 0
|
868
1007
|
def root(n)
|
869
|
-
raise ArgumentError,
|
870
|
-
raise ArgumentError,
|
871
|
-
raise ArgumentError,
|
1008
|
+
raise ArgumentError, 'Cannot take the root of a temperature' if temperature?
|
1009
|
+
raise ArgumentError, 'Exponent must an Integer' unless n.is_a?(Integer)
|
1010
|
+
raise ArgumentError, '0th root undefined' if n.zero?
|
872
1011
|
return self if n == 1
|
873
|
-
return
|
1012
|
+
return root(n.abs).inverse if n < 0
|
874
1013
|
|
875
|
-
vec =
|
876
|
-
vec =vec.map { |x| x % n }
|
877
|
-
raise ArgumentError,
|
1014
|
+
vec = unit_signature_vector
|
1015
|
+
vec = vec.map { |x| x % n }
|
1016
|
+
raise ArgumentError, 'Illegal root' unless vec.max.zero?
|
878
1017
|
num = @numerator.dup
|
879
1018
|
den = @denominator.dup
|
880
1019
|
|
881
|
-
|
882
|
-
x = num.find_all { |i| i==item }.size
|
883
|
-
r = ((x/n)*(n-1)).to_int
|
884
|
-
r.times {
|
1020
|
+
@numerator.uniq.each do |item|
|
1021
|
+
x = num.find_all { |i| i == item }.size
|
1022
|
+
r = ((x / n) * (n - 1)).to_int
|
1023
|
+
r.times { num.delete_at(num.index(item)) }
|
885
1024
|
end
|
886
1025
|
|
887
|
-
|
888
|
-
x = den.find_all { |i| i==item }.size
|
889
|
-
r = ((x/n)*(n-1)).to_int
|
890
|
-
r.times {
|
1026
|
+
@denominator.uniq.each do |item|
|
1027
|
+
x = den.find_all { |i| i == item }.size
|
1028
|
+
r = ((x / n) * (n - 1)).to_int
|
1029
|
+
r.times { den.delete_at(den.index(item)) }
|
891
1030
|
end
|
892
|
-
|
893
|
-
return RubyUnits::Unit.new(:scalar => q, :numerator => num, :denominator => den)
|
1031
|
+
RubyUnits::Unit.new(scalar: @scalar**Rational(1, n), numerator: num, denominator: den)
|
894
1032
|
end
|
895
1033
|
|
896
1034
|
# returns inverse of Unit (1/unit)
|
897
1035
|
# @return [Unit]
|
898
1036
|
def inverse
|
899
|
-
|
1037
|
+
RubyUnits::Unit.new('1') / self
|
900
1038
|
end
|
901
1039
|
|
902
1040
|
# convert to a specified unit string or to the same units as another Unit
|
@@ -922,182 +1060,195 @@ module RubyUnits
|
|
922
1060
|
return self if other.nil?
|
923
1061
|
return self if TrueClass === other
|
924
1062
|
return self if FalseClass === other
|
925
|
-
if (Unit === other && other.
|
926
|
-
raise ArgumentError,
|
927
|
-
start_unit =
|
1063
|
+
if (Unit === other && other.temperature?) || (String === other && other =~ /temp[CFRK]/)
|
1064
|
+
raise ArgumentError, 'Receiver is not a temperature unit' unless degree?
|
1065
|
+
start_unit = units
|
928
1066
|
target_unit = other.units rescue other
|
929
1067
|
unless @base_scalar
|
930
|
-
@base_scalar = case @@
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
1068
|
+
@base_scalar = case @@unit_map[start_unit]
|
1069
|
+
when '<tempC>'
|
1070
|
+
@scalar + 273.15
|
1071
|
+
when '<tempK>'
|
1072
|
+
@scalar
|
1073
|
+
when '<tempF>'
|
1074
|
+
(@scalar + 459.67) * Rational(5, 9)
|
1075
|
+
when '<tempR>'
|
1076
|
+
@scalar * Rational(5, 9)
|
939
1077
|
end
|
940
1078
|
end
|
941
|
-
q= case @@
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
1079
|
+
q = case @@unit_map[target_unit]
|
1080
|
+
when '<tempC>'
|
1081
|
+
@base_scalar - 273.15
|
1082
|
+
when '<tempK>'
|
1083
|
+
@base_scalar
|
1084
|
+
when '<tempF>'
|
1085
|
+
@base_scalar * Rational(9, 5) - 459.67
|
1086
|
+
when '<tempR>'
|
1087
|
+
@base_scalar * Rational(9, 5)
|
1088
|
+
end
|
951
1089
|
return RubyUnits::Unit.new("#{q} #{target_unit}")
|
952
1090
|
else
|
953
1091
|
case other
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
1092
|
+
when Unit
|
1093
|
+
return self if other.units == units
|
1094
|
+
target = other
|
1095
|
+
when String
|
1096
|
+
target = RubyUnits::Unit.new(other)
|
1097
|
+
else
|
1098
|
+
raise ArgumentError, 'Unknown target units'
|
961
1099
|
end
|
962
1100
|
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ target
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
q = @scalar * ((
|
969
|
-
((
|
970
|
-
return RubyUnits::Unit.new(:
|
1101
|
+
numerator1 = @numerator.map { |x| @@prefix_values[x] ? @@prefix_values[x] : x }.map { |i| i.is_a?(Numeric) ? i : @@unit_values[i][:scalar] }.compact
|
1102
|
+
denominator1 = @denominator.map { |x| @@prefix_values[x] ? @@prefix_values[x] : x }.map { |i| i.is_a?(Numeric) ? i : @@unit_values[i][:scalar] }.compact
|
1103
|
+
numerator2 = target.numerator.map { |x| @@prefix_values[x] ? @@prefix_values[x] : x }.map { |x| x.is_a?(Numeric) ? x : @@unit_values[x][:scalar] }.compact
|
1104
|
+
denominator2 = target.denominator.map { |x| @@prefix_values[x] ? @@prefix_values[x] : x }.map { |x| x.is_a?(Numeric) ? x : @@unit_values[x][:scalar] }.compact
|
1105
|
+
|
1106
|
+
q = @scalar * ((numerator1 + denominator2).inject(1) { |acc, elem| acc * elem }) /
|
1107
|
+
((numerator2 + denominator1).inject(1) { |acc, elem| acc * elem })
|
1108
|
+
return RubyUnits::Unit.new(scalar: q, numerator: target.numerator, denominator: target.denominator, signature: target.signature)
|
971
1109
|
end
|
972
1110
|
end
|
973
1111
|
|
974
|
-
alias
|
975
|
-
alias
|
1112
|
+
alias >> convert_to
|
1113
|
+
alias to convert_to
|
976
1114
|
|
977
1115
|
# converts the unit back to a float if it is unitless. Otherwise raises an exception
|
978
1116
|
# @return [Float]
|
979
1117
|
# @raise [RuntimeError] when not unitless
|
980
1118
|
def to_f
|
981
|
-
return @scalar.to_f if
|
982
|
-
raise
|
1119
|
+
return @scalar.to_f if unitless?
|
1120
|
+
raise "Cannot convert '#{self}' to Float unless unitless. Use Unit#scalar"
|
983
1121
|
end
|
984
1122
|
|
985
1123
|
# converts the unit back to a complex if it is unitless. Otherwise raises an exception
|
986
1124
|
# @return [Complex]
|
987
1125
|
# @raise [RuntimeError] when not unitless
|
988
1126
|
def to_c
|
989
|
-
return Complex(@scalar) if
|
990
|
-
raise
|
1127
|
+
return Complex(@scalar) if unitless?
|
1128
|
+
raise "Cannot convert '#{self}' to Complex unless unitless. Use Unit#scalar"
|
991
1129
|
end
|
992
1130
|
|
993
1131
|
# if unitless, returns an int, otherwise raises an error
|
994
1132
|
# @return [Integer]
|
995
1133
|
# @raise [RuntimeError] when not unitless
|
996
1134
|
def to_i
|
997
|
-
return @scalar.to_int if
|
998
|
-
raise
|
1135
|
+
return @scalar.to_int if unitless?
|
1136
|
+
raise "Cannot convert '#{self}' to Integer unless unitless. Use Unit#scalar"
|
999
1137
|
end
|
1000
1138
|
|
1001
|
-
alias
|
1139
|
+
alias to_int to_i
|
1002
1140
|
|
1003
1141
|
# if unitless, returns a Rational, otherwise raises an error
|
1004
1142
|
# @return [Rational]
|
1005
1143
|
# @raise [RuntimeError] when not unitless
|
1006
1144
|
def to_r
|
1007
|
-
return @scalar.to_r if
|
1008
|
-
raise
|
1145
|
+
return @scalar.to_r if unitless?
|
1146
|
+
raise "Cannot convert '#{self}' to Rational unless unitless. Use Unit#scalar"
|
1009
1147
|
end
|
1010
1148
|
|
1011
1149
|
# Returns string formatted for json
|
1012
1150
|
# @return [String]
|
1013
|
-
def as_json(*
|
1151
|
+
def as_json(*)
|
1014
1152
|
to_s
|
1015
1153
|
end
|
1016
1154
|
|
1017
1155
|
# returns the 'unit' part of the Unit object without the scalar
|
1018
1156
|
# @return [String]
|
1019
|
-
def units(with_prefix
|
1020
|
-
return
|
1021
|
-
output_numerator = []
|
1157
|
+
def units(with_prefix: true)
|
1158
|
+
return '' if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY
|
1159
|
+
output_numerator = ['1']
|
1022
1160
|
output_denominator = []
|
1023
1161
|
num = @numerator.clone.compact
|
1024
1162
|
den = @denominator.clone.compact
|
1025
1163
|
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1164
|
+
unless num == UNITY_ARRAY
|
1165
|
+
definitions = num.map { |element| RubyUnits::Unit.definition(element) }
|
1166
|
+
definitions.reject!(&:prefix?) unless with_prefix
|
1167
|
+
# there is a bug in jruby 9.1.6.0's implementation of chunk_while
|
1168
|
+
# see https://github.com/jruby/jruby/issues/4410
|
1169
|
+
# TODO: fix this after jruby fixes their bug.
|
1170
|
+
definitions = if definitions.respond_to?(:chunk_while) && RUBY_ENGINE != 'jruby'
|
1171
|
+
definitions.chunk_while { |defn, _| defn.prefix? }.to_a
|
1172
|
+
else # chunk_while is new to ruby 2.3+, so fallback to less efficient methods for older ruby
|
1173
|
+
result = []
|
1174
|
+
enumerator = definitions.to_enum
|
1175
|
+
loop do
|
1176
|
+
first = enumerator.next
|
1177
|
+
result << (first.prefix? ? [first, enumerator.next] : [first])
|
1178
|
+
end
|
1179
|
+
result
|
1180
|
+
end
|
1181
|
+
output_numerator = definitions.map { |element| element.map(&:display_name).join }
|
1038
1182
|
end
|
1039
1183
|
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1184
|
+
unless den == UNITY_ARRAY
|
1185
|
+
definitions = den.map { |element| RubyUnits::Unit.definition(element) }
|
1186
|
+
definitions.reject!(&:prefix?) unless with_prefix
|
1187
|
+
# there is a bug in jruby 9.1.6.0's implementation of chunk_while
|
1188
|
+
# see https://github.com/jruby/jruby/issues/4410
|
1189
|
+
# TODO: fix this after jruby fixes their bug.
|
1190
|
+
definitions = if definitions.respond_to?(:chunk_while) && RUBY_ENGINE != 'jruby'
|
1191
|
+
definitions.chunk_while { |defn, _| defn.prefix? }.to_a
|
1192
|
+
else # chunk_while is new to ruby 2.3+, so fallback to less efficient methods for older ruby
|
1193
|
+
result = []
|
1194
|
+
enumerator = definitions.to_enum
|
1195
|
+
loop do
|
1196
|
+
first = enumerator.next
|
1197
|
+
result << (first.prefix? ? [first, enumerator.next] : [first])
|
1198
|
+
end
|
1199
|
+
result
|
1200
|
+
end
|
1201
|
+
output_denominator = definitions.map { |element| element.map(&:display_name).join }
|
1052
1202
|
end
|
1053
1203
|
|
1054
|
-
on = output_numerator
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1204
|
+
on = output_numerator
|
1205
|
+
.uniq
|
1206
|
+
.map { |x| [x, output_numerator.count(x)] }
|
1207
|
+
.map { |element, power| (element.to_s.strip + (power > 1 ? "^#{power}" : '')) }
|
1208
|
+
od = output_denominator
|
1209
|
+
.uniq
|
1210
|
+
.map { |x| [x, output_denominator.count(x)] }
|
1211
|
+
.map { |element, power| (element.to_s.strip + (power > 1 ? "^#{power}" : '')) }
|
1212
|
+
"#{on.join('*')}#{od.empty? ? '' : '/' + od.join('*')}".strip
|
1062
1213
|
end
|
1063
1214
|
|
1064
1215
|
# negates the scalar of the Unit
|
1065
1216
|
# @return [Numeric,Unit]
|
1066
1217
|
def -@
|
1067
|
-
return -@scalar if
|
1068
|
-
|
1218
|
+
return -@scalar if unitless?
|
1219
|
+
dup * -1
|
1069
1220
|
end
|
1070
1221
|
|
1071
1222
|
# absolute value of a unit
|
1072
1223
|
# @return [Numeric,Unit]
|
1073
1224
|
def abs
|
1074
|
-
return @scalar.abs if
|
1075
|
-
|
1225
|
+
return @scalar.abs if unitless?
|
1226
|
+
RubyUnits::Unit.new(@scalar.abs, @numerator, @denominator)
|
1076
1227
|
end
|
1077
1228
|
|
1078
1229
|
# ceil of a unit
|
1079
1230
|
# @return [Numeric,Unit]
|
1080
1231
|
def ceil
|
1081
|
-
return @scalar.ceil if
|
1082
|
-
|
1232
|
+
return @scalar.ceil if unitless?
|
1233
|
+
RubyUnits::Unit.new(@scalar.ceil, @numerator, @denominator)
|
1083
1234
|
end
|
1084
1235
|
|
1085
1236
|
# @return [Numeric,Unit]
|
1086
1237
|
def floor
|
1087
|
-
return @scalar.floor if
|
1088
|
-
|
1238
|
+
return @scalar.floor if unitless?
|
1239
|
+
RubyUnits::Unit.new(@scalar.floor, @numerator, @denominator)
|
1089
1240
|
end
|
1090
1241
|
|
1091
1242
|
# @return [Numeric,Unit]
|
1092
1243
|
def round(ndigits = 0)
|
1093
|
-
return @scalar.round(ndigits) if
|
1094
|
-
|
1244
|
+
return @scalar.round(ndigits) if unitless?
|
1245
|
+
RubyUnits::Unit.new(@scalar.round(ndigits), @numerator, @denominator)
|
1095
1246
|
end
|
1096
1247
|
|
1097
1248
|
# @return [Numeric, Unit]
|
1098
1249
|
def truncate
|
1099
|
-
return @scalar.truncate if
|
1100
|
-
|
1250
|
+
return @scalar.truncate if unitless?
|
1251
|
+
RubyUnits::Unit.new(@scalar.truncate, @numerator, @denominator)
|
1101
1252
|
end
|
1102
1253
|
|
1103
1254
|
# returns next unit in a range. '1 mm'.to_unit.succ #=> '2 mm'.to_unit
|
@@ -1105,65 +1256,65 @@ module RubyUnits
|
|
1105
1256
|
# @return [Unit]
|
1106
1257
|
# @raise [ArgumentError] when scalar is not equal to an integer
|
1107
1258
|
def succ
|
1108
|
-
raise ArgumentError,
|
1109
|
-
|
1259
|
+
raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
|
1260
|
+
RubyUnits::Unit.new(@scalar.to_i.succ, @numerator, @denominator)
|
1110
1261
|
end
|
1111
1262
|
|
1112
|
-
alias
|
1263
|
+
alias next succ
|
1113
1264
|
|
1114
1265
|
# returns previous unit in a range. '2 mm'.to_unit.pred #=> '1 mm'.to_unit
|
1115
1266
|
# only works when the scalar is an integer
|
1116
1267
|
# @return [Unit]
|
1117
1268
|
# @raise [ArgumentError] when scalar is not equal to an integer
|
1118
1269
|
def pred
|
1119
|
-
raise ArgumentError,
|
1120
|
-
|
1270
|
+
raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
|
1271
|
+
RubyUnits::Unit.new(@scalar.to_i.pred, @numerator, @denominator)
|
1121
1272
|
end
|
1122
1273
|
|
1123
1274
|
# Tries to make a Time object from current unit. Assumes the current unit hold the duration in seconds from the epoch.
|
1124
1275
|
# @return [Time]
|
1125
1276
|
def to_time
|
1126
|
-
|
1277
|
+
Time.at(self)
|
1127
1278
|
end
|
1128
1279
|
|
1129
|
-
alias
|
1280
|
+
alias time to_time
|
1130
1281
|
|
1131
1282
|
# convert a duration to a DateTime. This will work so long as the duration is the duration from the zero date
|
1132
1283
|
# defined by DateTime
|
1133
1284
|
# @return [DateTime]
|
1134
1285
|
def to_datetime
|
1135
|
-
|
1286
|
+
DateTime.new!(convert_to('d').scalar)
|
1136
1287
|
end
|
1137
1288
|
|
1138
1289
|
# @return [Date]
|
1139
1290
|
def to_date
|
1140
|
-
|
1291
|
+
Date.new0(convert_to('d').scalar)
|
1141
1292
|
end
|
1142
1293
|
|
1143
1294
|
# true if scalar is zero
|
1144
1295
|
# @return [Boolean]
|
1145
1296
|
def zero?
|
1146
|
-
|
1297
|
+
base_scalar.zero?
|
1147
1298
|
end
|
1148
1299
|
|
1149
1300
|
# @example '5 min'.to_unit.ago
|
1150
1301
|
# @return [Unit]
|
1151
1302
|
def ago
|
1152
|
-
|
1303
|
+
before
|
1153
1304
|
end
|
1154
1305
|
|
1155
1306
|
# @example '5 min'.before(time)
|
1156
1307
|
# @return [Unit]
|
1157
1308
|
def before(time_point = ::Time.now)
|
1158
1309
|
case time_point
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1310
|
+
when Time, Date, DateTime
|
1311
|
+
return (time_point - self rescue time_point.to_datetime - self)
|
1312
|
+
else
|
1313
|
+
raise ArgumentError, 'Must specify a Time, Date, or DateTime'
|
1163
1314
|
end
|
1164
1315
|
end
|
1165
1316
|
|
1166
|
-
alias
|
1317
|
+
alias before_now before
|
1167
1318
|
|
1168
1319
|
# @example 'min'.since(time)
|
1169
1320
|
# @param [Time, Date, DateTime] time_point
|
@@ -1172,11 +1323,11 @@ module RubyUnits
|
|
1172
1323
|
def since(time_point)
|
1173
1324
|
case time_point
|
1174
1325
|
when Time
|
1175
|
-
|
1326
|
+
(Time.now - time_point).to_unit('s').convert_to(self)
|
1176
1327
|
when DateTime, Date
|
1177
|
-
|
1328
|
+
(DateTime.now - time_point).to_unit('d').convert_to(self)
|
1178
1329
|
else
|
1179
|
-
|
1330
|
+
raise ArgumentError, 'Must specify a Time, Date, or DateTime'
|
1180
1331
|
end
|
1181
1332
|
end
|
1182
1333
|
|
@@ -1186,11 +1337,11 @@ module RubyUnits
|
|
1186
1337
|
def until(time_point)
|
1187
1338
|
case time_point
|
1188
1339
|
when Time
|
1189
|
-
|
1340
|
+
(time_point - Time.now).to_unit('s').convert_to(self)
|
1190
1341
|
when DateTime, Date
|
1191
|
-
|
1342
|
+
(time_point - DateTime.now).to_unit('d').convert_to(self)
|
1192
1343
|
else
|
1193
|
-
|
1344
|
+
raise ArgumentError, 'Must specify a Time, Date, or DateTime'
|
1194
1345
|
end
|
1195
1346
|
end
|
1196
1347
|
|
@@ -1200,41 +1351,52 @@ module RubyUnits
|
|
1200
1351
|
# @raise [ArgumentError] when passed argument is not a Time, Date, or DateTime
|
1201
1352
|
def from(time_point)
|
1202
1353
|
case time_point
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1354
|
+
when Time, DateTime, Date
|
1355
|
+
(time_point + self rescue time_point.to_datetime + self)
|
1356
|
+
else
|
1357
|
+
raise ArgumentError, 'Must specify a Time, Date, or DateTime'
|
1207
1358
|
end
|
1208
1359
|
end
|
1209
1360
|
|
1210
|
-
alias
|
1211
|
-
alias
|
1361
|
+
alias after from
|
1362
|
+
alias from_now from
|
1212
1363
|
|
1213
1364
|
# automatically coerce objects to units when possible
|
1214
1365
|
# if an object defines a 'to_unit' method, it will be coerced using that method
|
1215
1366
|
# @param [Object, #to_unit]
|
1216
1367
|
# @return [Array]
|
1217
1368
|
def coerce(other)
|
1218
|
-
if other.respond_to? :to_unit
|
1219
|
-
return [other.to_unit, self]
|
1220
|
-
end
|
1369
|
+
return [other.to_unit, self] if other.respond_to? :to_unit
|
1221
1370
|
case other
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1371
|
+
when Unit
|
1372
|
+
[other, self]
|
1373
|
+
else
|
1374
|
+
[RubyUnits::Unit.new(other), self]
|
1226
1375
|
end
|
1227
1376
|
end
|
1228
1377
|
|
1229
1378
|
# returns a new unit that has been scaled to be more in line with typical usage.
|
1230
1379
|
def best_prefix
|
1231
|
-
return
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1380
|
+
return to_base if scalar.zero?
|
1381
|
+
best_prefix = if kind == :information
|
1382
|
+
@@prefix_values.key(2**((Math.log(base_scalar, 2) / 10.0).floor * 10))
|
1383
|
+
else
|
1384
|
+
@@prefix_values.key(10**((Math.log10(base_scalar) / 3.0).floor * 3))
|
1385
|
+
end
|
1386
|
+
to(RubyUnits::Unit.new(@@prefix_map.key(best_prefix) + units(with_prefix: false)))
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
# override hash method so objects with same values are considered equal
|
1390
|
+
def hash
|
1391
|
+
[
|
1392
|
+
@scalar,
|
1393
|
+
@numerator,
|
1394
|
+
@denominator,
|
1395
|
+
@base,
|
1396
|
+
@signature,
|
1397
|
+
@base_scalar,
|
1398
|
+
@unit_name
|
1399
|
+
].hash
|
1238
1400
|
end
|
1239
1401
|
|
1240
1402
|
# Protected and Private Functions that should only be called from this class
|
@@ -1243,11 +1405,11 @@ module RubyUnits
|
|
1243
1405
|
# figure out what the scalar part of the base unit for this unit is
|
1244
1406
|
# @return [nil]
|
1245
1407
|
def update_base_scalar
|
1246
|
-
if
|
1408
|
+
if base?
|
1247
1409
|
@base_scalar = @scalar
|
1248
1410
|
@signature = unit_signature
|
1249
1411
|
else
|
1250
|
-
base =
|
1412
|
+
base = to_base
|
1251
1413
|
@base_scalar = base.scalar
|
1252
1414
|
@signature = base.signature
|
1253
1415
|
end
|
@@ -1257,7 +1419,7 @@ module RubyUnits
|
|
1257
1419
|
# @return [Array]
|
1258
1420
|
# @raise [ArgumentError] when exponent associated with a unit is > 20 or < -20
|
1259
1421
|
def unit_signature_vector
|
1260
|
-
return
|
1422
|
+
return to_base.unit_signature_vector unless base?
|
1261
1423
|
vector = Array.new(SIGNATURE_VECTOR.size, 0)
|
1262
1424
|
# it's possible to have a kind that misses the array... kinds like :counting
|
1263
1425
|
# are more like prefixes, so don't use them to calculate the vector
|
@@ -1269,11 +1431,10 @@ module RubyUnits
|
|
1269
1431
|
index = SIGNATURE_VECTOR.index(definition.kind)
|
1270
1432
|
vector[index] -= 1 if index
|
1271
1433
|
end
|
1272
|
-
raise ArgumentError,
|
1273
|
-
|
1434
|
+
raise ArgumentError, 'Power out of range (-20 < net power of a unit < 20)' if vector.any? { |x| x.abs >= 20 }
|
1435
|
+
vector
|
1274
1436
|
end
|
1275
1437
|
|
1276
|
-
|
1277
1438
|
private
|
1278
1439
|
|
1279
1440
|
# used by #dup to duplicate a Unit
|
@@ -1294,61 +1455,8 @@ module RubyUnits
|
|
1294
1455
|
return @signature unless @signature.nil?
|
1295
1456
|
vector = unit_signature_vector
|
1296
1457
|
vector.each_with_index { |item, index| vector[index] = item * 20**index }
|
1297
|
-
@signature=vector.inject(0) { |
|
1298
|
-
|
1299
|
-
end
|
1300
|
-
|
1301
|
-
# @param [Numeric] q quantity
|
1302
|
-
# @param [Array] n numerator
|
1303
|
-
# @param [Array] d denominator
|
1304
|
-
# @return [Hash]
|
1305
|
-
def self.eliminate_terms(q, n, d)
|
1306
|
-
num = n.dup
|
1307
|
-
den = d.dup
|
1308
|
-
|
1309
|
-
num.delete_if { |v| v == UNITY }
|
1310
|
-
den.delete_if { |v| v == UNITY }
|
1311
|
-
combined = Hash.new(0)
|
1312
|
-
|
1313
|
-
i = 0
|
1314
|
-
loop do
|
1315
|
-
break if i > num.size
|
1316
|
-
if @@PREFIX_VALUES.has_key? num[i]
|
1317
|
-
k = [num[i], num[i+1]]
|
1318
|
-
i += 2
|
1319
|
-
else
|
1320
|
-
k = num[i]
|
1321
|
-
i += 1
|
1322
|
-
end
|
1323
|
-
combined[k] += 1 unless k.nil? || k == UNITY
|
1324
|
-
end
|
1325
|
-
|
1326
|
-
j = 0
|
1327
|
-
loop do
|
1328
|
-
break if j > den.size
|
1329
|
-
if @@PREFIX_VALUES.has_key? den[j]
|
1330
|
-
k = [den[j], den[j+1]]
|
1331
|
-
j += 2
|
1332
|
-
else
|
1333
|
-
k = den[j]
|
1334
|
-
j += 1
|
1335
|
-
end
|
1336
|
-
combined[k] -= 1 unless k.nil? || k == UNITY
|
1337
|
-
end
|
1338
|
-
|
1339
|
-
num = []
|
1340
|
-
den = []
|
1341
|
-
for key, value in combined do
|
1342
|
-
case
|
1343
|
-
when value > 0
|
1344
|
-
value.times { num << key }
|
1345
|
-
when value < 0
|
1346
|
-
value.abs.times { den << key }
|
1347
|
-
end
|
1348
|
-
end
|
1349
|
-
num = UNITY_ARRAY if num.empty?
|
1350
|
-
den = UNITY_ARRAY if den.empty?
|
1351
|
-
return { :scalar => q, :numerator => num.flatten.compact, :denominator => den.flatten.compact }
|
1458
|
+
@signature = vector.inject(0) { |acc, elem| acc + elem }
|
1459
|
+
@signature
|
1352
1460
|
end
|
1353
1461
|
|
1354
1462
|
# parse a string into a unit object.
|
@@ -1364,17 +1472,12 @@ module RubyUnits
|
|
1364
1472
|
# 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
|
1365
1473
|
# @return [nil | Unit]
|
1366
1474
|
# @todo This should either be a separate class or at least a class method
|
1367
|
-
def parse(passed_unit_string=
|
1475
|
+
def parse(passed_unit_string = '0')
|
1368
1476
|
unit_string = passed_unit_string.dup
|
1369
|
-
if unit_string =~ /\$\s*(#{NUMBER_REGEX})/
|
1370
|
-
unit_string = "#{$1} USD"
|
1371
|
-
end
|
1477
|
+
unit_string = "#{$1} USD" if unit_string =~ /\$\s*(#{NUMBER_REGEX})/
|
1372
1478
|
unit_string.gsub!("\u00b0".force_encoding('utf-8'), 'deg') if unit_string.encoding == Encoding::UTF_8
|
1373
1479
|
|
1374
|
-
unit_string.gsub!(
|
1375
|
-
unit_string.gsub!(/'/, 'feet')
|
1376
|
-
unit_string.gsub!(/"/, 'inch')
|
1377
|
-
unit_string.gsub!(/#/, 'pound')
|
1480
|
+
unit_string.gsub!(/[%'"#]/, '%' => 'percent', "'" => 'feet', '"' => 'inch', '#' => 'pound')
|
1378
1481
|
|
1379
1482
|
if defined?(Complex) && unit_string =~ COMPLEX_NUMBER
|
1380
1483
|
real, imaginary, unit_s = unit_string.scan(COMPLEX_REGEX)[0]
|
@@ -1385,9 +1488,9 @@ module RubyUnits
|
|
1385
1488
|
|
1386
1489
|
if defined?(Rational) && unit_string =~ RATIONAL_NUMBER
|
1387
1490
|
sign, proper, numerator, denominator, unit_s = unit_string.scan(RATIONAL_REGEX)[0]
|
1388
|
-
sign =
|
1491
|
+
sign = sign == '-' ? -1 : 1
|
1389
1492
|
rational = sign * (proper.to_i + Rational(numerator.to_i, denominator.to_i))
|
1390
|
-
result
|
1493
|
+
result = RubyUnits::Unit.new(unit_s || '1') * rational
|
1391
1494
|
copy(result)
|
1392
1495
|
return
|
1393
1496
|
end
|
@@ -1395,7 +1498,7 @@ module RubyUnits
|
|
1395
1498
|
unit_string =~ NUMBER_REGEX
|
1396
1499
|
unit = @@cached_units[$2]
|
1397
1500
|
mult = ($1.empty? ? 1.0 : $1.to_f) rescue 1.0
|
1398
|
-
mult = mult.to_int if
|
1501
|
+
mult = mult.to_int if mult.to_int == mult
|
1399
1502
|
if unit
|
1400
1503
|
copy(unit)
|
1401
1504
|
@scalar *= mult
|
@@ -1403,15 +1506,15 @@ module RubyUnits
|
|
1403
1506
|
return self
|
1404
1507
|
end
|
1405
1508
|
|
1406
|
-
while unit_string.gsub!
|
1509
|
+
while unit_string.gsub!(/(<#{@@unit_regex})><(#{@@unit_regex}>)/, '\1*\2')
|
1407
1510
|
# collapse <x><y><z> into <x*y*z>...
|
1408
1511
|
end
|
1409
1512
|
# ... and then strip the remaining brackets for x*y*z
|
1410
|
-
unit_string.gsub!(/[<>]/,
|
1513
|
+
unit_string.gsub!(/[<>]/, '')
|
1411
1514
|
|
1412
1515
|
if unit_string =~ /:/
|
1413
1516
|
hours, minutes, seconds, microseconds = unit_string.scan(TIME_REGEX)[0]
|
1414
|
-
raise ArgumentError,
|
1517
|
+
raise ArgumentError, 'Invalid Duration' if [hours, minutes, seconds, microseconds].all?(&:nil?)
|
1415
1518
|
result = RubyUnits::Unit.new("#{hours || 0} h") +
|
1416
1519
|
RubyUnits::Unit.new("#{minutes || 0} minutes") +
|
1417
1520
|
RubyUnits::Unit.new("#{seconds || 0} seconds") +
|
@@ -1423,7 +1526,7 @@ module RubyUnits
|
|
1423
1526
|
# Special processing for unusual unit strings
|
1424
1527
|
# feet -- 6'5"
|
1425
1528
|
feet, inches = unit_string.scan(FEET_INCH_REGEX)[0]
|
1426
|
-
if
|
1529
|
+
if feet && inches
|
1427
1530
|
result = RubyUnits::Unit.new("#{feet} ft") + RubyUnits::Unit.new("#{inches} inches")
|
1428
1531
|
copy(result)
|
1429
1532
|
return
|
@@ -1431,164 +1534,74 @@ module RubyUnits
|
|
1431
1534
|
|
1432
1535
|
# weight -- 8 lbs 12 oz
|
1433
1536
|
pounds, oz = unit_string.scan(LBS_OZ_REGEX)[0]
|
1434
|
-
if
|
1537
|
+
if pounds && oz
|
1435
1538
|
result = RubyUnits::Unit.new("#{pounds} lbs") + RubyUnits::Unit.new("#{oz} oz")
|
1436
1539
|
copy(result)
|
1437
1540
|
return
|
1438
1541
|
end
|
1439
1542
|
|
1543
|
+
# stone -- 3 stone 5, 2 stone, 14 stone 3 pounds, etc.
|
1544
|
+
stone, pounds = unit_string.scan(STONE_LB_REGEX)[0]
|
1545
|
+
if stone && pounds
|
1546
|
+
result = RubyUnits::Unit.new("#{stone} stone") + RubyUnits::Unit.new("#{pounds} lbs")
|
1547
|
+
copy(result)
|
1548
|
+
return
|
1549
|
+
end
|
1550
|
+
|
1440
1551
|
# more than one per. I.e., "1 m/s/s"
|
1441
1552
|
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.count('/') > 1
|
1442
|
-
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string
|
1443
|
-
@scalar, top, bottom = unit_string.scan(UNIT_STRING_REGEX)[0] #parse the string into parts
|
1553
|
+
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string =~ /\s[02-9]/
|
1554
|
+
@scalar, top, bottom = unit_string.scan(UNIT_STRING_REGEX)[0] # parse the string into parts
|
1444
1555
|
top.scan(TOP_REGEX).each do |item|
|
1445
1556
|
n = item[1].to_i
|
1446
1557
|
x = "#{item[0]} "
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
1558
|
+
if n >= 0
|
1559
|
+
top.gsub!(/#{item[0]}(\^|\*\*)#{n}/) { x * n }
|
1560
|
+
elsif n < 0
|
1561
|
+
bottom = "#{bottom} #{x * -n}"
|
1562
|
+
top.gsub!(/#{item[0]}(\^|\*\*)#{n}/, '')
|
1452
1563
|
end
|
1453
1564
|
end
|
1454
1565
|
if bottom
|
1455
|
-
bottom.gsub!(BOTTOM_REGEX) {
|
1566
|
+
bottom.gsub!(BOTTOM_REGEX) { "#{$1} " * $2.to_i }
|
1456
1567
|
# Separate leading decimal from denominator, if any
|
1457
1568
|
bottom_scalar, bottom = bottom.scan(NUMBER_UNIT_REGEX)[0]
|
1458
1569
|
end
|
1459
1570
|
|
1460
1571
|
@scalar = @scalar.to_f unless @scalar.nil? || @scalar.empty?
|
1461
|
-
@scalar = 1 unless @scalar.
|
1462
|
-
@scalar = @scalar.to_int if
|
1572
|
+
@scalar = 1 unless @scalar.is_a? Numeric
|
1573
|
+
@scalar = @scalar.to_int if @scalar.to_int == @scalar
|
1463
1574
|
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
1468
|
-
|
1469
|
-
|
1470
|
-
end
|
1575
|
+
bottom_scalar = 1 if bottom_scalar.nil? || bottom_scalar.empty?
|
1576
|
+
bottom_scalar = if bottom_scalar.to_i == bottom_scalar
|
1577
|
+
bottom_scalar.to_i
|
1578
|
+
else
|
1579
|
+
bottom_scalar.to_f
|
1580
|
+
end
|
1471
1581
|
|
1582
|
+
@scalar /= bottom_scalar
|
1472
1583
|
|
1473
1584
|
@numerator ||= UNITY_ARRAY
|
1474
1585
|
@denominator ||= UNITY_ARRAY
|
1475
|
-
@numerator = top.scan(RubyUnits::Unit.unit_match_regex).delete_if
|
1476
|
-
@denominator = bottom.scan(RubyUnits::Unit.unit_match_regex).delete_if
|
1586
|
+
@numerator = top.scan(RubyUnits::Unit.unit_match_regex).delete_if(&:empty?).compact if top
|
1587
|
+
@denominator = bottom.scan(RubyUnits::Unit.unit_match_regex).delete_if(&:empty?).compact if bottom
|
1477
1588
|
|
1478
1589
|
# eliminate all known terms from this string. This is a quick check to see if the passed unit
|
1479
1590
|
# contains terms that are not defined.
|
1480
|
-
used = "#{top} #{bottom}".to_s.gsub(RubyUnits::Unit.unit_match_regex, '').gsub(
|
1591
|
+
used = "#{top} #{bottom}".to_s.gsub(RubyUnits::Unit.unit_match_regex, '').gsub(%r{[\d\*, "'_^\/\$]}, '')
|
1481
1592
|
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") unless used.empty?
|
1482
1593
|
|
1483
1594
|
@numerator = @numerator.map do |item|
|
1484
|
-
@@
|
1485
|
-
end.flatten.compact.delete_if
|
1595
|
+
@@prefix_map[item[0]] ? [@@prefix_map[item[0]], @@unit_map[item[1]]] : [@@unit_map[item[1]]]
|
1596
|
+
end.flatten.compact.delete_if(&:empty?)
|
1486
1597
|
|
1487
1598
|
@denominator = @denominator.map do |item|
|
1488
|
-
@@
|
1489
|
-
end.flatten.compact.delete_if
|
1599
|
+
@@prefix_map[item[0]] ? [@@prefix_map[item[0]], @@unit_map[item[1]]] : [@@unit_map[item[1]]]
|
1600
|
+
end.flatten.compact.delete_if(&:empty?)
|
1490
1601
|
|
1491
1602
|
@numerator = UNITY_ARRAY if @numerator.empty?
|
1492
1603
|
@denominator = UNITY_ARRAY if @denominator.empty?
|
1493
|
-
|
1494
|
-
end
|
1495
|
-
|
1496
|
-
# return an array of base units
|
1497
|
-
# @return [Array]
|
1498
|
-
def self.base_units
|
1499
|
-
return @@base_units ||= @@definitions.dup.delete_if { |_, defn| !defn.base? }.keys.map { |u| RubyUnits::Unit.new(u) }
|
1500
|
-
end
|
1501
|
-
|
1502
|
-
# parse a string consisting of a number and a unit string
|
1503
|
-
# NOTE: This does not properly handle units formatted like '12mg/6ml'
|
1504
|
-
# @param [String] string
|
1505
|
-
# @return [Array] consisting of [Numeric, "unit"]
|
1506
|
-
# @private
|
1507
|
-
def self.parse_into_numbers_and_units(string)
|
1508
|
-
# scientific notation.... 123.234E22, -123.456e-10
|
1509
|
-
sci = %r{[+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*}
|
1510
|
-
# rational numbers.... -1/3, 1/5, 20/100, -6 1/2, -6-1/2
|
1511
|
-
rational = %r{\(?[+-]?(?:\d+[ -])?\d+\/\d+\)?}
|
1512
|
-
# complex numbers... -1.2+3i, +1.2-3.3i
|
1513
|
-
complex = %r{#{sci}{2,2}i}
|
1514
|
-
anynumber = %r{(?:(#{complex}|#{rational}|#{sci}))?\s?([^-\d\.].*)?}
|
1515
|
-
|
1516
|
-
num, unit = string.scan(anynumber).first
|
1517
|
-
|
1518
|
-
return [case num
|
1519
|
-
when NilClass
|
1520
|
-
1
|
1521
|
-
when complex
|
1522
|
-
if num.respond_to?(:to_c)
|
1523
|
-
num.to_c
|
1524
|
-
else
|
1525
|
-
#:nocov_19:
|
1526
|
-
Complex(*num.scan(/(#{sci})(#{sci})i/).flatten.map { |n| n.to_i })
|
1527
|
-
#:nocov_19:
|
1528
|
-
end
|
1529
|
-
when rational
|
1530
|
-
# if it has whitespace, it will be of the form '6 1/2'
|
1531
|
-
if num =~ RATIONAL_NUMBER
|
1532
|
-
sign = ($1 == '-') ? -1 : 1
|
1533
|
-
n = $2.to_i
|
1534
|
-
f = Rational($3.to_i,$4.to_i)
|
1535
|
-
sign * (n + f)
|
1536
|
-
else
|
1537
|
-
Rational(*num.split("/").map { |x| x.to_i })
|
1538
|
-
end
|
1539
|
-
else
|
1540
|
-
num.to_f
|
1541
|
-
end, unit.to_s.strip]
|
1542
|
-
end
|
1543
|
-
|
1544
|
-
# return a fragment of a regex to be used for matching units or reconstruct it if hasn't been used yet.
|
1545
|
-
# Unit names are reverse sorted by length so the regexp matcher will prefer longer and more specific names
|
1546
|
-
# @return [String]
|
1547
|
-
# @private
|
1548
|
-
def self.unit_regex
|
1549
|
-
@@UNIT_REGEX ||= @@UNIT_MAP.keys.sort_by { |unit_name| [unit_name.length, unit_name] }.reverse.join('|')
|
1550
|
-
end
|
1551
|
-
|
1552
|
-
# return a regex used to match units
|
1553
|
-
# @return [RegExp]
|
1554
|
-
# @private
|
1555
|
-
def self.unit_match_regex
|
1556
|
-
@@UNIT_MATCH_REGEX ||= /(#{RubyUnits::Unit.prefix_regex})??(#{RubyUnits::Unit.unit_regex})\b/
|
1557
|
-
end
|
1558
|
-
|
1559
|
-
# return a regexp fragment used to match prefixes
|
1560
|
-
# @return [String]
|
1561
|
-
# @private
|
1562
|
-
def self.prefix_regex
|
1563
|
-
return @@PREFIX_REGEX ||= @@PREFIX_MAP.keys.sort_by { |prefix| [prefix.length, prefix] }.reverse.join('|')
|
1564
|
-
end
|
1565
|
-
|
1566
|
-
def self.temp_regex
|
1567
|
-
@@TEMP_REGEX ||= Regexp.new "(?:#{
|
1568
|
-
temp_units=%w(tempK tempC tempF tempR degK degC degF degR)
|
1569
|
-
aliases =temp_units.map { |unit| d=RubyUnits::Unit.definition(unit); d && d.aliases }.flatten.compact
|
1570
|
-
regex_str = aliases.empty? ? '(?!x)x' : aliases.join('|')
|
1571
|
-
regex_str
|
1572
|
-
})"
|
1573
|
-
end
|
1574
|
-
|
1575
|
-
# inject a definition into the internal array and set it up for use
|
1576
|
-
# @private
|
1577
|
-
def self.use_definition(definition)
|
1578
|
-
@@UNIT_MATCH_REGEX = nil #invalidate the unit match regex
|
1579
|
-
@@TEMP_REGEX = nil #invalidate the temp regex
|
1580
|
-
if definition.prefix?
|
1581
|
-
@@PREFIX_VALUES[definition.name] = definition.scalar
|
1582
|
-
definition.aliases.each { |_alias| @@PREFIX_MAP[_alias] = definition.name }
|
1583
|
-
@@PREFIX_REGEX = nil #invalidate the prefix regex
|
1584
|
-
else
|
1585
|
-
@@UNIT_VALUES[definition.name] = {}
|
1586
|
-
@@UNIT_VALUES[definition.name][:scalar] = definition.scalar
|
1587
|
-
@@UNIT_VALUES[definition.name][:numerator] = definition.numerator if definition.numerator
|
1588
|
-
@@UNIT_VALUES[definition.name][:denominator] = definition.denominator if definition.denominator
|
1589
|
-
definition.aliases.each { |_alias| @@UNIT_MAP[_alias] = definition.name }
|
1590
|
-
@@UNIT_REGEX = nil #invalidate the unit regex
|
1591
|
-
end
|
1604
|
+
self
|
1592
1605
|
end
|
1593
1606
|
end
|
1594
1607
|
end
|