ruby-units 2.3.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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|
|