ruby-units 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|