ruby-units 2.3.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +16 -0
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/tests.yml +10 -1
- data/.ruby-version +1 -1
- data/.solargraph.yml +1 -1
- data/.tool-versions +3 -0
- data/CHANGELOG.txt +4 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +87 -67
- data/lib/ruby-units.rb +0 -1
- data/lib/ruby_units/array.rb +17 -7
- data/lib/ruby_units/cache.rb +27 -14
- data/lib/ruby_units/configuration.rb +12 -11
- data/lib/ruby_units/date.rb +46 -53
- data/lib/ruby_units/definition.rb +6 -2
- data/lib/ruby_units/math.rb +136 -113
- data/lib/ruby_units/namespaced.rb +0 -3
- data/lib/ruby_units/numeric.rb +21 -5
- data/lib/ruby_units/string.rb +34 -21
- data/lib/ruby_units/time.rb +76 -69
- data/lib/ruby_units/unit.rb +408 -377
- data/lib/ruby_units/version.rb +1 -1
- data/ruby-units.gemspec +4 -2
- metadata +40 -9
data/lib/ruby_units/unit.rb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
require 'date'
|
2
|
-
# Copyright 2006-2015
|
3
|
-
# @author Kevin C. Olbrich, Ph.D.
|
4
|
-
# @see https://github.com/olbrich/ruby-units
|
5
|
-
#
|
6
|
-
# @note The accuracy of unit conversions depends on the precision of the conversion factor.
|
7
|
-
# If you have more accurate estimates for particular conversion factors, please send them
|
8
|
-
# to me and I will incorporate them into the next release. It is also incumbent on the end-user
|
9
|
-
# to ensure that the accuracy of any conversions is sufficient for their intended application.
|
10
|
-
#
|
11
|
-
# While there are a large number of unit specified in the base package,
|
12
|
-
# there are also a large number of units that are not included.
|
13
|
-
# This package covers nearly all SI, Imperial, and units commonly used
|
14
|
-
# in the United States. If your favorite units are not listed here, file an issue on github.
|
15
|
-
#
|
16
|
-
# To add or override a unit definition, add a code block like this..
|
17
|
-
# @example Define a new unit
|
18
|
-
# RubyUnits::Unit.define("foobar") do |unit|
|
19
|
-
# unit.aliases = %w{foo fb foo-bar}
|
20
|
-
# unit.definition = RubyUnits::Unit.new("1 baz")
|
21
|
-
# end
|
22
|
-
#
|
23
2
|
module RubyUnits
|
24
|
-
|
3
|
+
# Copyright 2006-2022
|
4
|
+
# @author Kevin C. Olbrich, Ph.D.
|
5
|
+
# @see https://github.com/olbrich/ruby-units
|
6
|
+
#
|
7
|
+
# @note The accuracy of unit conversions depends on the precision of the conversion factor.
|
8
|
+
# If you have more accurate estimates for particular conversion factors, please send them
|
9
|
+
# to me and I will incorporate them into the next release. It is also incumbent on the end-user
|
10
|
+
# to ensure that the accuracy of any conversions is sufficient for their intended application.
|
11
|
+
#
|
12
|
+
# While there are a large number of unit specified in the base package,
|
13
|
+
# there are also a large number of units that are not included.
|
14
|
+
# This package covers nearly all SI, Imperial, and units commonly used
|
15
|
+
# in the United States. If your favorite units are not listed here, file an issue on GitHub.
|
16
|
+
#
|
17
|
+
# To add or override a unit definition, add a code block like this..
|
18
|
+
# @example Define a new unit
|
19
|
+
# RubyUnits::Unit.define("foobar") do |unit|
|
20
|
+
# unit.aliases = %w{foo fb foo-bar}
|
21
|
+
# unit.definition = RubyUnits::Unit.new("1 baz")
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
class Unit < ::Numeric
|
25
25
|
@@definitions = {}
|
26
26
|
@@prefix_values = {}
|
27
27
|
@@prefix_map = {}
|
@@ -31,30 +31,41 @@ module RubyUnits
|
|
31
31
|
@@unit_match_regex = nil
|
32
32
|
UNITY = '<1>'.freeze
|
33
33
|
UNITY_ARRAY = [UNITY].freeze
|
34
|
-
# ideally we would like to generate this regex from the alias for a 'feet'
|
35
|
-
# defined at the point in the code where we
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
34
|
+
# ideally we would like to generate this regex from the alias for a 'feet'
|
35
|
+
# and 'inches', but they aren't defined at the point in the code where we
|
36
|
+
# need this regex.
|
37
|
+
FEET_INCH_UNITS_REGEX = /(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inch(?:es)?)/.freeze
|
38
|
+
FEET_INCH_REGEX = /(\d+)\s*#{FEET_INCH_UNITS_REGEX}/.freeze
|
39
|
+
# ideally we would like to generate this regex from the alias for a 'pound'
|
40
|
+
# and 'ounce', but they aren't defined at the point in the code where we
|
41
|
+
# need this regex.
|
42
|
+
LBS_OZ_UNIT_REGEX = /(?:#|lbs?|pounds?|pound-mass)+[\s,]*(\d+)\s*(?:ozs?|ounces?)/.freeze
|
43
|
+
LBS_OZ_REGEX = /(\d+)\s*#{LBS_OZ_UNIT_REGEX}/.freeze
|
44
|
+
# ideally we would like to generate this regex from the alias for a 'stone'
|
45
|
+
# and 'pound', but they aren't defined at the point in the code where we
|
46
|
+
# need this regex. also note that the plural of 'stone' is still 'stone',
|
47
|
+
# but we accept 'stones' anyway.
|
48
|
+
STONE_LB_UNIT_REGEX = /(?:sts?|stones?)+[\s,]*(\d+)\s*(?:#|lbs?|pounds?|pound-mass)*/.freeze
|
49
|
+
STONE_LB_REGEX = /(\d+)\s*#{STONE_LB_UNIT_REGEX}/.freeze
|
50
|
+
# Time formats: 12:34:56,78, (hh:mm:ss,msec) etc.
|
51
|
+
TIME_REGEX = /(?<hour>\d+):(?<min>\d+):(?:(?<sec>\d+))?(?:,(?<msec>\d+))?/.freeze
|
52
|
+
# Scientific notation: 1, -1, +1, 1.2, +1.2, -1.2, 123.4E5, +123.4e5,
|
53
|
+
# -123.4E+5, -123.4e-5, etc.
|
54
|
+
SCI_NUMBER = /([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)/.freeze
|
55
|
+
# Rational number, including improper fractions: 1 2/3, -1 2/3, 5/3, etc.
|
56
|
+
RATIONAL_NUMBER = %r{\(?([+-])?(\d+[ -])?(\d+)/(\d+)\)?}.freeze
|
57
|
+
# Complex numbers: 1+2i, 1.0+2.0i, -1-1i, etc.
|
58
|
+
COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/.freeze
|
59
|
+
# Any Complex, Rational, or scientific number
|
60
|
+
ANY_NUMBER = /(#{COMPLEX_NUMBER}|#{RATIONAL_NUMBER}|#{SCI_NUMBER})/.freeze
|
61
|
+
ANY_NUMBER_REGEX = /(?:#{ANY_NUMBER})?\s?([^-\d.].*)?/.freeze
|
62
|
+
NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/.freeze
|
63
|
+
UNIT_STRING_REGEX = %r{#{SCI_NUMBER}*\s*([^/]*)/*(.+)*}.freeze
|
64
|
+
TOP_REGEX = /([^ *]+)(?:\^|\*\*)([\d-]+)/.freeze
|
65
|
+
BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/.freeze
|
66
|
+
NUMBER_UNIT_REGEX = /#{SCI_NUMBER}?(.*)/.freeze
|
67
|
+
COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/.freeze
|
68
|
+
RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(.+)?/.freeze
|
58
69
|
KELVIN = ['<kelvin>'].freeze
|
59
70
|
FAHRENHEIT = ['<fahrenheit>'].freeze
|
60
71
|
RANKINE = ['<rankine>'].freeze
|
@@ -73,62 +84,60 @@ module RubyUnits
|
|
73
84
|
angle
|
74
85
|
].freeze
|
75
86
|
@@kinds = {
|
76
|
-
-312_078
|
77
|
-
-312_058
|
78
|
-
-312_038
|
79
|
-
-152_040
|
80
|
-
-152_038
|
81
|
-
-152_058
|
82
|
-
-7997
|
83
|
-
-79
|
84
|
-
-59
|
85
|
-
-39
|
86
|
-
-38
|
87
|
-
-20
|
88
|
-
-19
|
89
|
-
-18
|
90
|
-
-17
|
91
|
-
-1
|
92
|
-
0
|
93
|
-
1
|
94
|
-
2
|
95
|
-
3
|
96
|
-
20
|
97
|
-
400
|
98
|
-
7941
|
99
|
-
7942
|
100
|
-
7959
|
101
|
-
7962
|
102
|
-
7979
|
103
|
-
7961
|
104
|
-
7981
|
105
|
-
7982
|
106
|
-
7997
|
107
|
-
7998
|
108
|
-
8000
|
109
|
-
152_020
|
110
|
-
159_999
|
111
|
-
160_000
|
112
|
-
160_020
|
113
|
-
312_058
|
114
|
-
312_078
|
115
|
-
3_199_980
|
116
|
-
3_199_997
|
117
|
-
3_200_000
|
118
|
-
63_999_998
|
119
|
-
64_000_000
|
87
|
+
-312_078 => :elastance,
|
88
|
+
-312_058 => :resistance,
|
89
|
+
-312_038 => :inductance,
|
90
|
+
-152_040 => :magnetism,
|
91
|
+
-152_038 => :magnetism,
|
92
|
+
-152_058 => :potential,
|
93
|
+
-7997 => :specific_volume,
|
94
|
+
-79 => :snap,
|
95
|
+
-59 => :jolt,
|
96
|
+
-39 => :acceleration,
|
97
|
+
-38 => :radiation,
|
98
|
+
-20 => :frequency,
|
99
|
+
-19 => :speed,
|
100
|
+
-18 => :viscosity,
|
101
|
+
-17 => :volumetric_flow,
|
102
|
+
-1 => :wavenumber,
|
103
|
+
0 => :unitless,
|
104
|
+
1 => :length,
|
105
|
+
2 => :area,
|
106
|
+
3 => :volume,
|
107
|
+
20 => :time,
|
108
|
+
400 => :temperature,
|
109
|
+
7941 => :yank,
|
110
|
+
7942 => :power,
|
111
|
+
7959 => :pressure,
|
112
|
+
7962 => :energy,
|
113
|
+
7979 => :viscosity,
|
114
|
+
7961 => :force,
|
115
|
+
7981 => :momentum,
|
116
|
+
7982 => :angular_momentum,
|
117
|
+
7997 => :density,
|
118
|
+
7998 => :area_density,
|
119
|
+
8000 => :mass,
|
120
|
+
152_020 => :radiation_exposure,
|
121
|
+
159_999 => :magnetism,
|
122
|
+
160_000 => :current,
|
123
|
+
160_020 => :charge,
|
124
|
+
312_058 => :conductance,
|
125
|
+
312_078 => :capacitance,
|
126
|
+
3_199_980 => :activity,
|
127
|
+
3_199_997 => :molar_concentration,
|
128
|
+
3_200_000 => :substance,
|
129
|
+
63_999_998 => :illuminance,
|
130
|
+
64_000_000 => :luminous_power,
|
120
131
|
1_280_000_000 => :currency,
|
121
|
-
25_600_000_000
|
132
|
+
25_600_000_000 => :information,
|
122
133
|
511_999_999_980 => :angular_velocity,
|
123
134
|
512_000_000_000 => :angle
|
124
135
|
}.freeze
|
125
|
-
@@cached_units = {}
|
126
|
-
@@base_unit_cache = {}
|
127
136
|
|
128
137
|
# Class Methods
|
129
138
|
|
130
139
|
# setup internal arrays and hashes
|
131
|
-
# @return [
|
140
|
+
# @return [Boolean]
|
132
141
|
def self.setup
|
133
142
|
clear_cache
|
134
143
|
@@prefix_values = {}
|
@@ -139,11 +148,11 @@ module RubyUnits
|
|
139
148
|
@@unit_match_regex = nil
|
140
149
|
@@prefix_regex = nil
|
141
150
|
|
142
|
-
@@definitions.
|
151
|
+
@@definitions.each_value do |definition|
|
143
152
|
use_definition(definition)
|
144
153
|
end
|
145
154
|
|
146
|
-
|
155
|
+
new(1)
|
147
156
|
true
|
148
157
|
end
|
149
158
|
|
@@ -155,7 +164,7 @@ module RubyUnits
|
|
155
164
|
end
|
156
165
|
|
157
166
|
# return the unit definition for a unit
|
158
|
-
# @param [String]
|
167
|
+
# @param unit_name [String]
|
159
168
|
# @return [RubyUnits::Unit::Definition, nil]
|
160
169
|
def self.definition(unit_name)
|
161
170
|
unit = unit_name =~ /^<.+>$/ ? unit_name : "<#{unit_name}>"
|
@@ -163,13 +172,13 @@ module RubyUnits
|
|
163
172
|
end
|
164
173
|
|
165
174
|
# return a list of all defined units
|
166
|
-
# @return [Array]
|
175
|
+
# @return [Array<RubyUnits::Units::Definition>]
|
167
176
|
def self.definitions
|
168
177
|
@@definitions
|
169
178
|
end
|
170
179
|
|
171
|
-
# @param [RubyUnits::Unit::Definition
|
172
|
-
# @param [
|
180
|
+
# @param [RubyUnits::Unit::Definition, String] unit_definition
|
181
|
+
# @param [Proc] block
|
173
182
|
# @return [RubyUnits::Unit::Definition]
|
174
183
|
# @raise [ArgumentError] when passed a non-string if using the block form
|
175
184
|
# Unpack a unit definition and add it to the array of defined units
|
@@ -184,54 +193,62 @@ module RubyUnits
|
|
184
193
|
# RubyUnits::Unit.define(unit_definition)
|
185
194
|
def self.define(unit_definition, &block)
|
186
195
|
if block_given?
|
187
|
-
raise ArgumentError, 'When using the block form of RubyUnits::Unit.define, pass the name of the unit' unless unit_definition.
|
196
|
+
raise ArgumentError, 'When using the block form of RubyUnits::Unit.define, pass the name of the unit' unless unit_definition.is_a?(String)
|
197
|
+
|
188
198
|
unit_definition = RubyUnits::Unit::Definition.new(unit_definition, &block)
|
189
199
|
end
|
190
|
-
|
191
|
-
|
200
|
+
definitions[unit_definition.name] = unit_definition
|
201
|
+
use_definition(unit_definition)
|
192
202
|
unit_definition
|
193
203
|
end
|
194
204
|
|
205
|
+
# Get the definition for a unit and allow it to be redefined
|
206
|
+
#
|
195
207
|
# @param [String] name Name of unit to redefine
|
196
|
-
# @param [
|
208
|
+
# @param [Proc] _block
|
197
209
|
# @raise [ArgumentError] if a block is not given
|
198
|
-
# @
|
210
|
+
# @yieldparam [RubyUnits::Unit::Definition] the definition of the unit being
|
211
|
+
# redefined
|
199
212
|
# @return (see RubyUnits::Unit.define)
|
200
|
-
|
201
|
-
def self.redefine!(name)
|
213
|
+
def self.redefine!(name, &_block)
|
202
214
|
raise ArgumentError, 'A block is required to redefine a unit' unless block_given?
|
215
|
+
|
203
216
|
unit_definition = definition(name)
|
204
217
|
raise(ArgumentError, "'#{name}' Unit not recognized") unless unit_definition
|
218
|
+
|
205
219
|
yield unit_definition
|
206
220
|
@@definitions.delete("<#{name}>")
|
207
221
|
define(unit_definition)
|
208
|
-
|
222
|
+
setup
|
209
223
|
end
|
210
224
|
|
211
|
-
# @param [String] name of unit to undefine
|
212
|
-
# @return (see RubyUnits::Unit.setup)
|
213
225
|
# Undefine a unit. Will not raise an exception for unknown units.
|
226
|
+
#
|
227
|
+
# @param unit [String] name of unit to undefine
|
228
|
+
# @return (see RubyUnits::Unit.setup)
|
214
229
|
def self.undefine!(unit)
|
215
230
|
@@definitions.delete("<#{unit}>")
|
216
|
-
|
231
|
+
setup
|
217
232
|
end
|
218
233
|
|
219
|
-
#
|
234
|
+
# Unit cache
|
235
|
+
#
|
236
|
+
# @return [RubyUnits::Cache]
|
220
237
|
def self.cached
|
221
|
-
|
238
|
+
@cached ||= RubyUnits::Cache.new
|
222
239
|
end
|
223
240
|
|
224
|
-
# @return [
|
241
|
+
# @return [Boolean]
|
225
242
|
def self.clear_cache
|
226
|
-
|
227
|
-
|
228
|
-
|
243
|
+
cached.clear
|
244
|
+
base_unit_cache.clear
|
245
|
+
new(1)
|
229
246
|
true
|
230
247
|
end
|
231
248
|
|
232
|
-
# @return [
|
249
|
+
# @return [RubyUnits::Cache]
|
233
250
|
def self.base_unit_cache
|
234
|
-
|
251
|
+
@base_unit_cache ||= RubyUnits::Cache.new
|
235
252
|
end
|
236
253
|
|
237
254
|
# @example parse strings
|
@@ -240,104 +257,75 @@ module RubyUnits
|
|
240
257
|
# @return [Unit]
|
241
258
|
def self.parse(input)
|
242
259
|
first, second = input.scan(/(.+)\s(?:in|to|as)\s(.+)/i).first
|
243
|
-
second.nil? ?
|
260
|
+
second.nil? ? new(first) : new(first).convert_to(second)
|
244
261
|
end
|
245
262
|
|
246
|
-
# @param [Numeric]
|
247
|
-
# @param [Array]
|
248
|
-
# @param [Array]
|
263
|
+
# @param q [Numeric] quantity
|
264
|
+
# @param n [Array] numerator
|
265
|
+
# @param d [Array] denominator
|
249
266
|
# @return [Hash]
|
250
267
|
def self.eliminate_terms(q, n, d)
|
251
268
|
num = n.dup
|
252
269
|
den = d.dup
|
270
|
+
num.delete(UNITY)
|
271
|
+
den.delete(UNITY)
|
253
272
|
|
254
|
-
|
255
|
-
den.delete_if { |v| v == UNITY }
|
256
|
-
combined = Hash.new(0)
|
257
|
-
|
258
|
-
i = 0
|
259
|
-
loop do
|
260
|
-
break if i > num.size
|
261
|
-
if @@prefix_values.key? num[i]
|
262
|
-
k = [num[i], num[i + 1]]
|
263
|
-
i += 2
|
264
|
-
else
|
265
|
-
k = num[i]
|
266
|
-
i += 1
|
267
|
-
end
|
268
|
-
combined[k] += 1 unless k.nil? || k == UNITY
|
269
|
-
end
|
273
|
+
combined = ::Hash.new(0)
|
270
274
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
k = [den[j], den[j + 1]]
|
276
|
-
j += 2
|
277
|
-
else
|
278
|
-
k = den[j]
|
279
|
-
j += 1
|
280
|
-
end
|
281
|
-
combined[k] -= 1 unless k.nil? || k == UNITY
|
275
|
+
[[num, 1], [den, -1]].each do |array, increment|
|
276
|
+
array.chunk_while { |elt_before, _| definition(elt_before).prefix? }
|
277
|
+
.to_a
|
278
|
+
.each { |unit| combined[unit] += increment }
|
282
279
|
end
|
283
280
|
|
284
281
|
num = []
|
285
282
|
den = []
|
286
283
|
combined.each do |key, value|
|
287
|
-
if value
|
284
|
+
if value.positive?
|
288
285
|
value.times { num << key }
|
289
|
-
elsif value
|
286
|
+
elsif value.negative?
|
290
287
|
value.abs.times { den << key }
|
291
288
|
end
|
292
289
|
end
|
293
290
|
num = UNITY_ARRAY if num.empty?
|
294
291
|
den = UNITY_ARRAY if den.empty?
|
295
|
-
{ scalar: q, numerator: num.flatten
|
292
|
+
{ scalar: q, numerator: num.flatten, denominator: den.flatten }
|
293
|
+
end
|
294
|
+
|
295
|
+
# Creates a new unit from the current one with all common terms eliminated.
|
296
|
+
#
|
297
|
+
# @return [RubyUnits::Unit]
|
298
|
+
def eliminate_terms
|
299
|
+
self.class.new(self.class.eliminate_terms(@scalar, @numerator, @denominator))
|
296
300
|
end
|
297
301
|
|
298
302
|
# return an array of base units
|
299
303
|
# @return [Array]
|
300
304
|
def self.base_units
|
301
|
-
@@base_units ||= @@definitions.dup.
|
305
|
+
@@base_units ||= @@definitions.dup.select { |_, definition| definition.base? }.keys.map { |u| new(u) }
|
302
306
|
end
|
303
307
|
|
304
|
-
#
|
308
|
+
# Parse a string consisting of a number and a unit string
|
305
309
|
# NOTE: This does not properly handle units formatted like '12mg/6ml'
|
310
|
+
#
|
306
311
|
# @param [String] string
|
307
|
-
# @return [Array] consisting of [
|
312
|
+
# @return [Array(Numeric, String)] consisting of [number, "unit"]
|
308
313
|
def self.parse_into_numbers_and_units(string)
|
309
|
-
|
310
|
-
sci = /[+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*/
|
311
|
-
# rational numbers.... -1/3, 1/5, 20/100, -6 1/2, -6-1/2
|
312
|
-
rational = %r{\(?[+-]?(?:\d+[ -])?\d+\/\d+\)?}
|
313
|
-
# complex numbers... -1.2+3i, +1.2-3.3i
|
314
|
-
complex = /#{sci}{2,2}i/
|
315
|
-
anynumber = /(?:(#{complex}|#{rational}|#{sci}))?\s?([^-\d\.].*)?/
|
316
|
-
|
317
|
-
num, unit = string.scan(anynumber).first
|
314
|
+
num, unit = string.scan(ANY_NUMBER_REGEX).first
|
318
315
|
|
319
316
|
[
|
320
317
|
case num
|
321
|
-
when
|
318
|
+
when nil # This happens when no number is passed and we are parsing a pure unit string
|
322
319
|
1
|
323
|
-
when
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
# if it has whitespace, it will be of the form '6 1/2'
|
333
|
-
if num =~ RATIONAL_NUMBER
|
334
|
-
sign = Regexp.last_match(1) == '-' ? -1 : 1
|
335
|
-
n = Regexp.last_match(2).to_i
|
336
|
-
f = Rational(Regexp.last_match(3).to_i, Regexp.last_match(4).to_i)
|
337
|
-
sign * (n + f)
|
338
|
-
else
|
339
|
-
Rational(*num.split('/').map(&:to_i))
|
340
|
-
end
|
320
|
+
when COMPLEX_NUMBER
|
321
|
+
num.to_c
|
322
|
+
when RATIONAL_NUMBER
|
323
|
+
# We use this method instead of relying on `to_r` because it does not
|
324
|
+
# handle improper fractions correctly.
|
325
|
+
sign = Regexp.last_match(1) == '-' ? -1 : 1
|
326
|
+
n = Regexp.last_match(2).to_i
|
327
|
+
f = Rational(Regexp.last_match(3).to_i, Regexp.last_match(4).to_i)
|
328
|
+
sign * (n + f)
|
341
329
|
else
|
342
330
|
num.to_f
|
343
331
|
end,
|
@@ -353,9 +341,9 @@ module RubyUnits
|
|
353
341
|
end
|
354
342
|
|
355
343
|
# return a regex used to match units
|
356
|
-
# @return [
|
344
|
+
# @return [Regexp]
|
357
345
|
def self.unit_match_regex
|
358
|
-
@@unit_match_regex ||= /(#{
|
346
|
+
@@unit_match_regex ||= /(#{prefix_regex})??(#{unit_regex})\b/
|
359
347
|
end
|
360
348
|
|
361
349
|
# return a regexp fragment used to match prefixes
|
@@ -365,11 +353,14 @@ module RubyUnits
|
|
365
353
|
@@prefix_regex ||= @@prefix_map.keys.sort_by { |prefix| [prefix.length, prefix] }.reverse.join('|')
|
366
354
|
end
|
367
355
|
|
356
|
+
# Generates (and memoizes) a regexp matching any of the temperature units or their aliases.
|
357
|
+
#
|
358
|
+
# @return [Regexp]
|
368
359
|
def self.temp_regex
|
369
360
|
@@temp_regex ||= begin
|
370
361
|
temp_units = %w[tempK tempC tempF tempR degK degC degF degR]
|
371
362
|
aliases = temp_units.map do |unit|
|
372
|
-
d =
|
363
|
+
d = definition(unit)
|
373
364
|
d && d.aliases
|
374
365
|
end.flatten.compact
|
375
366
|
regex_str = aliases.empty? ? '(?!x)x' : aliases.join('|')
|
@@ -378,6 +369,8 @@ module RubyUnits
|
|
378
369
|
end
|
379
370
|
|
380
371
|
# inject a definition into the internal array and set it up for use
|
372
|
+
#
|
373
|
+
# @param definition [RubyUnits::Unit::Definition]
|
381
374
|
def self.use_definition(definition)
|
382
375
|
@@unit_match_regex = nil # invalidate the unit match regex
|
383
376
|
@@temp_regex = nil # invalidate the temp regex
|
@@ -425,20 +418,16 @@ module RubyUnits
|
|
425
418
|
attr_accessor :unit_name
|
426
419
|
|
427
420
|
# Used to copy one unit to another
|
428
|
-
# @param [Unit]
|
429
|
-
# @return [Unit]
|
421
|
+
# @param from [RubyUnits::Unit] Unit to copy definition from
|
422
|
+
# @return [RubyUnits::Unit]
|
430
423
|
def copy(from)
|
431
|
-
@scalar
|
432
|
-
@numerator
|
424
|
+
@scalar = from.scalar
|
425
|
+
@numerator = from.numerator
|
433
426
|
@denominator = from.denominator
|
434
427
|
@base = from.base?
|
435
|
-
@signature
|
428
|
+
@signature = from.signature
|
436
429
|
@base_scalar = from.base_scalar
|
437
|
-
@unit_name =
|
438
|
-
from.unit_name
|
439
|
-
rescue
|
440
|
-
nil
|
441
|
-
end
|
430
|
+
@unit_name = from.unit_name
|
442
431
|
self
|
443
432
|
end
|
444
433
|
|
@@ -469,29 +458,26 @@ module RubyUnits
|
|
469
458
|
@signature = nil
|
470
459
|
@output = {}
|
471
460
|
raise ArgumentError, 'Invalid Unit Format' if options[0].nil?
|
461
|
+
|
472
462
|
if options.size == 2
|
473
463
|
# options[0] is the scalar
|
474
464
|
# options[1] is a unit string
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
options[1].units
|
481
|
-
rescue
|
482
|
-
options[1]
|
483
|
-
end)}")
|
465
|
+
cached = self.class.cached.get(options[1])
|
466
|
+
if cached.nil?
|
467
|
+
initialize("#{options[0]} #{options[1]}")
|
468
|
+
else
|
469
|
+
copy(cached * options[0])
|
484
470
|
end
|
485
471
|
return
|
486
472
|
end
|
487
473
|
if options.size == 3
|
488
474
|
options[1] = options[1].join if options[1].is_a?(Array)
|
489
475
|
options[2] = options[2].join if options[2].is_a?(Array)
|
490
|
-
|
491
|
-
|
492
|
-
copy(cached)
|
493
|
-
rescue
|
476
|
+
cached = self.class.cached.get("#{options[1]}/#{options[2]}")
|
477
|
+
if cached.nil?
|
494
478
|
initialize("#{options[0]} #{options[1]}/#{options[2]}")
|
479
|
+
else
|
480
|
+
copy(cached) * options[0]
|
495
481
|
end
|
496
482
|
return
|
497
483
|
end
|
@@ -528,20 +514,21 @@ module RubyUnits
|
|
528
514
|
end
|
529
515
|
update_base_scalar
|
530
516
|
raise ArgumentError, 'Temperatures must not be less than absolute zero' if temperature? && base_scalar < 0
|
517
|
+
|
531
518
|
unary_unit = units || ''
|
532
519
|
if options.first.instance_of?(String)
|
533
|
-
_opt_scalar, opt_units =
|
534
|
-
|
535
|
-
(opt_units =~ %r{\D/[\d
|
536
|
-
(opt_units =~ %r{(#{
|
537
|
-
|
520
|
+
_opt_scalar, opt_units = self.class.parse_into_numbers_and_units(options[0])
|
521
|
+
if !(self.class.cached.keys.include?(opt_units) ||
|
522
|
+
(opt_units =~ %r{\D/[\d+.]+}) ||
|
523
|
+
(opt_units =~ %r{(#{self.class.temp_regex})|(#{STONE_LB_UNIT_REGEX})|(#{LBS_OZ_UNIT_REGEX})|(#{FEET_INCH_UNITS_REGEX})|%|(#{TIME_REGEX})|i\s?(.+)?|±|\+/-})) && (opt_units && !opt_units.empty?)
|
524
|
+
self.class.cached.set(opt_units, scalar == 1 ? self : opt_units.to_unit)
|
538
525
|
end
|
539
526
|
end
|
540
|
-
unless
|
541
|
-
|
527
|
+
unless self.class.cached.keys.include?(unary_unit) || (unary_unit =~ self.class.temp_regex)
|
528
|
+
self.class.cached.set(unary_unit, scalar == 1 ? self : unary_unit.to_unit)
|
542
529
|
end
|
543
530
|
[@scalar, @numerator, @denominator, @base_scalar, @signature, @base].each(&:freeze)
|
544
|
-
|
531
|
+
super()
|
545
532
|
end
|
546
533
|
|
547
534
|
# @todo: figure out how to handle :counting units. This method should probably return :counting instead of :unitless for 'each'
|
@@ -551,9 +538,14 @@ module RubyUnits
|
|
551
538
|
@@kinds[signature]
|
552
539
|
end
|
553
540
|
|
554
|
-
#
|
555
|
-
|
556
|
-
|
541
|
+
# Convert the unit to a Unit, possibly performing a conversion.
|
542
|
+
# > The ability to pass a Unit to convert to was added in v3.0.0 for
|
543
|
+
# > consistency with other uses of #to_unit.
|
544
|
+
#
|
545
|
+
# @param other [RubyUnits::Unit, String] unit to convert to
|
546
|
+
# @return [RubyUnits::Unit]
|
547
|
+
def to_unit(other = nil)
|
548
|
+
other ? convert_to(other) : self
|
557
549
|
end
|
558
550
|
|
559
551
|
alias unit to_unit
|
@@ -562,10 +554,11 @@ module RubyUnits
|
|
562
554
|
# @return [Boolean]
|
563
555
|
def base?
|
564
556
|
return @base if defined? @base
|
557
|
+
|
565
558
|
@base = (@numerator + @denominator)
|
566
559
|
.compact
|
567
560
|
.uniq
|
568
|
-
.map { |unit|
|
561
|
+
.map { |unit| self.class.definition(unit) }
|
569
562
|
.all? { |element| element.unity? || element.base? }
|
570
563
|
@base
|
571
564
|
end
|
@@ -578,6 +571,7 @@ module RubyUnits
|
|
578
571
|
# @todo this is brittle as it depends on the display_name of a unit, which can be changed
|
579
572
|
def to_base
|
580
573
|
return self if base?
|
574
|
+
|
581
575
|
if @@unit_map[units] =~ /\A<(?:temp|deg)[CRF]>\Z/
|
582
576
|
@signature = @@kinds.key(:temperature)
|
583
577
|
base = if temperature?
|
@@ -588,12 +582,8 @@ module RubyUnits
|
|
588
582
|
return base
|
589
583
|
end
|
590
584
|
|
591
|
-
|
592
|
-
|
593
|
-
rescue
|
594
|
-
nil
|
595
|
-
end)
|
596
|
-
return cached if cached
|
585
|
+
cached_unit = self.class.base_unit_cache.get(units)
|
586
|
+
return cached_unit * scalar unless cached_unit.nil?
|
597
587
|
|
598
588
|
num = []
|
599
589
|
den = []
|
@@ -620,8 +610,8 @@ module RubyUnits
|
|
620
610
|
num = num.flatten.compact
|
621
611
|
den = den.flatten.compact
|
622
612
|
num = UNITY_ARRAY if num.empty?
|
623
|
-
base =
|
624
|
-
|
613
|
+
base = self.class.new(self.class.eliminate_terms(q, num, den))
|
614
|
+
self.class.base_unit_cache.set(units, base)
|
625
615
|
base * @scalar
|
626
616
|
end
|
627
617
|
|
@@ -646,6 +636,7 @@ module RubyUnits
|
|
646
636
|
def to_s(target_units = nil)
|
647
637
|
out = @output[target_units]
|
648
638
|
return out if out
|
639
|
+
|
649
640
|
separator = RubyUnits.configuration.separator
|
650
641
|
case target_units
|
651
642
|
when :ft
|
@@ -661,14 +652,14 @@ module RubyUnits
|
|
661
652
|
out = case target_units.strip
|
662
653
|
when /\A\s*\Z/ # whitespace only
|
663
654
|
''
|
664
|
-
when /(%[
|
655
|
+
when /(%[\-+.\w#]+)\s*(.+)*/ # format string like '%0.2f in'
|
665
656
|
begin
|
666
657
|
if Regexp.last_match(2) # unit specified, need to convert
|
667
658
|
convert_to(Regexp.last_match(2)).to_s(Regexp.last_match(1))
|
668
659
|
else
|
669
660
|
"#{Regexp.last_match(1) % @scalar}#{separator}#{Regexp.last_match(2) || units}".strip
|
670
661
|
end
|
671
|
-
rescue # parse it like a strftime format string
|
662
|
+
rescue StandardError # parse it like a strftime format string
|
672
663
|
(DateTime.new(0) + self).strftime(target_units)
|
673
664
|
end
|
674
665
|
when /(\S+)/ # unit only 'mm' or '1/mm'
|
@@ -695,6 +686,7 @@ module RubyUnits
|
|
695
686
|
# @return [String]
|
696
687
|
def inspect(dump = nil)
|
697
688
|
return super() if dump
|
689
|
+
|
698
690
|
to_s
|
699
691
|
end
|
700
692
|
|
@@ -702,7 +694,7 @@ module RubyUnits
|
|
702
694
|
# @return [Boolean]
|
703
695
|
# @todo use unit definition to determine if it's a temperature instead of a regex
|
704
696
|
def temperature?
|
705
|
-
degree? &&
|
697
|
+
degree? && units.match?(self.class.temp_regex)
|
706
698
|
end
|
707
699
|
|
708
700
|
alias is_temperature? temperature?
|
@@ -720,6 +712,7 @@ module RubyUnits
|
|
720
712
|
# @return [String] possible values: degC, degF, degR, or degK
|
721
713
|
def temperature_scale
|
722
714
|
return nil unless temperature?
|
715
|
+
|
723
716
|
"deg#{@@unit_map[units][/temp([CFRK])/, 1]}"
|
724
717
|
end
|
725
718
|
|
@@ -733,17 +726,19 @@ module RubyUnits
|
|
733
726
|
# Compare two Unit objects. Throws an exception if they are not of compatible types.
|
734
727
|
# Comparisons are done based on the value of the unit in base SI units.
|
735
728
|
# @param [Object] other
|
736
|
-
# @return [
|
729
|
+
# @return [Integer,nil]
|
737
730
|
# @raise [NoMethodError] when other does not define <=>
|
738
731
|
# @raise [ArgumentError] when units are not compatible
|
739
732
|
def <=>(other)
|
740
733
|
raise NoMethodError, "undefined method `<=>' for #{base_scalar.inspect}" unless base_scalar.respond_to?(:<=>)
|
734
|
+
|
741
735
|
if other.nil?
|
742
736
|
base_scalar <=> nil
|
743
737
|
elsif !temperature? && other.respond_to?(:zero?) && other.zero?
|
744
738
|
base_scalar <=> 0
|
745
739
|
elsif other.instance_of?(Unit)
|
746
740
|
raise ArgumentError, "Incompatible Units ('#{units}' not compatible with '#{other.units}')" unless self =~ other
|
741
|
+
|
747
742
|
base_scalar <=> other.base_scalar
|
748
743
|
else
|
749
744
|
x, y = coerce(other)
|
@@ -764,6 +759,7 @@ module RubyUnits
|
|
764
759
|
zero?
|
765
760
|
elsif other.instance_of?(Unit)
|
766
761
|
return false unless self =~ other
|
762
|
+
|
767
763
|
base_scalar == other.base_scalar
|
768
764
|
else
|
769
765
|
begin
|
@@ -791,9 +787,9 @@ module RubyUnits
|
|
791
787
|
else
|
792
788
|
begin
|
793
789
|
x, y = coerce(other)
|
794
|
-
|
790
|
+
x =~ y
|
795
791
|
rescue ArgumentError
|
796
|
-
|
792
|
+
false
|
797
793
|
end
|
798
794
|
end
|
799
795
|
end
|
@@ -814,9 +810,9 @@ module RubyUnits
|
|
814
810
|
else
|
815
811
|
begin
|
816
812
|
x, y = coerce(other)
|
817
|
-
|
813
|
+
x === y
|
818
814
|
rescue ArgumentError
|
819
|
-
|
815
|
+
false
|
820
816
|
end
|
821
817
|
end
|
822
818
|
end
|
@@ -839,14 +835,15 @@ module RubyUnits
|
|
839
835
|
other.dup
|
840
836
|
elsif self =~ other
|
841
837
|
raise ArgumentError, 'Cannot add two temperatures' if [self, other].all?(&:temperature?)
|
838
|
+
|
842
839
|
if [self, other].any?(&:temperature?)
|
843
840
|
if temperature?
|
844
|
-
|
841
|
+
self.class.new(scalar: (scalar + other.convert_to(temperature_scale).scalar), numerator: @numerator, denominator: @denominator, signature: @signature)
|
845
842
|
else
|
846
|
-
|
843
|
+
self.class.new(scalar: (other.scalar + convert_to(other.temperature_scale).scalar), numerator: other.numerator, denominator: other.denominator, signature: other.signature)
|
847
844
|
end
|
848
845
|
else
|
849
|
-
|
846
|
+
self.class.new(scalar: (base_scalar + other.base_scalar), numerator: base.numerator, denominator: base.denominator, signature: @signature).convert_to(self)
|
850
847
|
end
|
851
848
|
else
|
852
849
|
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
|
@@ -876,19 +873,19 @@ module RubyUnits
|
|
876
873
|
end
|
877
874
|
elsif self =~ other
|
878
875
|
if [self, other].all?(&:temperature?)
|
879
|
-
|
876
|
+
self.class.new(scalar: (base_scalar - other.base_scalar), numerator: KELVIN, denominator: UNITY_ARRAY, signature: @signature).convert_to(temperature_scale)
|
880
877
|
elsif temperature?
|
881
|
-
|
878
|
+
self.class.new(scalar: (base_scalar - other.base_scalar), numerator: ['<tempK>'], denominator: UNITY_ARRAY, signature: @signature).convert_to(self)
|
882
879
|
elsif other.temperature?
|
883
880
|
raise ArgumentError, 'Cannot subtract a temperature from a differential degree unit'
|
884
881
|
else
|
885
|
-
|
882
|
+
self.class.new(scalar: (base_scalar - other.base_scalar), numerator: base.numerator, denominator: base.denominator, signature: @signature).convert_to(self)
|
886
883
|
end
|
887
884
|
else
|
888
885
|
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')"
|
889
886
|
end
|
890
887
|
when Time
|
891
|
-
raise ArgumentError, 'Date and Time objects represent fixed points in time and cannot be subtracted from
|
888
|
+
raise ArgumentError, 'Date and Time objects represent fixed points in time and cannot be subtracted from a Unit'
|
892
889
|
else
|
893
890
|
x, y = coerce(other)
|
894
891
|
y - x
|
@@ -903,11 +900,12 @@ module RubyUnits
|
|
903
900
|
case other
|
904
901
|
when Unit
|
905
902
|
raise ArgumentError, 'Cannot multiply by temperatures' if [other, self].any?(&:temperature?)
|
906
|
-
|
903
|
+
|
904
|
+
opts = self.class.eliminate_terms(@scalar * other.scalar, @numerator + other.numerator, @denominator + other.denominator)
|
907
905
|
opts[:signature] = @signature + other.signature
|
908
|
-
|
906
|
+
self.class.new(opts)
|
909
907
|
when Numeric
|
910
|
-
|
908
|
+
self.class.new(scalar: @scalar * other, numerator: @numerator, denominator: @denominator, signature: @signature)
|
911
909
|
else
|
912
910
|
x, y = coerce(other)
|
913
911
|
x * y
|
@@ -925,16 +923,18 @@ module RubyUnits
|
|
925
923
|
when Unit
|
926
924
|
raise ZeroDivisionError if other.zero?
|
927
925
|
raise ArgumentError, 'Cannot divide with temperatures' if [other, self].any?(&:temperature?)
|
926
|
+
|
928
927
|
sc = Rational(@scalar, other.scalar)
|
929
928
|
sc = sc.numerator if sc.denominator == 1
|
930
|
-
opts =
|
929
|
+
opts = self.class.eliminate_terms(sc, @numerator + other.denominator, @denominator + other.numerator)
|
931
930
|
opts[:signature] = @signature - other.signature
|
932
|
-
|
931
|
+
self.class.new(opts)
|
933
932
|
when Numeric
|
934
933
|
raise ZeroDivisionError if other.zero?
|
934
|
+
|
935
935
|
sc = Rational(@scalar, other)
|
936
936
|
sc = sc.numerator if sc.denominator == 1
|
937
|
-
|
937
|
+
self.class.new(scalar: sc, numerator: @numerator, denominator: @denominator, signature: @signature)
|
938
938
|
else
|
939
939
|
x, y = coerce(other)
|
940
940
|
y / x
|
@@ -949,6 +949,7 @@ module RubyUnits
|
|
949
949
|
def divmod(other)
|
950
950
|
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ other
|
951
951
|
return scalar.divmod(other.scalar) if units == other.units
|
952
|
+
|
952
953
|
to_base.scalar.divmod(other.to_base.scalar)
|
953
954
|
end
|
954
955
|
|
@@ -959,7 +960,7 @@ module RubyUnits
|
|
959
960
|
divmod(other).last
|
960
961
|
end
|
961
962
|
|
962
|
-
#
|
963
|
+
# Exponentiation. Only takes integer powers.
|
963
964
|
# Note that anything raised to the power of 0 results in a Unit object with a scalar of 1, and no units.
|
964
965
|
# Throws an exception if exponent is not an integer.
|
965
966
|
# Ideally this routine should accept a float for the exponent
|
@@ -975,6 +976,7 @@ module RubyUnits
|
|
975
976
|
# @raise [ArgumentError] when an invalid exponent is passed
|
976
977
|
def **(other)
|
977
978
|
raise ArgumentError, 'Cannot raise a temperature to a power' if temperature?
|
979
|
+
|
978
980
|
if other.is_a?(Numeric)
|
979
981
|
return inverse if other == -1
|
980
982
|
return self if other == 1
|
@@ -982,14 +984,16 @@ module RubyUnits
|
|
982
984
|
end
|
983
985
|
case other
|
984
986
|
when Rational
|
985
|
-
|
987
|
+
power(other.numerator).root(other.denominator)
|
986
988
|
when Integer
|
987
|
-
|
989
|
+
power(other)
|
988
990
|
when Float
|
989
991
|
return self**other.to_i if other == other.to_i
|
992
|
+
|
990
993
|
valid = (1..9).map { |n| Rational(1, n) }
|
991
994
|
raise ArgumentError, 'Not a n-th root (1..9), use 1/n' unless valid.include? other.abs
|
992
|
-
|
995
|
+
|
996
|
+
root(Rational(1, other).to_int)
|
993
997
|
when Complex
|
994
998
|
raise ArgumentError, 'exponentiation of complex numbers is not supported.'
|
995
999
|
else
|
@@ -1009,6 +1013,7 @@ module RubyUnits
|
|
1009
1013
|
return 1 if n.zero?
|
1010
1014
|
return self if n == 1
|
1011
1015
|
return (1..(n - 1).to_i).inject(self) { |acc, _elem| acc * self } if n >= 0
|
1016
|
+
|
1012
1017
|
(1..-(n - 1).to_i).inject(self) { |acc, _elem| acc / self }
|
1013
1018
|
end
|
1014
1019
|
|
@@ -1016,7 +1021,7 @@ module RubyUnits
|
|
1016
1021
|
# if n < 0, returns 1/unit^(1/n)
|
1017
1022
|
# @param [Integer] n
|
1018
1023
|
# @return [Unit]
|
1019
|
-
# @raise [ArgumentError] when
|
1024
|
+
# @raise [ArgumentError] when attempting to take the root of a temperature
|
1020
1025
|
# @raise [ArgumentError] when n is not an integer
|
1021
1026
|
# @raise [ArgumentError] when n is 0
|
1022
1027
|
def root(n)
|
@@ -1029,6 +1034,7 @@ module RubyUnits
|
|
1029
1034
|
vec = unit_signature_vector
|
1030
1035
|
vec = vec.map { |x| x % n }
|
1031
1036
|
raise ArgumentError, 'Illegal root' unless vec.max.zero?
|
1037
|
+
|
1032
1038
|
num = @numerator.dup
|
1033
1039
|
den = @denominator.dup
|
1034
1040
|
|
@@ -1043,13 +1049,13 @@ module RubyUnits
|
|
1043
1049
|
r = ((x / n) * (n - 1)).to_int
|
1044
1050
|
r.times { den.delete_at(den.index(item)) }
|
1045
1051
|
end
|
1046
|
-
|
1052
|
+
self.class.new(scalar: @scalar**Rational(1, n), numerator: num, denominator: den)
|
1047
1053
|
end
|
1048
1054
|
|
1049
1055
|
# returns inverse of Unit (1/unit)
|
1050
1056
|
# @return [Unit]
|
1051
1057
|
def inverse
|
1052
|
-
|
1058
|
+
self.class.new('1') / self
|
1053
1059
|
end
|
1054
1060
|
|
1055
1061
|
# convert to a specified unit string or to the same units as another Unit
|
@@ -1060,13 +1066,17 @@ module RubyUnits
|
|
1060
1066
|
# To convert a Unit object to match another Unit object, use:
|
1061
1067
|
# unit1 >>= unit2
|
1062
1068
|
#
|
1063
|
-
# Special handling for temperature conversions is supported. If the Unit
|
1064
|
-
# from one temperature unit to another, the proper
|
1065
|
-
# Supports Kelvin, Celsius, Fahrenheit,
|
1069
|
+
# Special handling for temperature conversions is supported. If the Unit
|
1070
|
+
# object is converted from one temperature unit to another, the proper
|
1071
|
+
# temperature offsets will be used. Supports Kelvin, Celsius, Fahrenheit,
|
1072
|
+
# and Rankine scales.
|
1066
1073
|
#
|
1067
|
-
# @note If temperature is part of a compound unit, the temperature will be
|
1068
|
-
# and the units will be scaled appropriately.
|
1069
|
-
# @
|
1074
|
+
# @note If temperature is part of a compound unit, the temperature will be
|
1075
|
+
# treated as a differential and the units will be scaled appropriately.
|
1076
|
+
# @note When converting units with Integer scalars, the scalar will be
|
1077
|
+
# converted to a Rational to avoid unexpected behavior caused by Integer
|
1078
|
+
# division.
|
1079
|
+
# @param other [Unit, String]
|
1070
1080
|
# @return [Unit]
|
1071
1081
|
# @raise [ArgumentError] when attempting to convert a degree to a temperature
|
1072
1082
|
# @raise [ArgumentError] when target unit is unknown
|
@@ -1075,14 +1085,23 @@ module RubyUnits
|
|
1075
1085
|
return self if other.nil?
|
1076
1086
|
return self if TrueClass === other
|
1077
1087
|
return self if FalseClass === other
|
1078
|
-
|
1088
|
+
|
1089
|
+
if (other.is_a?(Unit) && other.temperature?) || (other.is_a?(String) && other =~ self.class.temp_regex)
|
1079
1090
|
raise ArgumentError, 'Receiver is not a temperature unit' unless degree?
|
1091
|
+
|
1080
1092
|
start_unit = units
|
1081
|
-
|
1093
|
+
# @type [String]
|
1094
|
+
target_unit = case other
|
1095
|
+
when Unit
|
1082
1096
|
other.units
|
1083
|
-
|
1097
|
+
when String
|
1084
1098
|
other
|
1099
|
+
else
|
1100
|
+
raise ArgumentError, 'Unknown target units'
|
1085
1101
|
end
|
1102
|
+
return self if target_unit == start_unit
|
1103
|
+
|
1104
|
+
# @type [Numeric]
|
1086
1105
|
@base_scalar ||= case @@unit_map[start_unit]
|
1087
1106
|
when '<tempC>'
|
1088
1107
|
@scalar + 273.15
|
@@ -1093,36 +1112,47 @@ module RubyUnits
|
|
1093
1112
|
when '<tempR>'
|
1094
1113
|
@scalar.to_r * Rational(5, 9)
|
1095
1114
|
end
|
1115
|
+
# @type [Numeric]
|
1096
1116
|
q = case @@unit_map[target_unit]
|
1097
1117
|
when '<tempC>'
|
1098
|
-
@base_scalar - 273.
|
1118
|
+
@base_scalar - 273.15
|
1099
1119
|
when '<tempK>'
|
1100
1120
|
@base_scalar
|
1101
1121
|
when '<tempF>'
|
1102
|
-
@base_scalar.to_r * Rational(9, 5) - 459.67r
|
1122
|
+
(@base_scalar.to_r * Rational(9, 5)) - 459.67r
|
1103
1123
|
when '<tempR>'
|
1104
1124
|
@base_scalar.to_r * Rational(9, 5)
|
1105
1125
|
end
|
1106
|
-
|
1126
|
+
self.class.new("#{q} #{target_unit}")
|
1107
1127
|
else
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1128
|
+
# @type [Unit]
|
1129
|
+
target = case other
|
1130
|
+
when Unit
|
1131
|
+
other
|
1132
|
+
when String
|
1133
|
+
self.class.new(other)
|
1134
|
+
else
|
1135
|
+
raise ArgumentError, 'Unknown target units'
|
1136
|
+
end
|
1137
|
+
return self if target.units == units
|
1138
|
+
|
1117
1139
|
raise ArgumentError, "Incompatible Units ('#{self}' not compatible with '#{other}')" unless self =~ target
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1140
|
+
|
1141
|
+
numerator1 = @numerator.map { |x| @@prefix_values[x] || x }.map { |i| i.is_a?(Numeric) ? i : @@unit_values[i][:scalar] }.compact
|
1142
|
+
denominator1 = @denominator.map { |x| @@prefix_values[x] || x }.map { |i| i.is_a?(Numeric) ? i : @@unit_values[i][:scalar] }.compact
|
1143
|
+
numerator2 = target.numerator.map { |x| @@prefix_values[x] || x }.map { |x| x.is_a?(Numeric) ? x : @@unit_values[x][:scalar] }.compact
|
1144
|
+
denominator2 = target.denominator.map { |x| @@prefix_values[x] || x }.map { |x| x.is_a?(Numeric) ? x : @@unit_values[x][:scalar] }.compact
|
1145
|
+
|
1146
|
+
# If the scalar is an Integer, convert it to a Rational number so that
|
1147
|
+
# if the value is scaled during conversion, resolution is not lost due
|
1148
|
+
# to integer math
|
1149
|
+
# @type [Rational, Numeric]
|
1150
|
+
conversion_scalar = @scalar.is_a?(Integer) ? @scalar.to_r : @scalar
|
1151
|
+
q = conversion_scalar * (numerator1 + denominator2).reduce(1, :*) / (numerator2 + denominator1).reduce(1, :*)
|
1152
|
+
# Convert the scalar to an Integer if the result is equivalent to an
|
1153
|
+
# integer
|
1154
|
+
q = q.to_i if @scalar.is_a?(Integer) && q.to_i == q
|
1155
|
+
self.class.new(scalar: q, numerator: target.numerator, denominator: target.denominator, signature: target.signature)
|
1126
1156
|
end
|
1127
1157
|
end
|
1128
1158
|
|
@@ -1134,6 +1164,7 @@ module RubyUnits
|
|
1134
1164
|
# @raise [RuntimeError] when not unitless
|
1135
1165
|
def to_f
|
1136
1166
|
return @scalar.to_f if unitless?
|
1167
|
+
|
1137
1168
|
raise "Cannot convert '#{self}' to Float unless unitless. Use Unit#scalar"
|
1138
1169
|
end
|
1139
1170
|
|
@@ -1142,6 +1173,7 @@ module RubyUnits
|
|
1142
1173
|
# @raise [RuntimeError] when not unitless
|
1143
1174
|
def to_c
|
1144
1175
|
return Complex(@scalar) if unitless?
|
1176
|
+
|
1145
1177
|
raise "Cannot convert '#{self}' to Complex unless unitless. Use Unit#scalar"
|
1146
1178
|
end
|
1147
1179
|
|
@@ -1150,6 +1182,7 @@ module RubyUnits
|
|
1150
1182
|
# @raise [RuntimeError] when not unitless
|
1151
1183
|
def to_i
|
1152
1184
|
return @scalar.to_int if unitless?
|
1185
|
+
|
1153
1186
|
raise "Cannot convert '#{self}' to Integer unless unitless. Use Unit#scalar"
|
1154
1187
|
end
|
1155
1188
|
|
@@ -1160,6 +1193,7 @@ module RubyUnits
|
|
1160
1193
|
# @raise [RuntimeError] when not unitless
|
1161
1194
|
def to_r
|
1162
1195
|
return @scalar.to_r if unitless?
|
1196
|
+
|
1163
1197
|
raise "Cannot convert '#{self}' to Rational unless unitless. Use Unit#scalar"
|
1164
1198
|
end
|
1165
1199
|
|
@@ -1169,52 +1203,29 @@ module RubyUnits
|
|
1169
1203
|
to_s
|
1170
1204
|
end
|
1171
1205
|
|
1172
|
-
#
|
1206
|
+
# Returns the 'unit' part of the Unit object without the scalar
|
1207
|
+
#
|
1208
|
+
# @param with_prefix [Boolean] include prefixes in output
|
1173
1209
|
# @return [String]
|
1174
1210
|
def units(with_prefix: true)
|
1175
1211
|
return '' if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY
|
1212
|
+
|
1176
1213
|
output_numerator = ['1']
|
1177
1214
|
output_denominator = []
|
1178
1215
|
num = @numerator.clone.compact
|
1179
1216
|
den = @denominator.clone.compact
|
1180
1217
|
|
1181
1218
|
unless num == UNITY_ARRAY
|
1182
|
-
definitions = num.map { |element|
|
1219
|
+
definitions = num.map { |element| self.class.definition(element) }
|
1183
1220
|
definitions.reject!(&:prefix?) unless with_prefix
|
1184
|
-
|
1185
|
-
# see https://github.com/jruby/jruby/issues/4410
|
1186
|
-
# TODO: fix this after jruby fixes their bug.
|
1187
|
-
definitions = if definitions.respond_to?(:chunk_while) && RUBY_ENGINE != 'jruby'
|
1188
|
-
definitions.chunk_while { |defn, _| defn.prefix? }.to_a
|
1189
|
-
else # chunk_while is new to ruby 2.3+, so fallback to less efficient methods for older ruby
|
1190
|
-
result = []
|
1191
|
-
enumerator = definitions.to_enum
|
1192
|
-
loop do
|
1193
|
-
first = enumerator.next
|
1194
|
-
result << (first.prefix? ? [first, enumerator.next] : [first])
|
1195
|
-
end
|
1196
|
-
result
|
1197
|
-
end
|
1221
|
+
definitions = definitions.chunk_while { |definition, _| definition.prefix? }.to_a
|
1198
1222
|
output_numerator = definitions.map { |element| element.map(&:display_name).join }
|
1199
1223
|
end
|
1200
1224
|
|
1201
1225
|
unless den == UNITY_ARRAY
|
1202
|
-
definitions = den.map { |element|
|
1226
|
+
definitions = den.map { |element| self.class.definition(element) }
|
1203
1227
|
definitions.reject!(&:prefix?) unless with_prefix
|
1204
|
-
|
1205
|
-
# see https://github.com/jruby/jruby/issues/4410
|
1206
|
-
# TODO: fix this after jruby fixes their bug.
|
1207
|
-
definitions = if definitions.respond_to?(:chunk_while) && RUBY_ENGINE != 'jruby'
|
1208
|
-
definitions.chunk_while { |defn, _| defn.prefix? }.to_a
|
1209
|
-
else # chunk_while is new to ruby 2.3+, so fallback to less efficient methods for older ruby
|
1210
|
-
result = []
|
1211
|
-
enumerator = definitions.to_enum
|
1212
|
-
loop do
|
1213
|
-
first = enumerator.next
|
1214
|
-
result << (first.prefix? ? [first, enumerator.next] : [first])
|
1215
|
-
end
|
1216
|
-
result
|
1217
|
-
end
|
1228
|
+
definitions = definitions.chunk_while { |definition, _| definition.prefix? }.to_a
|
1218
1229
|
output_denominator = definitions.map { |element| element.map(&:display_name).join }
|
1219
1230
|
end
|
1220
1231
|
|
@@ -1226,13 +1237,14 @@ module RubyUnits
|
|
1226
1237
|
.uniq
|
1227
1238
|
.map { |x| [x, output_denominator.count(x)] }
|
1228
1239
|
.map { |element, power| (element.to_s.strip + (power > 1 ? "^#{power}" : '')) }
|
1229
|
-
"#{on.join('*')}#{od.empty? ? '' :
|
1240
|
+
"#{on.join('*')}#{od.empty? ? '' : "/#{od.join('*')}"}".strip
|
1230
1241
|
end
|
1231
1242
|
|
1232
1243
|
# negates the scalar of the Unit
|
1233
1244
|
# @return [Numeric,Unit]
|
1234
1245
|
def -@
|
1235
1246
|
return -@scalar if unitless?
|
1247
|
+
|
1236
1248
|
dup * -1
|
1237
1249
|
end
|
1238
1250
|
|
@@ -1240,32 +1252,46 @@ module RubyUnits
|
|
1240
1252
|
# @return [Numeric,Unit]
|
1241
1253
|
def abs
|
1242
1254
|
return @scalar.abs if unitless?
|
1243
|
-
|
1255
|
+
|
1256
|
+
self.class.new(@scalar.abs, @numerator, @denominator)
|
1244
1257
|
end
|
1245
1258
|
|
1246
1259
|
# ceil of a unit
|
1247
1260
|
# @return [Numeric,Unit]
|
1248
|
-
def ceil
|
1249
|
-
return @scalar.ceil if unitless?
|
1250
|
-
|
1261
|
+
def ceil(*args)
|
1262
|
+
return @scalar.ceil(*args) if unitless?
|
1263
|
+
|
1264
|
+
self.class.new(@scalar.ceil(*args), @numerator, @denominator)
|
1251
1265
|
end
|
1252
1266
|
|
1253
1267
|
# @return [Numeric,Unit]
|
1254
|
-
def floor
|
1255
|
-
return @scalar.floor if unitless?
|
1256
|
-
|
1268
|
+
def floor(*args)
|
1269
|
+
return @scalar.floor(*args) if unitless?
|
1270
|
+
|
1271
|
+
self.class.new(@scalar.floor(*args), @numerator, @denominator)
|
1257
1272
|
end
|
1258
1273
|
|
1274
|
+
# Round the unit according to the rules of the scalar's class. Call this
|
1275
|
+
# with the arguments appropriate for the scalar's class (e.g., Integer,
|
1276
|
+
# Rational, etc..). Because unit conversions can often result in Rational
|
1277
|
+
# scalars (to preserve precision), it may be advisable to use +to_s+ to
|
1278
|
+
# format output instead of using +round+.
|
1279
|
+
# @example
|
1280
|
+
# RubyUnits::Unit.new('21870 mm/min').convert_to('m/min').round(1) #=> 2187/100 m/min
|
1281
|
+
# RubyUnits::Unit.new('21870 mm/min').convert_to('m/min').to_s('%0.1f') #=> 21.9 m/min
|
1282
|
+
#
|
1259
1283
|
# @return [Numeric,Unit]
|
1260
|
-
def round(
|
1261
|
-
return @scalar.round(
|
1262
|
-
|
1284
|
+
def round(*args, **kwargs)
|
1285
|
+
return @scalar.round(*args, **kwargs) if unitless?
|
1286
|
+
|
1287
|
+
self.class.new(@scalar.round(*args, **kwargs), @numerator, @denominator)
|
1263
1288
|
end
|
1264
1289
|
|
1265
1290
|
# @return [Numeric, Unit]
|
1266
|
-
def truncate
|
1267
|
-
return @scalar.truncate if unitless?
|
1268
|
-
|
1291
|
+
def truncate(*args)
|
1292
|
+
return @scalar.truncate(*args) if unitless?
|
1293
|
+
|
1294
|
+
self.class.new(@scalar.truncate(*args), @numerator, @denominator)
|
1269
1295
|
end
|
1270
1296
|
|
1271
1297
|
# returns next unit in a range. '1 mm'.to_unit.succ #=> '2 mm'.to_unit
|
@@ -1274,7 +1300,8 @@ module RubyUnits
|
|
1274
1300
|
# @raise [ArgumentError] when scalar is not equal to an integer
|
1275
1301
|
def succ
|
1276
1302
|
raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
|
1277
|
-
|
1303
|
+
|
1304
|
+
self.class.new(@scalar.to_i.succ, @numerator, @denominator)
|
1278
1305
|
end
|
1279
1306
|
|
1280
1307
|
alias next succ
|
@@ -1285,7 +1312,8 @@ module RubyUnits
|
|
1285
1312
|
# @raise [ArgumentError] when scalar is not equal to an integer
|
1286
1313
|
def pred
|
1287
1314
|
raise ArgumentError, 'Non Integer Scalar' unless @scalar == @scalar.to_i
|
1288
|
-
|
1315
|
+
|
1316
|
+
self.class.new(@scalar.to_i.pred, @numerator, @denominator)
|
1289
1317
|
end
|
1290
1318
|
|
1291
1319
|
# Tries to make a Time object from current unit. Assumes the current unit hold the duration in seconds from the epoch.
|
@@ -1298,7 +1326,7 @@ module RubyUnits
|
|
1298
1326
|
|
1299
1327
|
# convert a duration to a DateTime. This will work so long as the duration is the duration from the zero date
|
1300
1328
|
# defined by DateTime
|
1301
|
-
# @return [DateTime]
|
1329
|
+
# @return [::DateTime]
|
1302
1330
|
def to_datetime
|
1303
1331
|
DateTime.new!(convert_to('d').scalar)
|
1304
1332
|
end
|
@@ -1325,11 +1353,11 @@ module RubyUnits
|
|
1325
1353
|
def before(time_point = ::Time.now)
|
1326
1354
|
case time_point
|
1327
1355
|
when Time, Date, DateTime
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1356
|
+
(begin
|
1357
|
+
time_point - self
|
1358
|
+
rescue StandardError
|
1359
|
+
time_point.to_datetime - self
|
1360
|
+
end)
|
1333
1361
|
else
|
1334
1362
|
raise ArgumentError, 'Must specify a Time, Date, or DateTime'
|
1335
1363
|
end
|
@@ -1344,9 +1372,9 @@ module RubyUnits
|
|
1344
1372
|
def since(time_point)
|
1345
1373
|
case time_point
|
1346
1374
|
when Time
|
1347
|
-
(Time.now - time_point
|
1375
|
+
self.class.new(::Time.now - time_point, 'second').convert_to(self)
|
1348
1376
|
when DateTime, Date
|
1349
|
-
(DateTime.now - time_point
|
1377
|
+
self.class.new(::DateTime.now - time_point, 'day').convert_to(self)
|
1350
1378
|
else
|
1351
1379
|
raise ArgumentError, 'Must specify a Time, Date, or DateTime'
|
1352
1380
|
end
|
@@ -1358,9 +1386,9 @@ module RubyUnits
|
|
1358
1386
|
def until(time_point)
|
1359
1387
|
case time_point
|
1360
1388
|
when Time
|
1361
|
-
(time_point - Time.now
|
1389
|
+
self.class.new(time_point - ::Time.now, 'second').convert_to(self)
|
1362
1390
|
when DateTime, Date
|
1363
|
-
(time_point - DateTime.now
|
1391
|
+
self.class.new(time_point - ::DateTime.now, 'day').convert_to(self)
|
1364
1392
|
else
|
1365
1393
|
raise ArgumentError, 'Must specify a Time, Date, or DateTime'
|
1366
1394
|
end
|
@@ -1374,10 +1402,10 @@ module RubyUnits
|
|
1374
1402
|
case time_point
|
1375
1403
|
when Time, DateTime, Date
|
1376
1404
|
(begin
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1405
|
+
time_point + self
|
1406
|
+
rescue StandardError
|
1407
|
+
time_point.to_datetime + self
|
1408
|
+
end)
|
1381
1409
|
else
|
1382
1410
|
raise ArgumentError, 'Must specify a Time, Date, or DateTime'
|
1383
1411
|
end
|
@@ -1388,27 +1416,29 @@ module RubyUnits
|
|
1388
1416
|
|
1389
1417
|
# automatically coerce objects to units when possible
|
1390
1418
|
# if an object defines a 'to_unit' method, it will be coerced using that method
|
1391
|
-
# @param [Object, #to_unit]
|
1419
|
+
# @param other [Object, #to_unit]
|
1392
1420
|
# @return [Array]
|
1393
1421
|
def coerce(other)
|
1394
1422
|
return [other.to_unit, self] if other.respond_to? :to_unit
|
1423
|
+
|
1395
1424
|
case other
|
1396
1425
|
when Unit
|
1397
1426
|
[other, self]
|
1398
1427
|
else
|
1399
|
-
[
|
1428
|
+
[self.class.new(other), self]
|
1400
1429
|
end
|
1401
1430
|
end
|
1402
1431
|
|
1403
1432
|
# returns a new unit that has been scaled to be more in line with typical usage.
|
1404
1433
|
def best_prefix
|
1405
1434
|
return to_base if scalar.zero?
|
1435
|
+
|
1406
1436
|
best_prefix = if kind == :information
|
1407
|
-
@@prefix_values.key(2**((Math.log(base_scalar, 2) / 10.0).floor * 10))
|
1437
|
+
@@prefix_values.key(2**((::Math.log(base_scalar, 2) / 10.0).floor * 10))
|
1408
1438
|
else
|
1409
|
-
@@prefix_values.key(10**((Math.log10(base_scalar) / 3.0).floor * 3))
|
1439
|
+
@@prefix_values.key(10**((::Math.log10(base_scalar) / 3.0).floor * 3))
|
1410
1440
|
end
|
1411
|
-
to(
|
1441
|
+
to(self.class.new(@@prefix_map.key(best_prefix) + units(with_prefix: false)))
|
1412
1442
|
end
|
1413
1443
|
|
1414
1444
|
# override hash method so objects with same values are considered equal
|
@@ -1445,18 +1475,19 @@ module RubyUnits
|
|
1445
1475
|
# @raise [ArgumentError] when exponent associated with a unit is > 20 or < -20
|
1446
1476
|
def unit_signature_vector
|
1447
1477
|
return to_base.unit_signature_vector unless base?
|
1448
|
-
vector = Array.new(SIGNATURE_VECTOR.size, 0)
|
1478
|
+
vector = ::Array.new(SIGNATURE_VECTOR.size, 0)
|
1449
1479
|
# it's possible to have a kind that misses the array... kinds like :counting
|
1450
1480
|
# are more like prefixes, so don't use them to calculate the vector
|
1451
|
-
@numerator.map { |element|
|
1481
|
+
@numerator.map { |element| self.class.definition(element) }.each do |definition|
|
1452
1482
|
index = SIGNATURE_VECTOR.index(definition.kind)
|
1453
1483
|
vector[index] += 1 if index
|
1454
1484
|
end
|
1455
|
-
@denominator.map { |element|
|
1485
|
+
@denominator.map { |element| self.class.definition(element) }.each do |definition|
|
1456
1486
|
index = SIGNATURE_VECTOR.index(definition.kind)
|
1457
1487
|
vector[index] -= 1 if index
|
1458
1488
|
end
|
1459
1489
|
raise ArgumentError, 'Power out of range (-20 < net power of a unit < 20)' if vector.any? { |x| x.abs >= 20 }
|
1490
|
+
|
1460
1491
|
vector
|
1461
1492
|
end
|
1462
1493
|
|
@@ -1478,8 +1509,9 @@ module RubyUnits
|
|
1478
1509
|
# @return [Array]
|
1479
1510
|
def unit_signature
|
1480
1511
|
return @signature unless @signature.nil?
|
1512
|
+
|
1481
1513
|
vector = unit_signature_vector
|
1482
|
-
vector.each_with_index { |item, index| vector[index] = item * 20**index }
|
1514
|
+
vector.each_with_index { |item, index| vector[index] = item * (20**index) }
|
1483
1515
|
@signature = vector.inject(0) { |acc, elem| acc + elem }
|
1484
1516
|
@signature
|
1485
1517
|
end
|
@@ -1495,7 +1527,7 @@ module RubyUnits
|
|
1495
1527
|
# "GPa" -- creates a unit with scalar 1 with units 'GPa'
|
1496
1528
|
# 6'4" -- recognized as 6 feet + 4 inches
|
1497
1529
|
# 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
|
1498
|
-
# @return [nil
|
1530
|
+
# @return [nil,RubyUnits::Unit]
|
1499
1531
|
# @todo This should either be a separate class or at least a class method
|
1500
1532
|
def parse(passed_unit_string = '0')
|
1501
1533
|
unit_string = passed_unit_string.dup
|
@@ -1506,7 +1538,7 @@ module RubyUnits
|
|
1506
1538
|
|
1507
1539
|
if defined?(Complex) && unit_string =~ COMPLEX_NUMBER
|
1508
1540
|
real, imaginary, unit_s = unit_string.scan(COMPLEX_REGEX)[0]
|
1509
|
-
result =
|
1541
|
+
result = self.class.new(unit_s || '1') * Complex(real.to_f, imaginary.to_f)
|
1510
1542
|
copy(result)
|
1511
1543
|
return
|
1512
1544
|
end
|
@@ -1515,19 +1547,16 @@ module RubyUnits
|
|
1515
1547
|
sign, proper, numerator, denominator, unit_s = unit_string.scan(RATIONAL_REGEX)[0]
|
1516
1548
|
sign = sign == '-' ? -1 : 1
|
1517
1549
|
rational = sign * (proper.to_i + Rational(numerator.to_i, denominator.to_i))
|
1518
|
-
result =
|
1550
|
+
result = self.class.new(unit_s || '1') * rational
|
1519
1551
|
copy(result)
|
1520
1552
|
return
|
1521
1553
|
end
|
1522
1554
|
|
1523
1555
|
unit_string =~ NUMBER_REGEX
|
1524
|
-
unit =
|
1525
|
-
mult =
|
1526
|
-
(Regexp.last_match(1).empty? ? 1.0 : Regexp.last_match(1).to_f)
|
1527
|
-
rescue
|
1528
|
-
1.0
|
1529
|
-
end
|
1556
|
+
unit = self.class.cached.get(Regexp.last_match(2))
|
1557
|
+
mult = Regexp.last_match(1).nil? ? 1.0 : Regexp.last_match(1).to_f
|
1530
1558
|
mult = mult.to_int if mult.to_int == mult
|
1559
|
+
|
1531
1560
|
if unit
|
1532
1561
|
copy(unit)
|
1533
1562
|
@scalar *= mult
|
@@ -1541,13 +1570,14 @@ module RubyUnits
|
|
1541
1570
|
# ... and then strip the remaining brackets for x*y*z
|
1542
1571
|
unit_string.gsub!(/[<>]/, '')
|
1543
1572
|
|
1544
|
-
if unit_string =~
|
1573
|
+
if unit_string =~ TIME_REGEX
|
1545
1574
|
hours, minutes, seconds, microseconds = unit_string.scan(TIME_REGEX)[0]
|
1546
1575
|
raise ArgumentError, 'Invalid Duration' if [hours, minutes, seconds, microseconds].all?(&:nil?)
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1576
|
+
|
1577
|
+
result = self.class.new("#{hours || 0} h") +
|
1578
|
+
self.class.new("#{minutes || 0} minutes") +
|
1579
|
+
self.class.new("#{seconds || 0} seconds") +
|
1580
|
+
self.class.new("#{microseconds || 0} usec")
|
1551
1581
|
copy(result)
|
1552
1582
|
return
|
1553
1583
|
end
|
@@ -1556,7 +1586,7 @@ module RubyUnits
|
|
1556
1586
|
# feet -- 6'5"
|
1557
1587
|
feet, inches = unit_string.scan(FEET_INCH_REGEX)[0]
|
1558
1588
|
if feet && inches
|
1559
|
-
result =
|
1589
|
+
result = self.class.new("#{feet} ft") + self.class.new("#{inches} inches")
|
1560
1590
|
copy(result)
|
1561
1591
|
return
|
1562
1592
|
end
|
@@ -1564,7 +1594,7 @@ module RubyUnits
|
|
1564
1594
|
# weight -- 8 lbs 12 oz
|
1565
1595
|
pounds, oz = unit_string.scan(LBS_OZ_REGEX)[0]
|
1566
1596
|
if pounds && oz
|
1567
|
-
result =
|
1597
|
+
result = self.class.new("#{pounds} lbs") + self.class.new("#{oz} oz")
|
1568
1598
|
copy(result)
|
1569
1599
|
return
|
1570
1600
|
end
|
@@ -1572,7 +1602,7 @@ module RubyUnits
|
|
1572
1602
|
# stone -- 3 stone 5, 2 stone, 14 stone 3 pounds, etc.
|
1573
1603
|
stone, pounds = unit_string.scan(STONE_LB_REGEX)[0]
|
1574
1604
|
if stone && pounds
|
1575
|
-
result =
|
1605
|
+
result = self.class.new("#{stone} stone") + self.class.new("#{pounds} lbs")
|
1576
1606
|
copy(result)
|
1577
1607
|
return
|
1578
1608
|
end
|
@@ -1580,6 +1610,7 @@ module RubyUnits
|
|
1580
1610
|
# more than one per. I.e., "1 m/s/s"
|
1581
1611
|
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.count('/') > 1
|
1582
1612
|
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string =~ /\s[02-9]/
|
1613
|
+
|
1583
1614
|
@scalar, top, bottom = unit_string.scan(UNIT_STRING_REGEX)[0] # parse the string into parts
|
1584
1615
|
top.scan(TOP_REGEX).each do |item|
|
1585
1616
|
n = item[1].to_i
|
@@ -1612,12 +1643,12 @@ module RubyUnits
|
|
1612
1643
|
|
1613
1644
|
@numerator ||= UNITY_ARRAY
|
1614
1645
|
@denominator ||= UNITY_ARRAY
|
1615
|
-
@numerator = top.scan(
|
1616
|
-
@denominator = bottom.scan(
|
1646
|
+
@numerator = top.scan(self.class.unit_match_regex).delete_if(&:empty?).compact if top
|
1647
|
+
@denominator = bottom.scan(self.class.unit_match_regex).delete_if(&:empty?).compact if bottom
|
1617
1648
|
|
1618
1649
|
# eliminate all known terms from this string. This is a quick check to see if the passed unit
|
1619
1650
|
# contains terms that are not defined.
|
1620
|
-
used = "#{top} #{bottom}".to_s.gsub(
|
1651
|
+
used = "#{top} #{bottom}".to_s.gsub(self.class.unit_match_regex, '').gsub(%r{[\d*, "'_^/$]}, '')
|
1621
1652
|
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") unless used.empty?
|
1622
1653
|
|
1623
1654
|
@numerator = @numerator.map do |item|
|