bson 4.1.1 → 5.2.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.
Files changed (226) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +106 -7
  3. data/Rakefile +86 -43
  4. data/ext/bson/{native-endian.h → bson-endian.h} +14 -107
  5. data/ext/bson/bson-native.h +135 -0
  6. data/ext/bson/bytebuf.c +133 -0
  7. data/ext/bson/endian.c +117 -0
  8. data/ext/bson/extconf.rb +8 -3
  9. data/ext/bson/init.c +364 -0
  10. data/ext/bson/libbson-utf8.c +230 -0
  11. data/ext/bson/read.c +470 -0
  12. data/ext/bson/util.c +250 -0
  13. data/ext/bson/write.c +675 -0
  14. data/lib/bson/active_support.rb +19 -0
  15. data/lib/bson/array.rb +97 -30
  16. data/lib/bson/big_decimal.rb +77 -0
  17. data/lib/bson/binary.rb +510 -70
  18. data/lib/bson/boolean.rb +15 -4
  19. data/lib/bson/code.rb +25 -12
  20. data/lib/bson/code_with_scope.rb +41 -15
  21. data/lib/bson/config.rb +3 -28
  22. data/lib/bson/date.rb +16 -4
  23. data/lib/bson/date_time.rb +6 -4
  24. data/lib/bson/db_pointer.rb +110 -0
  25. data/lib/bson/dbref.rb +154 -0
  26. data/lib/bson/decimal128/builder.rb +456 -0
  27. data/lib/bson/decimal128.rb +272 -0
  28. data/lib/bson/document.rb +177 -7
  29. data/lib/bson/environment.rb +17 -2
  30. data/lib/bson/error/bson_decode_error.rb +11 -0
  31. data/lib/bson/error/ext_json_parse_error.rb +11 -0
  32. data/lib/bson/error/illegal_key.rb +23 -0
  33. data/lib/bson/error/invalid_binary_type.rb +37 -0
  34. data/lib/bson/error/invalid_dbref_argument.rb +12 -0
  35. data/lib/bson/error/invalid_decimal128_argument.rb +25 -0
  36. data/lib/bson/error/invalid_decimal128_range.rb +27 -0
  37. data/lib/bson/error/invalid_decimal128_string.rb +26 -0
  38. data/lib/bson/error/invalid_key.rb +24 -0
  39. data/lib/bson/error/invalid_object_id.rb +11 -0
  40. data/lib/bson/error/invalid_regexp_pattern.rb +13 -0
  41. data/lib/bson/error/unrepresentable_precision.rb +19 -0
  42. data/lib/bson/error/unserializable_class.rb +13 -0
  43. data/lib/bson/error/unsupported_binary_subtype.rb +12 -0
  44. data/lib/bson/error/unsupported_type.rb +11 -0
  45. data/lib/bson/error.rb +22 -0
  46. data/lib/bson/ext_json.rb +389 -0
  47. data/lib/bson/false_class.rb +6 -4
  48. data/lib/bson/float.rb +43 -7
  49. data/lib/bson/hash.rb +152 -37
  50. data/lib/bson/int32.rb +104 -6
  51. data/lib/bson/int64.rb +111 -8
  52. data/lib/bson/integer.rb +43 -9
  53. data/lib/bson/json.rb +3 -1
  54. data/lib/bson/max_key.rb +21 -10
  55. data/lib/bson/min_key.rb +21 -10
  56. data/lib/bson/nil_class.rb +7 -3
  57. data/lib/bson/object.rb +25 -17
  58. data/lib/bson/object_id.rb +98 -113
  59. data/lib/bson/open_struct.rb +59 -0
  60. data/lib/bson/regexp.rb +129 -56
  61. data/lib/bson/registry.rb +7 -10
  62. data/lib/bson/specialized.rb +8 -4
  63. data/lib/bson/string.rb +12 -32
  64. data/lib/bson/symbol.rb +107 -11
  65. data/lib/bson/time.rb +68 -7
  66. data/lib/bson/time_with_zone.rb +67 -0
  67. data/lib/bson/timestamp.rb +50 -10
  68. data/lib/bson/true_class.rb +6 -4
  69. data/lib/bson/undefined.rb +28 -2
  70. data/lib/bson/vector.rb +44 -0
  71. data/lib/bson/version.rb +6 -14
  72. data/lib/bson.rb +22 -12
  73. data/spec/README.md +14 -0
  74. data/spec/bson/array_spec.rb +38 -62
  75. data/spec/bson/big_decimal_spec.rb +328 -0
  76. data/spec/bson/binary_spec.rb +199 -53
  77. data/spec/bson/binary_uuid_spec.rb +190 -0
  78. data/spec/bson/boolean_spec.rb +2 -1
  79. data/spec/bson/byte_buffer_read_spec.rb +198 -0
  80. data/spec/bson/byte_buffer_spec.rb +122 -381
  81. data/spec/bson/byte_buffer_write_spec.rb +855 -0
  82. data/spec/bson/code_spec.rb +6 -4
  83. data/spec/bson/code_with_scope_spec.rb +6 -4
  84. data/spec/bson/config_spec.rb +1 -35
  85. data/spec/bson/date_spec.rb +2 -1
  86. data/spec/bson/date_time_spec.rb +55 -1
  87. data/spec/bson/dbref_legacy_spec.rb +186 -0
  88. data/spec/bson/dbref_spec.rb +487 -0
  89. data/spec/bson/decimal128_spec.rb +1840 -0
  90. data/spec/bson/document_as_spec.rb +61 -0
  91. data/spec/bson/document_spec.rb +205 -32
  92. data/spec/bson/ext_json_parse_spec.rb +346 -0
  93. data/spec/bson/false_class_spec.rb +9 -1
  94. data/spec/bson/float_spec.rb +42 -1
  95. data/spec/bson/hash_as_spec.rb +58 -0
  96. data/spec/bson/hash_spec.rb +318 -66
  97. data/spec/bson/int32_spec.rb +248 -1
  98. data/spec/bson/int64_spec.rb +308 -1
  99. data/spec/bson/integer_spec.rb +61 -3
  100. data/spec/bson/json_spec.rb +2 -1
  101. data/spec/bson/max_key_spec.rb +6 -4
  102. data/spec/bson/min_key_spec.rb +6 -4
  103. data/spec/bson/nil_class_spec.rb +2 -1
  104. data/spec/bson/object_id_spec.rb +95 -5
  105. data/spec/bson/object_spec.rb +3 -2
  106. data/spec/bson/open_struct_spec.rb +87 -0
  107. data/spec/bson/raw_spec.rb +594 -0
  108. data/spec/bson/regexp_spec.rb +61 -8
  109. data/spec/bson/registry_spec.rb +3 -2
  110. data/spec/bson/string_spec.rb +26 -33
  111. data/spec/bson/symbol_raw_spec.rb +70 -0
  112. data/spec/bson/symbol_spec.rb +77 -20
  113. data/spec/bson/time_spec.rb +206 -2
  114. data/spec/bson/time_with_zone_spec.rb +69 -0
  115. data/spec/bson/timestamp_spec.rb +58 -2
  116. data/spec/bson/true_class_spec.rb +9 -1
  117. data/spec/bson/undefined_spec.rb +28 -1
  118. data/spec/bson/vector_spec.rb +33 -0
  119. data/spec/bson_spec.rb +2 -1
  120. data/spec/runners/binary_vector.rb +78 -0
  121. data/spec/runners/common_driver.rb +348 -0
  122. data/spec/runners/corpus.rb +191 -0
  123. data/spec/runners/corpus_legacy.rb +248 -0
  124. data/spec/shared/LICENSE +20 -0
  125. data/spec/shared/bin/get-mongodb-download-url +17 -0
  126. data/spec/shared/bin/s3-copy +45 -0
  127. data/spec/shared/bin/s3-upload +69 -0
  128. data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
  129. data/spec/shared/lib/mrss/cluster_config.rb +231 -0
  130. data/spec/shared/lib/mrss/constraints.rb +378 -0
  131. data/spec/shared/lib/mrss/docker_runner.rb +298 -0
  132. data/spec/shared/lib/mrss/eg_config_utils.rb +51 -0
  133. data/spec/shared/lib/mrss/event_subscriber.rb +210 -0
  134. data/spec/shared/lib/mrss/lite_constraints.rb +238 -0
  135. data/spec/shared/lib/mrss/release/candidate.rb +281 -0
  136. data/spec/shared/lib/mrss/release/product_data.rb +144 -0
  137. data/spec/shared/lib/mrss/server_version_registry.rb +113 -0
  138. data/spec/shared/lib/mrss/session_registry.rb +69 -0
  139. data/spec/shared/lib/mrss/session_registry_legacy.rb +60 -0
  140. data/spec/shared/lib/mrss/spec_organizer.rb +179 -0
  141. data/spec/shared/lib/mrss/utils.rb +37 -0
  142. data/spec/shared/lib/tasks/candidate.rake +64 -0
  143. data/spec/shared/share/Dockerfile.erb +251 -0
  144. data/spec/shared/share/haproxy-1.conf +16 -0
  145. data/spec/shared/share/haproxy-2.conf +17 -0
  146. data/spec/shared/shlib/config.sh +27 -0
  147. data/spec/shared/shlib/distro.sh +84 -0
  148. data/spec/shared/shlib/server.sh +423 -0
  149. data/spec/shared/shlib/set_env.sh +110 -0
  150. data/spec/spec_helper.rb +61 -1
  151. data/spec/spec_tests/binary_vector_spec.rb +82 -0
  152. data/spec/spec_tests/common_driver_spec.rb +84 -0
  153. data/spec/spec_tests/corpus_legacy_spec.rb +72 -0
  154. data/spec/spec_tests/corpus_spec.rb +134 -0
  155. data/spec/spec_tests/data/binary_vector/README.md +61 -0
  156. data/spec/spec_tests/data/binary_vector/float32.json +65 -0
  157. data/spec/spec_tests/data/binary_vector/int8.json +57 -0
  158. data/spec/spec_tests/data/binary_vector/packed_bit.json +83 -0
  159. data/spec/spec_tests/data/corpus/README.md +15 -0
  160. data/spec/spec_tests/data/corpus/array.json +49 -0
  161. data/spec/spec_tests/data/corpus/binary.json +153 -0
  162. data/spec/spec_tests/data/corpus/boolean.json +27 -0
  163. data/spec/spec_tests/data/corpus/code.json +67 -0
  164. data/spec/spec_tests/data/corpus/code_w_scope.json +78 -0
  165. data/spec/spec_tests/data/corpus/datetime.json +42 -0
  166. data/spec/spec_tests/data/corpus/dbpointer.json +56 -0
  167. data/spec/spec_tests/data/corpus/dbref.json +51 -0
  168. data/spec/spec_tests/data/corpus/decimal128-1.json +317 -0
  169. data/spec/spec_tests/data/corpus/decimal128-2.json +793 -0
  170. data/spec/spec_tests/data/corpus/decimal128-3.json +1771 -0
  171. data/spec/spec_tests/data/corpus/decimal128-4.json +165 -0
  172. data/spec/spec_tests/data/corpus/decimal128-5.json +402 -0
  173. data/spec/spec_tests/data/corpus/decimal128-6.json +131 -0
  174. data/spec/spec_tests/data/corpus/decimal128-7.json +327 -0
  175. data/spec/spec_tests/data/corpus/document.json +60 -0
  176. data/spec/spec_tests/data/corpus/double.json +87 -0
  177. data/spec/spec_tests/data/corpus/int32.json +43 -0
  178. data/spec/spec_tests/data/corpus/int64.json +43 -0
  179. data/spec/spec_tests/data/corpus/maxkey.json +12 -0
  180. data/spec/spec_tests/data/corpus/minkey.json +12 -0
  181. data/spec/spec_tests/data/corpus/multi-type-deprecated.json +15 -0
  182. data/spec/spec_tests/data/corpus/multi-type.json +11 -0
  183. data/spec/spec_tests/data/corpus/null.json +12 -0
  184. data/spec/spec_tests/data/corpus/oid.json +28 -0
  185. data/spec/spec_tests/data/corpus/regex.json +65 -0
  186. data/spec/spec_tests/data/corpus/string.json +72 -0
  187. data/spec/spec_tests/data/corpus/symbol.json +80 -0
  188. data/spec/spec_tests/data/corpus/timestamp.json +34 -0
  189. data/spec/spec_tests/data/corpus/top.json +262 -0
  190. data/spec/spec_tests/data/corpus/undefined.json +15 -0
  191. data/spec/spec_tests/data/corpus_legacy/array.json +49 -0
  192. data/spec/spec_tests/data/corpus_legacy/binary.json +69 -0
  193. data/spec/spec_tests/data/corpus_legacy/boolean.json +27 -0
  194. data/spec/spec_tests/data/corpus_legacy/code.json +67 -0
  195. data/spec/spec_tests/data/corpus_legacy/code_w_scope.json +78 -0
  196. data/spec/spec_tests/data/corpus_legacy/document.json +36 -0
  197. data/spec/spec_tests/data/corpus_legacy/double.json +69 -0
  198. data/spec/spec_tests/data/corpus_legacy/failures/datetime.json +31 -0
  199. data/spec/spec_tests/data/corpus_legacy/failures/dbpointer.json +42 -0
  200. data/spec/spec_tests/data/corpus_legacy/failures/int64.json +38 -0
  201. data/spec/spec_tests/data/corpus_legacy/failures/symbol.json +62 -0
  202. data/spec/spec_tests/data/corpus_legacy/int32.json +38 -0
  203. data/spec/spec_tests/data/corpus_legacy/maxkey.json +12 -0
  204. data/spec/spec_tests/data/corpus_legacy/minkey.json +12 -0
  205. data/spec/spec_tests/data/corpus_legacy/null.json +12 -0
  206. data/spec/spec_tests/data/corpus_legacy/oid.json +28 -0
  207. data/spec/spec_tests/data/corpus_legacy/regex.json +37 -0
  208. data/spec/spec_tests/data/corpus_legacy/string.json +67 -0
  209. data/spec/spec_tests/data/corpus_legacy/timestamp.json +18 -0
  210. data/spec/spec_tests/data/corpus_legacy/top.json +62 -0
  211. data/spec/spec_tests/data/corpus_legacy/undefined.json +13 -0
  212. data/spec/spec_tests/data/decimal128/decimal128-1.json +363 -0
  213. data/spec/spec_tests/data/decimal128/decimal128-2.json +793 -0
  214. data/spec/spec_tests/data/decimal128/decimal128-3.json +1771 -0
  215. data/spec/spec_tests/data/decimal128/decimal128-4.json +165 -0
  216. data/spec/spec_tests/data/decimal128/decimal128-5.json +402 -0
  217. data/spec/spec_tests/data/decimal128/decimal128-6.json +131 -0
  218. data/spec/spec_tests/data/decimal128/decimal128-7.json +327 -0
  219. data/spec/support/shared_examples.rb +32 -11
  220. data/spec/support/spec_config.rb +17 -0
  221. data/spec/support/utils.rb +58 -0
  222. metadata +284 -45
  223. checksums.yaml.gz.sig +0 -3
  224. data/ext/bson/native.c +0 -722
  225. data.tar.gz.sig +0 -0
  226. metadata.gz.sig +0 -0
@@ -0,0 +1,456 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:todo all
3
+ # Copyright (C) 2016-2020 MongoDB Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module BSON
18
+ class Decimal128
19
+
20
+ # Helper module for parsing String, Integer, Float, BigDecimal, and Decimal128
21
+ # objects into other objects.
22
+ #
23
+ # @api private
24
+ #
25
+ # @since 4.2.0
26
+ module Builder
27
+
28
+ # Infinity mask.
29
+ #
30
+ # @since 4.2.0
31
+ INFINITY_MASK = 0x7800000000000000
32
+
33
+ # NaN mask.
34
+ #
35
+ # @since 4.2.0
36
+ NAN_MASK = 0x7c00000000000000
37
+
38
+ # SNaN mask.
39
+ #
40
+ # @since 4.2.0
41
+ SNAN_MASK = (1 << 57)
42
+
43
+ # Signed bit mask.
44
+ #
45
+ # @since 4.2.0
46
+ SIGN_BIT_MASK = (1 << 63)
47
+
48
+ # The two highest bits of the 64 high order bits.
49
+ #
50
+ # @since 4.2.0
51
+ TWO_HIGHEST_BITS_SET = (3 << 61)
52
+
53
+ extend self
54
+
55
+ # Convert parts representing a Decimal128 into the corresponding bits.
56
+ #
57
+ # @param [ Integer ] significand The significand.
58
+ # @param [ Integer ] exponent The exponent.
59
+ # @param [ true, false ] is_negative Whether the value is negative.
60
+ #
61
+ # @return [ Array ] Tuple of the low and high bits.
62
+ #
63
+ # @since 4.2.0
64
+ def parts_to_bits(significand, exponent, is_negative)
65
+ validate_range!(exponent, significand)
66
+ exponent = exponent + Decimal128::EXPONENT_OFFSET
67
+ high = significand >> 64
68
+ low = (high << 64) ^ significand
69
+
70
+ if high >> 49 == 1
71
+ high = high & 0x7fffffffffff
72
+ high |= TWO_HIGHEST_BITS_SET
73
+ high |= (exponent & 0x3fff) << 47
74
+ else
75
+ high |= exponent << 49
76
+ end
77
+
78
+ if is_negative
79
+ high |= SIGN_BIT_MASK
80
+ end
81
+
82
+
83
+ [ low, high ]
84
+ end
85
+
86
+ private
87
+
88
+ def validate_range!(exponent, significand)
89
+ unless valid_exponent?(exponent)
90
+ raise Error::InvalidDecimal128Range.new
91
+ end
92
+
93
+ unless valid_significand?(significand)
94
+ raise Error::UnrepresentablePrecision.new
95
+ end
96
+ end
97
+
98
+ def valid_significand?(significand)
99
+ significand.to_s.length <= Decimal128::MAX_DIGITS_OF_PRECISION
100
+ end
101
+
102
+ def valid_exponent?(exponent)
103
+ exponent <= Decimal128::MAX_EXPONENT && exponent >= Decimal128::MIN_EXPONENT
104
+ end
105
+
106
+ # Helper class for parsing a String into Decimal128 high and low bits.
107
+ #
108
+ # @api private
109
+ #
110
+ # @since 4.2.0
111
+ class FromString
112
+
113
+ # Regex matching a string representing NaN.
114
+ #
115
+ # @return [ Regex ] A regex matching a NaN string.
116
+ #
117
+ # @since 4.2.0
118
+ NAN_REGEX = /^(\-)?(S)?NaN$/i
119
+
120
+ # Regex matching a string representing positive or negative Infinity.
121
+ #
122
+ # @return [ Regex ] A regex matching a positive or negative Infinity string.
123
+ #
124
+ # @since 4.2.0
125
+ INFINITY_REGEX = /^(\+|\-)?Inf(inity)?$/i
126
+
127
+ # Regex for the fraction, including leading zeros.
128
+ #
129
+ # @return [ Regex ] The regex for matching the fraction,
130
+ # including leading zeros.
131
+ #
132
+ # @since 4.2.0
133
+ SIGNIFICAND_WITH_LEADING_ZEROS_REGEX = /(0*)(\d+)/
134
+
135
+ # Regex for separating a negative sign from the significands.
136
+ #
137
+ # @return [ Regex ] The regex for separating a sign from significands.
138
+ #
139
+ # @since 4.2.0
140
+ SIGN_AND_DIGITS_REGEX = /^(\-)?(\S+)/
141
+
142
+ # Regex matching a scientific exponent.
143
+ #
144
+ # @return [ Regex ] A regex matching E, e, E+, e+.
145
+ #
146
+ # @since 4.2.0
147
+ SCIENTIFIC_EXPONENT_REGEX = /E\+?/i
148
+
149
+ # Regex for capturing trailing zeros.
150
+ #
151
+ # @since 4.2.0
152
+ TRAILING_ZEROS_REGEX = /[1-9]*(0+)$/
153
+
154
+ # Regex for a valid decimal128 string format.
155
+ #
156
+ # @return [ Regex ] The regex for a valid decimal128 string.
157
+ #
158
+ # @since 4.2.0
159
+ VALID_DECIMAL128_STRING_REGEX = /\A[\-\+]?(\d+(\.\d*)?|\.\d+)(E[\-\+]?\d+)?\Z/i
160
+
161
+ # Initialize the FromString Builder object.
162
+ #
163
+ # @example Create the FromString builder.
164
+ # Builder::FromString.new(string)
165
+ #
166
+ # @param [ String ] string The string to create a Decimal128 from.
167
+ #
168
+ # @since 4.2.0
169
+ def initialize(string)
170
+ @string = string
171
+ end
172
+
173
+ # Get the bits representing the Decimal128 that the string corresponds to.
174
+ #
175
+ # @example Get the bits for the Decimal128 object created from the string.
176
+ # builder.bits
177
+ #
178
+ # @return [ Array ] Tuple of the low and high bits.
179
+ #
180
+ # @since 4.2.0
181
+ def bits
182
+ if special?
183
+ to_special_bits
184
+ else
185
+ validate_format!
186
+ to_bits
187
+ end
188
+ end
189
+
190
+ private
191
+
192
+ def to_bits
193
+ _, sign, digits_str = SIGN_AND_DIGITS_REGEX.match(@string).to_a
194
+ digits, _, scientific_exp = digits_str.partition(SCIENTIFIC_EXPONENT_REGEX)
195
+ before_decimal, _, after_decimal = digits.partition('.')
196
+
197
+ significand_str = before_decimal << after_decimal
198
+ significand_str = SIGNIFICAND_WITH_LEADING_ZEROS_REGEX.match(significand_str).to_a[2]
199
+
200
+ exponent = -(after_decimal.length)
201
+ exponent = exponent + scientific_exp.to_i
202
+ exponent, significand_str = round_exact(exponent, significand_str)
203
+ exponent, significand_str = clamp(exponent, significand_str)
204
+
205
+ Builder.parts_to_bits(significand_str.to_i, exponent, sign == '-')
206
+ end
207
+
208
+ def round_exact(exponent, significand)
209
+ if exponent < Decimal128::MIN_EXPONENT
210
+ if significand.to_i == 0
211
+ round = Decimal128::MIN_EXPONENT - exponent
212
+ exponent += round
213
+ elsif trailing_zeros = TRAILING_ZEROS_REGEX.match(significand)
214
+ round = [ (Decimal128::MIN_EXPONENT - exponent),
215
+ trailing_zeros[1].size ].min
216
+ significand = significand[0...-round]
217
+ exponent += round
218
+ end
219
+ elsif significand.length > Decimal128::MAX_DIGITS_OF_PRECISION
220
+ trailing_zeros = TRAILING_ZEROS_REGEX.match(significand)
221
+ if trailing_zeros
222
+ round = [ trailing_zeros[1].size,
223
+ (significand.length - Decimal128::MAX_DIGITS_OF_PRECISION),
224
+ (Decimal128::MAX_EXPONENT - exponent)].min
225
+ significand = significand[0...-round]
226
+ exponent += round
227
+ end
228
+ end
229
+ [ exponent, significand ]
230
+ end
231
+
232
+ def clamp(exponent, significand)
233
+ if exponent > Decimal128::MAX_EXPONENT
234
+ if significand.to_i == 0
235
+ adjust = exponent - Decimal128::MAX_EXPONENT
236
+ significand = '0'
237
+ else
238
+ adjust = [ (exponent - Decimal128::MAX_EXPONENT),
239
+ Decimal128::MAX_DIGITS_OF_PRECISION - significand.length ].min
240
+ significand << '0'* adjust
241
+ end
242
+ exponent -= adjust
243
+ end
244
+
245
+ [ exponent, significand ]
246
+ end
247
+
248
+ def to_special_bits
249
+ high = 0
250
+ if match = NAN_REGEX.match(@string)
251
+ high = NAN_MASK
252
+ high = high | SIGN_BIT_MASK if match[1]
253
+ high = high | SNAN_MASK if match[2]
254
+ elsif match = INFINITY_REGEX.match(@string)
255
+ high = INFINITY_MASK
256
+ high = high | SIGN_BIT_MASK if match[1] == '-'
257
+ end
258
+ [ 0, high ]
259
+ end
260
+
261
+ def special?
262
+ @string =~ NAN_REGEX || @string =~ INFINITY_REGEX
263
+ end
264
+
265
+ def validate_format!
266
+ raise Error::InvalidDecimal128String.new unless @string =~ VALID_DECIMAL128_STRING_REGEX
267
+ end
268
+ end
269
+
270
+ # Helper class for parsing a BigDecimal into Decimal128 high and low bits.
271
+ #
272
+ # @api private
273
+ #
274
+ # @since 4.2.0
275
+ class FromBigDecimal
276
+
277
+ # Initialize the FromBigDecimal Builder object.
278
+ #
279
+ # @example Create the FromBigDecimal builder.
280
+ # Builder::FromBigDecimal.new(big_decimal)
281
+ #
282
+ # @param [ BigDecimal ] big_decimal The big decimal object to
283
+ # create a Decimal128 from.
284
+ #
285
+ # @since 4.2.0
286
+ def initialize(big_decimal)
287
+ @big_decimal = big_decimal
288
+ end
289
+
290
+ # Get the bits representing the Decimal128 that the big decimal corresponds to.
291
+ #
292
+ # @example Get the bits for the Decimal128 object created from the big decimal.
293
+ # builder.bits
294
+ #
295
+ # @return [ Array ] Tuple of the low and high bits.
296
+ #
297
+ # @since 4.2.0
298
+ def bits
299
+ if special?
300
+ to_special_bits
301
+ else
302
+ to_bits
303
+ end
304
+ end
305
+
306
+ private
307
+
308
+ def to_special_bits
309
+ case @big_decimal.sign
310
+ when ::BigDecimal::SIGN_POSITIVE_INFINITE
311
+ high = INFINITY_MASK
312
+ when ::BigDecimal::SIGN_NEGATIVE_INFINITE
313
+ high = INFINITY_MASK | SIGN_BIT_MASK
314
+ when ::BigDecimal::SIGN_NaN
315
+ high = NAN_MASK
316
+ end
317
+ [ 0, high ]
318
+ end
319
+
320
+ def to_bits
321
+ sign, significand_str, _, exp = @big_decimal.split
322
+ exponent = @big_decimal.zero? ? 0 : exp - significand_str.length
323
+ is_negative = (sign == ::BigDecimal::SIGN_NEGATIVE_FINITE || sign == ::BigDecimal::SIGN_NEGATIVE_ZERO)
324
+ Builder.parts_to_bits(significand_str.to_i,
325
+ exponent,
326
+ is_negative)
327
+ end
328
+
329
+ def special?
330
+ @big_decimal.infinite? || @big_decimal.nan?
331
+ end
332
+ end
333
+
334
+ # Helper class for getting a String representation of a Decimal128 object.
335
+ #
336
+ # @api private
337
+ #
338
+ # @since 4.2.0
339
+ class ToString
340
+
341
+ # String representing a NaN value.
342
+ #
343
+ # @return [ String ] The string representing NaN.
344
+ #
345
+ # @since 4.2.0
346
+ NAN_STRING = 'NaN'
347
+
348
+ # String representing an Infinity value.
349
+ #
350
+ # @return [ String ] The string representing Infinity.
351
+ #
352
+ # @since 4.2.0
353
+ INFINITY_STRING = 'Infinity'
354
+
355
+ # Initialize the FromBigDecimal Builder object.
356
+ #
357
+ # @example Create the ToString builder.
358
+ # Builder::ToString.new(big_decimal)
359
+ #
360
+ # @param [ Decimal128 ] decimal128 The decimal128 object to
361
+ # create a String from.
362
+ #
363
+ # @since 4.2.0
364
+ def initialize(decimal128)
365
+ @decimal128 = decimal128
366
+ end
367
+
368
+ # Get the string representing the Decimal128 object.
369
+ #
370
+ # @example Get a string representing the decimal128.
371
+ # builder.string
372
+ #
373
+ # @return [ String ] The string representing the decimal128 object.
374
+ #
375
+ # @note The returned string may be frozen.
376
+ #
377
+ # @since 4.2.0
378
+ def string
379
+ return NAN_STRING if nan?
380
+ str = infinity? ? INFINITY_STRING : create_string
381
+ negative? ? "-#{str}" : str
382
+ end
383
+
384
+ private
385
+
386
+ def create_string
387
+ if use_scientific_notation?
388
+ exp_pos_sign = exponent < 0 ? '' : '+'
389
+ if significand.length > 1
390
+ str = "#{significand[0]}.#{significand[1..-1]}E#{exp_pos_sign}#{scientific_exponent}"
391
+ else
392
+ str = "#{significand}E#{exp_pos_sign}#{scientific_exponent}"
393
+ end
394
+ elsif exponent < 0
395
+ if significand.length > exponent.abs
396
+ decimal_point_index = significand.length - exponent.abs
397
+ str = "#{significand[0..decimal_point_index-1]}.#{significand[decimal_point_index..-1]}"
398
+ else
399
+ left_zero_pad = (exponent + significand.length).abs
400
+ str = "0.#{'0' * left_zero_pad}#{significand}"
401
+ end
402
+ end
403
+ str || significand
404
+ end
405
+
406
+ def scientific_exponent
407
+ @scientific_exponent ||= (significand.length - 1) + exponent
408
+ end
409
+
410
+ def use_scientific_notation?
411
+ exponent > 0 || scientific_exponent < -6
412
+ end
413
+
414
+ def exponent
415
+ @exponent ||= two_highest_bits_set? ?
416
+ ((high_bits & 0x1fffe00000000000) >> 47) - Decimal128::EXPONENT_OFFSET :
417
+ ((high_bits & 0x7fff800000000000) >> 49) - Decimal128::EXPONENT_OFFSET
418
+ end
419
+
420
+ def significand
421
+ @significand ||= two_highest_bits_set? ? '0' : bits_to_significand.to_s
422
+ end
423
+
424
+ def bits_to_significand
425
+ significand = high_bits & 0x1ffffffffffff
426
+ significand = significand << 64
427
+ significand |= low_bits
428
+ end
429
+
430
+ def two_highest_bits_set?
431
+ high_bits & TWO_HIGHEST_BITS_SET == TWO_HIGHEST_BITS_SET
432
+ end
433
+
434
+ def nan?
435
+ high_bits & NAN_MASK == NAN_MASK
436
+ end
437
+
438
+ def negative?
439
+ high_bits & SIGN_BIT_MASK == SIGN_BIT_MASK
440
+ end
441
+
442
+ def infinity?
443
+ high_bits & INFINITY_MASK == INFINITY_MASK
444
+ end
445
+
446
+ def high_bits
447
+ @decimal128.instance_variable_get(:@high)
448
+ end
449
+
450
+ def low_bits
451
+ @decimal128.instance_variable_get(:@low)
452
+ end
453
+ end
454
+ end
455
+ end
456
+ end