bson 4.1.1 → 4.2.0.rc0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Rakefile +18 -3
- data/ext/bson/{native.c → bson_native.c} +48 -8
- data/ext/bson/extconf.rb +1 -1
- data/ext/bson/native-endian.h +1 -1
- data/lib/bson.rb +3 -1
- data/lib/bson/config.rb +1 -1
- data/lib/bson/decimal128.rb +318 -0
- data/lib/bson/decimal128/builder.rb +448 -0
- data/lib/bson/document.rb +2 -2
- data/lib/bson/environment.rb +13 -1
- data/lib/bson/int32.rb +46 -0
- data/lib/bson/int64.rb +46 -0
- data/lib/bson/max_key.rb +1 -1
- data/lib/bson/min_key.rb +1 -1
- data/lib/bson/object_id.rb +2 -1
- data/lib/bson/open_struct.rb +57 -0
- data/lib/bson/regexp.rb +1 -1
- data/lib/bson/registry.rb +1 -1
- data/lib/bson/version.rb +2 -2
- data/spec/bson/decimal128_spec.rb +1583 -0
- data/spec/bson/document_spec.rb +1 -1
- data/spec/bson/driver_bson_spec.rb +77 -0
- data/spec/bson/int32_spec.rb +58 -0
- data/spec/bson/int64_spec.rb +58 -0
- data/spec/bson/open_struct_spec.rb +144 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/common_driver.rb +347 -0
- data/spec/support/driver-spec-tests/decimal128/decimal128-1.json +363 -0
- data/spec/support/driver-spec-tests/decimal128/decimal128-2.json +793 -0
- data/spec/support/driver-spec-tests/decimal128/decimal128-3.json +1771 -0
- data/spec/support/driver-spec-tests/decimal128/decimal128-4.json +165 -0
- data/spec/support/driver-spec-tests/decimal128/decimal128-5.json +402 -0
- data/spec/support/driver-spec-tests/decimal128/decimal128-6.json +131 -0
- data/spec/support/driver-spec-tests/decimal128/decimal128-7.json +327 -0
- metadata +29 -4
- metadata.gz.sig +0 -0
@@ -0,0 +1,448 @@
|
|
1
|
+
# Copyright (C) 2016 MongoDB Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module BSON
|
16
|
+
class Decimal128
|
17
|
+
|
18
|
+
# Helper module for parsing String, Integer, Float, BigDecimal, and Decimal128
|
19
|
+
# objects into other objects.
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
#
|
23
|
+
# @since 4.2.0
|
24
|
+
module Builder
|
25
|
+
|
26
|
+
# Infinity mask.
|
27
|
+
#
|
28
|
+
# @since 4.2.0
|
29
|
+
INFINITY_MASK = 0x7800000000000000.freeze
|
30
|
+
|
31
|
+
# NaN mask.
|
32
|
+
#
|
33
|
+
# @since 4.2.0
|
34
|
+
NAN_MASK = 0x7c00000000000000.freeze
|
35
|
+
|
36
|
+
# SNaN mask.
|
37
|
+
#
|
38
|
+
# @since 4.2.0
|
39
|
+
SNAN_MASK = (1 << 57).freeze
|
40
|
+
|
41
|
+
# Signed bit mask.
|
42
|
+
#
|
43
|
+
# @since 4.2.0
|
44
|
+
SIGN_BIT_MASK = (1 << 63).freeze
|
45
|
+
|
46
|
+
# The two highest bits of the 64 high order bits.
|
47
|
+
#
|
48
|
+
# @since 4.2.0
|
49
|
+
TWO_HIGHEST_BITS_SET = (3 << 61).freeze
|
50
|
+
|
51
|
+
extend self
|
52
|
+
|
53
|
+
# Convert parts representing a Decimal128 into the corresponding bits.
|
54
|
+
#
|
55
|
+
# @param [ Integer ] significand The significand.
|
56
|
+
# @param [ Integer ] exponent The exponent.
|
57
|
+
# @param [ true, false ] is_negative Whether the value is negative.
|
58
|
+
#
|
59
|
+
# @return [ Array ] Tuple of the low and high bits.
|
60
|
+
#
|
61
|
+
# @since 4.2.0
|
62
|
+
def parts_to_bits(significand, exponent, is_negative)
|
63
|
+
validate_range!(exponent, significand)
|
64
|
+
exponent = exponent + Decimal128::EXPONENT_OFFSET
|
65
|
+
high = significand >> 64
|
66
|
+
low = (high << 64) ^ significand
|
67
|
+
|
68
|
+
if high >> 49 == 1
|
69
|
+
high = high & 0x7fffffffffff
|
70
|
+
high |= TWO_HIGHEST_BITS_SET
|
71
|
+
high |= (exponent & 0x3fff) << 47
|
72
|
+
else
|
73
|
+
high |= exponent << 49
|
74
|
+
end
|
75
|
+
|
76
|
+
if is_negative
|
77
|
+
high |= SIGN_BIT_MASK
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
[ low, high ]
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def validate_range!(exponent, significand)
|
87
|
+
unless valid_significand?(significand) && valid_exponent?(exponent)
|
88
|
+
raise Decimal128::InvalidRange.new
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def valid_significand?(significand)
|
93
|
+
significand.to_s.length <= Decimal128::MAX_DIGITS_OF_PRECISION
|
94
|
+
end
|
95
|
+
|
96
|
+
def valid_exponent?(exponent)
|
97
|
+
exponent <= Decimal128::MAX_EXPONENT && exponent >= Decimal128::MIN_EXPONENT
|
98
|
+
end
|
99
|
+
|
100
|
+
# Helper class for parsing a String into Decimal128 high and low bits.
|
101
|
+
#
|
102
|
+
# @api private
|
103
|
+
#
|
104
|
+
# @since 4.2.0
|
105
|
+
class FromString
|
106
|
+
|
107
|
+
# Regex matching a string representing NaN.
|
108
|
+
#
|
109
|
+
# @return [ Regex ] A regex matching a NaN string.
|
110
|
+
#
|
111
|
+
# @since 4.2.0
|
112
|
+
NAN_REGEX = /^(\-)?(S)?NaN$/i.freeze
|
113
|
+
|
114
|
+
# Regex matching a string representing positive or negative Infinity.
|
115
|
+
#
|
116
|
+
# @return [ Regex ] A regex matching a positive or negative Infinity string.
|
117
|
+
#
|
118
|
+
# @since 4.2.0
|
119
|
+
INFINITY_REGEX = /^(\+|\-)?Inf(inity)?$/i.freeze
|
120
|
+
|
121
|
+
# Regex for the fraction, including leading zeros.
|
122
|
+
#
|
123
|
+
# @return [ Regex ] The regex for matching the fraction,
|
124
|
+
# including leading zeros.
|
125
|
+
#
|
126
|
+
# @since 4.2.0
|
127
|
+
SIGNIFICAND_WITH_LEADING_ZEROS_REGEX = /(0*)(\d+)/.freeze
|
128
|
+
|
129
|
+
# Regex for separating a negative sign from the significands.
|
130
|
+
#
|
131
|
+
# @return [ Regex ] The regex for separating a sign from significands.
|
132
|
+
#
|
133
|
+
# @since 4.2.0
|
134
|
+
SIGN_AND_DIGITS_REGEX = /^(\-)?(\S+)/.freeze
|
135
|
+
|
136
|
+
# Regex matching a scientific exponent.
|
137
|
+
#
|
138
|
+
# @return [ Regex ] A regex matching E, e, E+, e+.
|
139
|
+
#
|
140
|
+
# @since 4.2.0
|
141
|
+
SCIENTIFIC_EXPONENT_REGEX = /E\+?/i.freeze
|
142
|
+
|
143
|
+
# Regex for capturing trailing zeros.
|
144
|
+
#
|
145
|
+
# @since 4.2.0
|
146
|
+
TRAILING_ZEROS_REGEX = /[1-9]*(0+)$/.freeze
|
147
|
+
|
148
|
+
# Regex for a valid decimal128 string format.
|
149
|
+
#
|
150
|
+
# @return [ Regex ] The regex for a valid decimal128 string.
|
151
|
+
#
|
152
|
+
# @since 4.2.0
|
153
|
+
VALID_DECIMAL128_STRING_REGEX = /^[\-\+]?(\d+(\.\d*)?|\.\d+)(E[\-\+]?\d+)?$/i.freeze
|
154
|
+
|
155
|
+
# Initialize the FromString Builder object.
|
156
|
+
#
|
157
|
+
# @example Create the FromString builder.
|
158
|
+
# Builder::FromString.new(string)
|
159
|
+
#
|
160
|
+
# @param [ String ] string The string to create a Decimal128 from.
|
161
|
+
#
|
162
|
+
# @since 4.2.0
|
163
|
+
def initialize(string)
|
164
|
+
@string = string
|
165
|
+
end
|
166
|
+
|
167
|
+
# Get the bits representing the Decimal128 that the string corresponds to.
|
168
|
+
#
|
169
|
+
# @example Get the bits for the Decimal128 object created from the string.
|
170
|
+
# builder.bits
|
171
|
+
#
|
172
|
+
# @return [ Array ] Tuple of the low and high bits.
|
173
|
+
#
|
174
|
+
# @since 4.2.0
|
175
|
+
def bits
|
176
|
+
if special?
|
177
|
+
to_special_bits
|
178
|
+
else
|
179
|
+
validate_format!
|
180
|
+
to_bits
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def to_bits
|
187
|
+
original, sign, digits_str = SIGN_AND_DIGITS_REGEX.match(@string).to_a
|
188
|
+
digits, e, scientific_exp = digits_str.partition(SCIENTIFIC_EXPONENT_REGEX)
|
189
|
+
before_decimal, decimal, after_decimal = digits.partition('.')
|
190
|
+
|
191
|
+
significand_str = before_decimal << after_decimal
|
192
|
+
significand_str = SIGNIFICAND_WITH_LEADING_ZEROS_REGEX.match(significand_str).to_a[2]
|
193
|
+
|
194
|
+
exponent = -(after_decimal.length)
|
195
|
+
exponent = exponent + scientific_exp.to_i
|
196
|
+
exponent, significand_str = round_exact(exponent, significand_str)
|
197
|
+
exponent, significand_str = clamp(exponent, significand_str)
|
198
|
+
|
199
|
+
Builder.parts_to_bits(significand_str.to_i, exponent, sign == '-')
|
200
|
+
end
|
201
|
+
|
202
|
+
def round_exact(exponent, significand)
|
203
|
+
if exponent < Decimal128::MIN_EXPONENT
|
204
|
+
if significand.to_i == 0
|
205
|
+
round = Decimal128::MIN_EXPONENT - exponent
|
206
|
+
exponent += round
|
207
|
+
elsif trailing_zeros = TRAILING_ZEROS_REGEX.match(significand)
|
208
|
+
round = [ (Decimal128::MIN_EXPONENT - exponent),
|
209
|
+
trailing_zeros[1].size ].min
|
210
|
+
significand = significand[0...-round]
|
211
|
+
exponent += round
|
212
|
+
end
|
213
|
+
elsif significand.length > Decimal128::MAX_DIGITS_OF_PRECISION
|
214
|
+
trailing_zeros = TRAILING_ZEROS_REGEX.match(significand)
|
215
|
+
if trailing_zeros
|
216
|
+
round = [ trailing_zeros[1].size,
|
217
|
+
(significand.length - Decimal128::MAX_DIGITS_OF_PRECISION),
|
218
|
+
(Decimal128::MAX_EXPONENT - exponent)].min
|
219
|
+
significand = significand[0...-round]
|
220
|
+
exponent += round
|
221
|
+
end
|
222
|
+
end
|
223
|
+
[ exponent, significand ]
|
224
|
+
end
|
225
|
+
|
226
|
+
def clamp(exponent, significand)
|
227
|
+
if exponent > Decimal128::MAX_EXPONENT
|
228
|
+
if significand.to_i == 0
|
229
|
+
adjust = exponent - Decimal128::MAX_EXPONENT
|
230
|
+
significand = '0'
|
231
|
+
else
|
232
|
+
adjust = [ (exponent - Decimal128::MAX_EXPONENT),
|
233
|
+
Decimal128::MAX_DIGITS_OF_PRECISION - significand.length ].min
|
234
|
+
significand << '0'* adjust
|
235
|
+
end
|
236
|
+
exponent -= adjust
|
237
|
+
end
|
238
|
+
|
239
|
+
[ exponent, significand ]
|
240
|
+
end
|
241
|
+
|
242
|
+
def to_special_bits
|
243
|
+
high = 0
|
244
|
+
if match = NAN_REGEX.match(@string)
|
245
|
+
high = NAN_MASK
|
246
|
+
high = high | SIGN_BIT_MASK if match[1]
|
247
|
+
high = high | SNAN_MASK if match[2]
|
248
|
+
elsif match = INFINITY_REGEX.match(@string)
|
249
|
+
high = INFINITY_MASK
|
250
|
+
high = high | SIGN_BIT_MASK if match[1] == '-'
|
251
|
+
end
|
252
|
+
[ 0, high ]
|
253
|
+
end
|
254
|
+
|
255
|
+
def special?
|
256
|
+
@string =~ NAN_REGEX || @string =~ INFINITY_REGEX
|
257
|
+
end
|
258
|
+
|
259
|
+
def validate_format!
|
260
|
+
raise BSON::Decimal128::InvalidString.new unless @string =~ VALID_DECIMAL128_STRING_REGEX
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Helper class for parsing a BigDecimal into Decimal128 high and low bits.
|
265
|
+
#
|
266
|
+
# @api private
|
267
|
+
#
|
268
|
+
# @since 4.2.0
|
269
|
+
class FromBigDecimal
|
270
|
+
|
271
|
+
# Initialize the FromBigDecimal Builder object.
|
272
|
+
#
|
273
|
+
# @example Create the FromBigDecimal builder.
|
274
|
+
# Builder::FromBigDecimal.new(big_decimal)
|
275
|
+
#
|
276
|
+
# @param [ BigDecimal ] big_decimal The big decimal object to
|
277
|
+
# create a Decimal128 from.
|
278
|
+
#
|
279
|
+
# @since 4.2.0
|
280
|
+
def initialize(big_decimal)
|
281
|
+
@big_decimal = big_decimal
|
282
|
+
end
|
283
|
+
|
284
|
+
# Get the bits representing the Decimal128 that the big decimal corresponds to.
|
285
|
+
#
|
286
|
+
# @example Get the bits for the Decimal128 object created from the big decimal.
|
287
|
+
# builder.bits
|
288
|
+
#
|
289
|
+
# @return [ Array ] Tuple of the low and high bits.
|
290
|
+
#
|
291
|
+
# @since 4.2.0
|
292
|
+
def bits
|
293
|
+
if special?
|
294
|
+
to_special_bits
|
295
|
+
else
|
296
|
+
to_bits
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
private
|
301
|
+
|
302
|
+
def to_special_bits
|
303
|
+
case @big_decimal.sign
|
304
|
+
when BigDecimal::SIGN_POSITIVE_INFINITE
|
305
|
+
high = INFINITY_MASK
|
306
|
+
when BigDecimal::SIGN_NEGATIVE_INFINITE
|
307
|
+
high = INFINITY_MASK | SIGN_BIT_MASK
|
308
|
+
when BigDecimal::SIGN_NaN
|
309
|
+
high = NAN_MASK
|
310
|
+
end
|
311
|
+
[ 0, high ]
|
312
|
+
end
|
313
|
+
|
314
|
+
def to_bits
|
315
|
+
sign, significand_str, base, exp = @big_decimal.split
|
316
|
+
exponent = @big_decimal.zero? ? 0 : exp - significand_str.length
|
317
|
+
is_negative = (sign == BigDecimal::SIGN_NEGATIVE_FINITE || sign == BigDecimal::SIGN_NEGATIVE_ZERO)
|
318
|
+
Builder.parts_to_bits(significand_str.to_i,
|
319
|
+
exponent,
|
320
|
+
is_negative)
|
321
|
+
end
|
322
|
+
|
323
|
+
def special?
|
324
|
+
@big_decimal.infinite? || @big_decimal.nan?
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
# Helper class for getting a String representation of a Decimal128 object.
|
329
|
+
#
|
330
|
+
# @api private
|
331
|
+
#
|
332
|
+
# @since 4.2.0
|
333
|
+
class ToString
|
334
|
+
|
335
|
+
# String representing a NaN value.
|
336
|
+
#
|
337
|
+
# @return [ String ] The string representing NaN.
|
338
|
+
#
|
339
|
+
# @since 4.2.0
|
340
|
+
NAN_STRING = 'NaN'.freeze
|
341
|
+
|
342
|
+
# String representing an Infinity value.
|
343
|
+
#
|
344
|
+
# @return [ String ] The string representing Infinity.
|
345
|
+
#
|
346
|
+
# @since 4.2.0
|
347
|
+
INFINITY_STRING = 'Infinity'.freeze
|
348
|
+
|
349
|
+
# Initialize the FromBigDecimal Builder object.
|
350
|
+
#
|
351
|
+
# @example Create the ToString builder.
|
352
|
+
# Builder::ToString.new(big_decimal)
|
353
|
+
#
|
354
|
+
# @param [ Decimal128 ] decimal128 The decimal128 object to
|
355
|
+
# create a String from.
|
356
|
+
#
|
357
|
+
# @since 4.2.0
|
358
|
+
def initialize(decimal128)
|
359
|
+
@decimal128 = decimal128
|
360
|
+
end
|
361
|
+
|
362
|
+
# Get the string representing the Decimal128 object.
|
363
|
+
#
|
364
|
+
# @example Get a string representing the decimal128.
|
365
|
+
# builder.string
|
366
|
+
#
|
367
|
+
# @return [ String ] The string representing the decimal128 object.
|
368
|
+
#
|
369
|
+
# @since 4.2.0
|
370
|
+
def string
|
371
|
+
return NAN_STRING if nan?
|
372
|
+
str = infinity? ? INFINITY_STRING : create_string
|
373
|
+
negative? ? '-' << str : str
|
374
|
+
end
|
375
|
+
|
376
|
+
private
|
377
|
+
|
378
|
+
def create_string
|
379
|
+
if use_scientific_notation?
|
380
|
+
exp_pos_sign = exponent < 0 ? '' : '+'
|
381
|
+
if significand.length > 1
|
382
|
+
str = "#{significand[0]}.#{significand[1..-1]}E#{exp_pos_sign}#{scientific_exponent}"
|
383
|
+
else
|
384
|
+
str = "#{significand}E#{exp_pos_sign}#{scientific_exponent}"
|
385
|
+
end
|
386
|
+
elsif exponent < 0
|
387
|
+
if significand.length > exponent.abs
|
388
|
+
decimal_point_index = significand.length - exponent.abs
|
389
|
+
str = "#{significand[0..decimal_point_index-1]}.#{significand[decimal_point_index..-1]}"
|
390
|
+
else
|
391
|
+
left_zero_pad = (exponent + significand.length).abs
|
392
|
+
str = "0.#{'0' * left_zero_pad}#{significand}"
|
393
|
+
end
|
394
|
+
end
|
395
|
+
str || significand
|
396
|
+
end
|
397
|
+
|
398
|
+
def scientific_exponent
|
399
|
+
@scientific_exponent ||= (significand.length - 1) + exponent
|
400
|
+
end
|
401
|
+
|
402
|
+
def use_scientific_notation?
|
403
|
+
exponent > 0 || scientific_exponent < -6
|
404
|
+
end
|
405
|
+
|
406
|
+
def exponent
|
407
|
+
@exponent ||= two_highest_bits_set? ?
|
408
|
+
((high_bits & 0x1fffe00000000000) >> 47) - Decimal128::EXPONENT_OFFSET :
|
409
|
+
((high_bits & 0x7fff800000000000) >> 49) - Decimal128::EXPONENT_OFFSET
|
410
|
+
end
|
411
|
+
|
412
|
+
def significand
|
413
|
+
@significand ||= two_highest_bits_set? ? '0' : bits_to_significand.to_s
|
414
|
+
end
|
415
|
+
|
416
|
+
def bits_to_significand
|
417
|
+
significand = high_bits & 0x1ffffffffffff
|
418
|
+
significand = significand << 64
|
419
|
+
significand |= low_bits
|
420
|
+
end
|
421
|
+
|
422
|
+
def two_highest_bits_set?
|
423
|
+
high_bits & TWO_HIGHEST_BITS_SET == TWO_HIGHEST_BITS_SET
|
424
|
+
end
|
425
|
+
|
426
|
+
def nan?
|
427
|
+
high_bits & NAN_MASK == NAN_MASK
|
428
|
+
end
|
429
|
+
|
430
|
+
def negative?
|
431
|
+
high_bits & SIGN_BIT_MASK == SIGN_BIT_MASK
|
432
|
+
end
|
433
|
+
|
434
|
+
def infinity?
|
435
|
+
high_bits & INFINITY_MASK == INFINITY_MASK
|
436
|
+
end
|
437
|
+
|
438
|
+
def high_bits
|
439
|
+
@decimal128.instance_variable_get(:@high)
|
440
|
+
end
|
441
|
+
|
442
|
+
def low_bits
|
443
|
+
@decimal128.instance_variable_get(:@low)
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
data/lib/bson/document.rb
CHANGED
@@ -144,7 +144,7 @@ module BSON
|
|
144
144
|
#
|
145
145
|
# @param [ BSON::Document, Hash ] other The document/hash to merge with.
|
146
146
|
#
|
147
|
-
# @
|
147
|
+
# @return [ BSON::Document ] The result of the merge.
|
148
148
|
#
|
149
149
|
# @since 3.0.0
|
150
150
|
def merge(other, &block)
|
@@ -159,7 +159,7 @@ module BSON
|
|
159
159
|
#
|
160
160
|
# @param [ BSON::Document, Hash ] other The document/hash to merge with.
|
161
161
|
#
|
162
|
-
# @
|
162
|
+
# @return [ BSON::Document ] The result of the merge.
|
163
163
|
#
|
164
164
|
# @since 3.0.0
|
165
165
|
def merge!(other)
|