schemurai 2.2.2 → 2.3.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.
@@ -0,0 +1,1055 @@
1
+ //! Numeric instructions distinguish Ruby's ordinary comparison from decimal
2
+ //! multipleOf semantics. All evaluation helpers operate under the GVL and
3
+ //! allocate no Ruby objects; unsupported magnitudes retain VM compatibility.
4
+ use super::*;
5
+ use std::cmp::Ordering;
6
+
7
+ // Strictly ordered integers are unique without fingerprinting their digits.
8
+ // Each comparison checks both original handles; mixed or unordered inputs
9
+ // retain the indexed JSON equality path. No Ruby allocation occurs here.
10
+ pub(super) fn ordered_big_unique(values: &[Value]) -> Option<Verdict> {
11
+ let direction = unsafe { sn_big_order(values[0], values[1]) };
12
+ match direction {
13
+ 0 => return Some(Invalid),
14
+ -1 | 1 => {}
15
+ _ => return None,
16
+ }
17
+ for (offset, pair) in values[1..].windows(2).enumerate() {
18
+ let order = unsafe { sn_big_order(pair[0], pair[1]) };
19
+ if order == 0 {
20
+ return Some(Invalid);
21
+ }
22
+ if order != direction {
23
+ // A return to the first value is a duplicate even when it breaks
24
+ // the order. Otherwise indexing must examine the entire input.
25
+ if order == ADAPTER_UNSUPPORTED {
26
+ return None;
27
+ }
28
+ if unsafe { sn_big_order(values[0], pair[1]) } == 0 {
29
+ return Some(Invalid);
30
+ }
31
+ let tail = offset + 2;
32
+ return (values.len() - tail <= 16)
33
+ .then(|| ordered_tail_unique(values, tail, direction))
34
+ .flatten();
35
+ }
36
+ }
37
+ Some(Valid)
38
+ }
39
+
40
+ // A short unordered tail can search the proven ordered prefix directly.
41
+ // Bound the tail to keep its internal pair comparisons constant-sized and
42
+ // avoid rebuilding a full fingerprint index after an almost complete scan.
43
+ fn ordered_tail_unique(values: &[Value], prefix: usize, direction: i32) -> Option<Verdict> {
44
+ for at in prefix..values.len() {
45
+ let (mut low, mut high) = (0, prefix);
46
+ while low < high {
47
+ let middle = low + (high - low) / 2;
48
+ match unsafe { sn_big_order(values[middle], values[at]) } {
49
+ 0 => return Some(Invalid),
50
+ ADAPTER_UNSUPPORTED => return None,
51
+ order if order == direction => low = middle + 1,
52
+ _ => high = middle,
53
+ }
54
+ }
55
+ for previous in prefix..at {
56
+ if unsafe { sn_big_order(values[previous], values[at]) } == 0 {
57
+ return Some(Invalid);
58
+ }
59
+ }
60
+ }
61
+ Some(Valid)
62
+ }
63
+
64
+ // A finite binary64 integer has at most 1,024 magnitude bits. Arithmetic
65
+ // adapters fill fixed stack words; there is no instance-tree conversion or
66
+ // Ruby allocation. Larger integers cannot equal any finite Float.
67
+ pub(super) fn integer_float_equal(integer: Value, float: f64) -> Verdict {
68
+ if !float.is_finite() || float.trunc() != float {
69
+ return Invalid;
70
+ }
71
+ let mut actual = [0_u64; 16];
72
+ let sign = unsafe { sn_integer1024(integer, actual.as_mut_ptr()) };
73
+ if sign.abs() > 1 || (sign < 0) != (float < 0.0) {
74
+ return Invalid;
75
+ }
76
+ let mut expected = [0_u64; 16];
77
+ if float != 0.0 {
78
+ let bits = float.to_bits();
79
+ let mantissa = (bits & ((1_u64 << 52) - 1)) | (1_u64 << 52);
80
+ let shift = ((bits >> 52) & 0x7ff) as i32 - 1023 - 52;
81
+ if shift < 0 {
82
+ expected[0] = mantissa >> shift.unsigned_abs();
83
+ } else {
84
+ let word = shift as usize / 64;
85
+ let bit = shift as usize % 64;
86
+ expected[word] = mantissa << bit;
87
+ if bit != 0 && word + 1 < expected.len() {
88
+ expected[word + 1] = mantissa >> (64 - bit);
89
+ }
90
+ }
91
+ }
92
+ verdict(actual == expected)
93
+ }
94
+
95
+ pub(super) fn integer_fingerprint(integer: Value) -> u64 {
96
+ let mut words = [0_u64; 16];
97
+ let sign = unsafe { sn_integer1024(integer, words.as_mut_ptr()) };
98
+ if sign.abs() > 1 {
99
+ // No finite Float can equal this magnitude. Mix bounded low words,
100
+ // sign, and magnitude size; full equality resolves all collisions,
101
+ // including integers that differ only in unsampled high digits.
102
+ let len = words
103
+ .iter()
104
+ .rposition(|&word| word != 0)
105
+ .map_or(0, |index| index + 1);
106
+ return integer_bucket(&words[..len], sign, unsafe { sn_integer_bytes(integer) });
107
+ }
108
+ exact_integer_fingerprint(&words, sign)
109
+ }
110
+
111
+ pub(super) fn big_fingerprint(integer: Value) -> Option<u64> {
112
+ let mut words = [0_u64; 4];
113
+ let sign = unsafe { sn_big256(integer, words.as_mut_ptr()) };
114
+ if sign == ADAPTER_UNSUPPORTED {
115
+ return None;
116
+ }
117
+ Some(if sign.abs() > 1 {
118
+ integer_fingerprint(integer)
119
+ } else {
120
+ exact_integer_fingerprint(&words, sign)
121
+ })
122
+ }
123
+
124
+ // Only used by an index that restarts with canonical fingerprints if any item
125
+ // is not a Bignum. No Float can enter this index, so it needs no binary64
126
+ // normalization. Full integer equality resolves every fingerprint collision.
127
+ pub(super) fn big_only_fingerprint(integer: Value) -> Option<u64> {
128
+ let mut hash = 0;
129
+ (unsafe { sn_big_hash(integer, &mut hash) } != 0).then_some(hash)
130
+ }
131
+
132
+ // This convention is restricted to Big/Big comparisons. The caller restarts
133
+ // with the full hash when collisions accumulate, or canonical fingerprints
134
+ // when a non-Bignum appears. No sampled metadata decides equality.
135
+ pub(super) fn big_low_fingerprint(integer: Value) -> Option<u64> {
136
+ let sample = unsafe { sn_big_low_word(integer) };
137
+ (sample != 1 << 33).then(|| {
138
+ // Fold the short magnitude sample without scrambling adjacent low
139
+ // words. Exact equality and wider bounded retries resolve collisions.
140
+ let bits = u64::from(sample as u32) ^ if sample & (1 << 32) != 0 { u64::MAX } else { 0 };
141
+ let folded = bits ^ (bits >> 33);
142
+ folded ^ (folded >> 11) ^ (folded >> 22)
143
+ })
144
+ }
145
+
146
+ pub(super) fn big_low64_fingerprint(integer: Value) -> Option<u64> {
147
+ let mut word = 0;
148
+ let sign = unsafe { sn_big_low64_word(integer, &mut word) };
149
+ (sign != 3).then(|| {
150
+ let bits = word ^ if sign < 0 { u64::MAX } else { 0 };
151
+ let folded = bits ^ (bits >> 33);
152
+ let folded = folded ^ (folded >> 11) ^ (folded >> 22);
153
+ folded.wrapping_mul(0x9e3779b97f4a7c15)
154
+ })
155
+ }
156
+
157
+ fn exact_integer_fingerprint(words: &[u64], sign: i32) -> u64 {
158
+ let top = words.iter().rposition(|&word| word != 0).unwrap_or(0);
159
+ if top == 0 && (words[0] < (1_u64 << 63) || (sign < 0 && words[0] == 1_u64 << 63)) {
160
+ let bits = if sign < 0 {
161
+ words[0].wrapping_neg()
162
+ } else {
163
+ words[0]
164
+ };
165
+ return mix(bits ^ TYPE_NUMBER as u64);
166
+ }
167
+ let exponent = top * 64 + 63 - words[top].leading_zeros() as usize;
168
+ let shift = exponent - 52;
169
+ let word = shift / 64;
170
+ let bit = shift % 64;
171
+ if words[..word].iter().any(|&word| word != 0)
172
+ || (bit != 0 && words[word] & ((1_u64 << bit) - 1) != 0)
173
+ {
174
+ // Nonrepresentable integers cannot equal any Float. Keep their low
175
+ // digits instead of putting nearby integers into one rounded bucket.
176
+ return integer_bucket(&words[..=top], sign, exponent / 8 + 1);
177
+ }
178
+ let mut mantissa = words[word] >> bit;
179
+ if bit != 0 && word + 1 < words.len() {
180
+ mantissa |= words[word + 1] << (64 - bit);
181
+ }
182
+ // Exactly representable integers share bits with their equal Float.
183
+ // Every collision still goes through full JSON equality.
184
+ let bits = ((sign < 0) as u64) << 63
185
+ | ((exponent + 1023) as u64) << 52
186
+ | (mantissa & ((1_u64 << 52) - 1));
187
+ mix(bits ^ TYPE_NUMBER as u64)
188
+ }
189
+
190
+ // Callers supply the prefix ending at the last nonzero word. It may be empty
191
+ // when all sampled low words of an oversized integer are zero.
192
+ fn integer_bucket(words: &[u64], sign: i32, bytes: usize) -> u64 {
193
+ let mut hash = bytes as u64 ^ TYPE_NUMBER as u64 ^ ((sign < 0) as u64);
194
+ // Independent lanes shorten the multiplication dependency chain while
195
+ // retaining every sampled word. Full VALUE equality resolves collisions.
196
+ let mut other = hash ^ 0x9e37_79b9_7f4a_7c15;
197
+ let (pairs, remainder) = words.as_chunks::<2>();
198
+ for pair in pairs {
199
+ hash = (hash ^ pair[0]).wrapping_mul(0x0000_0100_0000_01b3);
200
+ other = (other ^ pair[1]).wrapping_mul(0x0000_0100_0000_01b3);
201
+ }
202
+ if let [word] = remainder {
203
+ hash = (hash ^ word).wrapping_mul(0x0000_0100_0000_01b3);
204
+ }
205
+ mix(hash ^ other.rotate_left(32))
206
+ }
207
+
208
+ #[derive(Debug, Clone, Copy)]
209
+ struct Fraction {
210
+ numerator: i128,
211
+ denominator: i128,
212
+ }
213
+ impl Fraction {
214
+ fn compare(self, other: Self) -> Ordering {
215
+ let signs = self.numerator.signum().cmp(&other.numerator.signum());
216
+ if signs != Ordering::Equal {
217
+ return signs;
218
+ }
219
+ let (mut a, mut b) = (self.numerator.unsigned_abs(), self.denominator as u128);
220
+ let (mut c, mut d) = (other.numerator.unsigned_abs(), other.denominator as u128);
221
+ let mut reverse = self.numerator < 0;
222
+ loop {
223
+ let order = (a / b).cmp(&(c / d));
224
+ if order != Ordering::Equal {
225
+ return if reverse { order.reverse() } else { order };
226
+ }
227
+ let (r, s) = (a % b, c % d);
228
+ if r == 0 || s == 0 {
229
+ let order = r.cmp(&s);
230
+ return if reverse { order.reverse() } else { order };
231
+ }
232
+ (a, b, c, d) = (b, r, d, s);
233
+ reverse = !reverse;
234
+ }
235
+ }
236
+
237
+ fn multiple_of(self, divisor: Self) -> bool {
238
+ // Both fractions are reduced: p must divide n and d must divide q.
239
+ divisor.numerator > 0
240
+ && self.numerator % divisor.numerator == 0
241
+ && divisor.denominator % self.denominator == 0
242
+ }
243
+ }
244
+
245
+ fn decimal_float(value: f64) -> Option<Fraction> {
246
+ if !value.is_finite() {
247
+ return None;
248
+ }
249
+ let mut buffer = ryu::Buffer::new();
250
+ let mut bytes = buffer.format_finite(value).as_bytes();
251
+ let negative = bytes.first() == Some(&b'-');
252
+ if negative {
253
+ bytes = &bytes[1..];
254
+ }
255
+ let exponent = if let Some(at) = bytes.iter().position(|&byte| byte == b'e') {
256
+ let exponent = std::str::from_utf8(&bytes[at + 1..])
257
+ .ok()?
258
+ .parse::<i32>()
259
+ .ok()?;
260
+ bytes = &bytes[..at];
261
+ exponent
262
+ } else {
263
+ 0
264
+ };
265
+ let fractional = bytes
266
+ .iter()
267
+ .position(|&b| b == b'.')
268
+ .map_or(0, |at| bytes.len() - at - 1);
269
+ let mut end = bytes.len();
270
+ let mut trailing = 0;
271
+ while end > 0 && (bytes[end - 1] == b'0' || bytes[end - 1] == b'.') {
272
+ if bytes[end - 1] == b'0' {
273
+ trailing += 1;
274
+ }
275
+ end -= 1;
276
+ }
277
+ let mut numerator = 0_i128;
278
+ for &byte in &bytes[..end] {
279
+ if byte == b'.' {
280
+ continue;
281
+ }
282
+ if !byte.is_ascii_digit() {
283
+ return None;
284
+ }
285
+ numerator = numerator
286
+ .checked_mul(10)?
287
+ .checked_add(i128::from(byte.checked_sub(b'0')?))?;
288
+ }
289
+ if numerator == 0 {
290
+ return Some(Fraction {
291
+ numerator: 0,
292
+ denominator: 1,
293
+ });
294
+ }
295
+ let power = exponent + trailing - fractional as i32;
296
+ let mut denominator = 1;
297
+ if power >= 0 {
298
+ numerator = numerator.checked_mul(10_i128.checked_pow(power as u32)?)?;
299
+ } else {
300
+ denominator = 10_i128.checked_pow(power.unsigned_abs())?;
301
+ }
302
+ let (mut a, mut b) = (numerator, denominator);
303
+ while b != 0 {
304
+ (a, b) = (b, a % b);
305
+ }
306
+ Some(Fraction {
307
+ numerator: if negative {
308
+ -numerator / a
309
+ } else {
310
+ numerator / a
311
+ },
312
+ denominator: denominator / a,
313
+ })
314
+ }
315
+ #[derive(Debug)]
316
+ struct Limit {
317
+ exact: Option<Fraction>,
318
+ wide_integer: Option<Value>,
319
+ wide_rounded_order: Option<Ordering>,
320
+ rounded: f64,
321
+ integer: bool,
322
+ sum_words: bool,
323
+ }
324
+
325
+ impl Limit {
326
+ fn float_order(&self, n: f64) -> Result<Option<Ordering>, Verdict> {
327
+ if let Some(exact) = self.exact {
328
+ return Ok(compare_float_integer(n, exact.numerator));
329
+ }
330
+ let Some(tie) = self.wide_rounded_order else {
331
+ return Err(Unsupported);
332
+ };
333
+ // Adjacent Floats cannot lie inside the rounding interval of an
334
+ // Integer. Only equality with its rounded Float needs an exact tie.
335
+ Ok(n.partial_cmp(&self.rounded).map(
336
+ |order| {
337
+ if order == Ordering::Equal { tie } else { order }
338
+ },
339
+ ))
340
+ }
341
+ // Runtime callers have classified the instance as an ordinary Integer.
342
+ // Wide schema bounds remain rooted Ruby handles, with no digit export.
343
+ fn integer_order(&self, value: Value, integer: Option<i128>, sign: i32) -> Option<Ordering> {
344
+ if let Some(exact) = self.exact {
345
+ return Some(integer.map_or_else(
346
+ || {
347
+ if sign > 0 {
348
+ Ordering::Greater
349
+ } else {
350
+ Ordering::Less
351
+ }
352
+ },
353
+ |n| compare_integer(n, exact),
354
+ ));
355
+ }
356
+ self.wide_integer
357
+ .map(|bound| unsafe { sn_wide_integer_compare(value, bound) }.cmp(&0))
358
+ }
359
+ }
360
+ #[derive(Debug)]
361
+ pub(super) struct Rules {
362
+ limits: Vec<(i32, Limit)>,
363
+ multiple: Option<Limit>,
364
+ pub(super) fast_limits: Option<Vec<(i32, i64)>>,
365
+ pub(super) fast_multiple: Option<FastMultiple>,
366
+ fixnum_flag: Value,
367
+ bounds_view: bool,
368
+ bounded_signs: Option<[bool; 2]>,
369
+ }
370
+
371
+ #[derive(Debug)]
372
+ pub(super) struct FastMultiple {
373
+ numerator: i64,
374
+ limits: Vec<(i32, i64)>,
375
+ }
376
+
377
+ impl FastMultiple {
378
+ fn failures(&self, n: i64) -> i32 {
379
+ // An Integer has denominator 1. For a reduced divisor p/q, only
380
+ // divisibility by p remains; q is always divisible by 1.
381
+ let mut bits = if self.numerator > 0 && n % self.numerator == 0 {
382
+ 0
383
+ } else {
384
+ MULTIPLE_OF
385
+ };
386
+ for &(bit, limit) in &self.limits {
387
+ if match bit {
388
+ MAXIMUM => n > limit,
389
+ MINIMUM => n < limit,
390
+ EXCLUSIVE_MAXIMUM => n >= limit,
391
+ _ => n <= limit,
392
+ } {
393
+ bits |= bit;
394
+ }
395
+ }
396
+ bits
397
+ }
398
+
399
+ pub(super) fn eval(&self, n: i64) -> Verdict {
400
+ verdict(self.failures(n) == 0)
401
+ }
402
+ }
403
+
404
+ impl Engine {
405
+ pub(super) fn compile_number(&mut self, operand: Value) -> Result<Option<Rules>, Value> {
406
+ let mask = self.field(operand, b"mask\0")?;
407
+ let mask = unsafe { sn_integer(mask) } as i32;
408
+ let mut limits = vec![];
409
+ for (bit, name) in [
410
+ (MAXIMUM, b"maximum\0".as_slice()),
411
+ (MINIMUM, b"minimum\0"),
412
+ (EXCLUSIVE_MAXIMUM, b"exclusive_maximum\0"),
413
+ (EXCLUSIVE_MINIMUM, b"exclusive_minimum\0"),
414
+ ] {
415
+ if mask & bit != 0 {
416
+ let value = self.field(operand, name)?;
417
+ let Some(limit) = self.numeric_limit(value)? else {
418
+ return Ok(None);
419
+ };
420
+ limits.push((bit, limit));
421
+ }
422
+ }
423
+ let multiple = if mask & MULTIPLE_OF == 0 {
424
+ None
425
+ } else {
426
+ let value = self.field(operand, b"multiple_of\0")?;
427
+ let Some(limit) = self.numeric_limit(value)? else {
428
+ return Ok(None);
429
+ };
430
+ Some(limit)
431
+ };
432
+ let fast_limits = if multiple.is_some() {
433
+ None
434
+ } else {
435
+ limits
436
+ .iter()
437
+ .map(|(bit, limit)| {
438
+ if !limit.integer {
439
+ return None;
440
+ }
441
+ Some((*bit, i64::try_from(limit.exact?.numerator).ok()?))
442
+ })
443
+ .collect()
444
+ };
445
+ let fast_multiple = multiple
446
+ .as_ref()
447
+ .and_then(|limit| limit.exact)
448
+ .and_then(|fraction| i64::try_from(fraction.numerator).ok())
449
+ .and_then(|numerator| {
450
+ let limits = limits
451
+ .iter()
452
+ .map(|&(bit, ref limit)| {
453
+ let fraction = limit.exact?;
454
+ (fraction.denominator == 1)
455
+ .then(|| i64::try_from(fraction.numerator).ok())
456
+ .flatten()
457
+ .map(|n| (bit, n))
458
+ })
459
+ .collect::<Option<Vec<_>>>()?;
460
+ Some(FastMultiple { numerator, limits })
461
+ });
462
+ let bounds_view = multiple.is_none() && limits.iter().all(|(_, limit)| limit.integer);
463
+ let bounded_signs = (bounds_view && limits.iter().all(|(_, limit)| limit.exact.is_some()))
464
+ .then(|| {
465
+ [
466
+ !limits
467
+ .iter()
468
+ .any(|(bit, _)| matches!(*bit, MINIMUM | EXCLUSIVE_MINIMUM)),
469
+ !limits
470
+ .iter()
471
+ .any(|(bit, _)| matches!(*bit, MAXIMUM | EXCLUSIVE_MAXIMUM)),
472
+ ]
473
+ });
474
+ Ok(Some(Rules {
475
+ limits,
476
+ multiple,
477
+ fast_limits,
478
+ fast_multiple,
479
+ fixnum_flag: self.fixnum_flag,
480
+ bounds_view,
481
+ bounded_signs,
482
+ }))
483
+ }
484
+
485
+ fn numeric_limit(&mut self, value: Value) -> Result<Option<Limit>, Value> {
486
+ let integer = unsafe { sn_kind(value) } & TYPE_INTEGER != 0;
487
+ if !integer && unsafe { sn_is_rational(value) } == 0 {
488
+ return Ok(None);
489
+ }
490
+ let exact = if integer {
491
+ read_integer(value).map(|numerator| Fraction {
492
+ numerator,
493
+ denominator: 1,
494
+ })
495
+ } else {
496
+ let numerator = self.field(value, b"numerator\0")?;
497
+ let denominator = self.field(value, b"denominator\0")?;
498
+ read_integer(numerator).zip(read_integer(denominator)).map(
499
+ |(numerator, denominator)| Fraction {
500
+ numerator,
501
+ denominator,
502
+ },
503
+ )
504
+ };
505
+ let rounded = self.field(value, b"to_f\0")?;
506
+ let rounded = unsafe { sn_float(rounded) };
507
+ let wide_rounded_order = if integer && exact.is_none() {
508
+ wide_float_tie(value, rounded)
509
+ } else {
510
+ None
511
+ };
512
+ let sum_words = exact.is_some_and(|fraction| {
513
+ fraction.numerator > 1 && (1_u128 << 64) % fraction.numerator as u128 == 1
514
+ });
515
+ Ok(Some(Limit {
516
+ exact,
517
+ wide_integer: (integer && exact.is_none()).then(|| self.retain(value)),
518
+ wide_rounded_order,
519
+ rounded,
520
+ integer,
521
+ sum_words,
522
+ }))
523
+ }
524
+ }
525
+
526
+ fn read_integer(value: Value) -> Option<i128> {
527
+ read_integer_status(value).ok()
528
+ }
529
+
530
+ // Err(sign) identifies a magnitude outside i128; Err(0) is not an Integer.
531
+ fn read_integer_status(value: Value) -> Result<i128, i32> {
532
+ if unsafe { sn_fixnum(value) } != 0 {
533
+ return Ok(unsafe { sn_integer(value) } as i128);
534
+ }
535
+ if unsafe { sn_kind(value) } & TYPE_INTEGER == 0 || unsafe { sn_is_float(value) } != 0 {
536
+ return Err(0);
537
+ }
538
+ unpack_integer_status(value)
539
+ }
540
+
541
+ // Runtime numeric instructions have already rejected non-numeric instances.
542
+ // Their non-Float branch contains only ordinary Integers; compilation uses
543
+ // read_integer_status instead because schema metadata still needs checking.
544
+ fn read_known_integer_status(value: Value, fixnum_flag: Value) -> Result<i128, i32> {
545
+ if value & fixnum_flag != 0 {
546
+ return Ok(immediate_integer(value) as i128);
547
+ }
548
+ let mut words = [0_u64; 2];
549
+ let sign = unsafe { sn_integer_bounds128(value, words.as_mut_ptr()) };
550
+ integer_words_status(&words, sign)
551
+ }
552
+
553
+ fn unpack_integer_status(value: Value) -> Result<i128, i32> {
554
+ let mut words = [0_u64; 2];
555
+ let sign = unsafe { sn_integer128(value, words.as_mut_ptr()) };
556
+ integer_words_status(&words, sign)
557
+ }
558
+
559
+ fn integer_words_status(words: &[u64], sign: i32) -> Result<i128, i32> {
560
+ if sign.abs() > 1 {
561
+ return Err(sign);
562
+ }
563
+ if words[2..].iter().any(|&word| word != 0) {
564
+ return Err(sign);
565
+ }
566
+ let magnitude = u128::from(words[0]) | (u128::from(words[1]) << 64);
567
+ if sign < 0 {
568
+ if magnitude == 1_u128 << 127 {
569
+ Ok(i128::MIN)
570
+ } else {
571
+ i128::try_from(magnitude)
572
+ .map_err(|_| sign)?
573
+ .checked_neg()
574
+ .ok_or(sign)
575
+ }
576
+ } else {
577
+ i128::try_from(magnitude).map_err(|_| sign)
578
+ }
579
+ }
580
+
581
+ fn wide_integer_multiple(value: Value, divisor: Fraction, sum_words: bool) -> Verdict {
582
+ if divisor.numerator <= 0 {
583
+ return Invalid;
584
+ }
585
+ if divisor.numerator == 1 {
586
+ return Valid;
587
+ }
588
+ let mut words = [0_u64; 16];
589
+ if unsafe { sn_integer1024(value, words.as_mut_ptr()) }.abs() > 1 {
590
+ return Unsupported;
591
+ }
592
+ wide_multiple_words(&words, divisor, sum_words)
593
+ }
594
+
595
+ fn wide_multiple_words(words: &[u64; 16], divisor: Fraction, sum_words: bool) -> Verdict {
596
+ let len = words
597
+ .iter()
598
+ .rposition(|&word| word != 0)
599
+ .map_or(0, |i| i + 1);
600
+ let divisor = divisor.numerator as u128;
601
+ let mut remainder = 0_u128;
602
+ if sum_words {
603
+ // If base 2^64 is 1 modulo the divisor, a magnitude has the same
604
+ // remainder as the sum of its words. At most 16 u64 words sum below
605
+ // 2^68, so one u128 remainder replaces per-word division.
606
+ remainder = words[..len]
607
+ .iter()
608
+ .map(|&word| u128::from(word))
609
+ .sum::<u128>()
610
+ % divisor;
611
+ } else if divisor <= u64::MAX as u128 {
612
+ // remainder < divisor <= u64::MAX, so shifting it by 64 fits u128.
613
+ for &word in words[..len].iter().rev() {
614
+ remainder = ((remainder << 64) | u128::from(word)) % divisor;
615
+ }
616
+ } else {
617
+ // The positive divisor is at most i128::MAX. Doubling a remainder
618
+ // below it cannot overflow u128; consume magnitude bits without
619
+ // allocating or multiplying an arbitrary-precision integer.
620
+ for &word in words[..len].iter().rev() {
621
+ for bit in (0..64).rev() {
622
+ remainder = (remainder << 1) | u128::from((word >> bit) & 1);
623
+ if remainder >= divisor {
624
+ remainder -= divisor;
625
+ }
626
+ }
627
+ }
628
+ }
629
+ // The instance denominator is one, which divides the reduced divisor's
630
+ // denominator. Sign does not affect divisibility by its positive numerator.
631
+ verdict(remainder == 0)
632
+ }
633
+
634
+ fn fixnum_bounds(n: i64, limits: &[(i32, i64)]) -> Verdict {
635
+ verdict(limits.iter().all(|&(bit, limit)| match bit {
636
+ MAXIMUM => n <= limit,
637
+ MINIMUM => n >= limit,
638
+ EXCLUSIVE_MAXIMUM => n < limit,
639
+ _ => n > limit,
640
+ }))
641
+ }
642
+
643
+ fn compare_integer(n: i128, limit: Fraction) -> Ordering {
644
+ let whole = limit.numerator / limit.denominator;
645
+ n.cmp(&whole)
646
+ .then_with(|| 0.cmp(&(limit.numerator % limit.denominator)))
647
+ }
648
+
649
+ fn compare_float_integer(n: f64, limit: i128) -> Option<Ordering> {
650
+ if n.is_nan() {
651
+ return None;
652
+ }
653
+ if n < i128::MIN as f64 {
654
+ return Some(Ordering::Less);
655
+ }
656
+ if n >= i128::MAX as f64 {
657
+ return Some(Ordering::Greater);
658
+ }
659
+ Some(
660
+ (n as i128)
661
+ .cmp(&limit)
662
+ .then_with(|| n.fract().partial_cmp(&0.0).unwrap()),
663
+ )
664
+ }
665
+
666
+ // Schema metadata only: record the comparison at the rounded Float once.
667
+ // Every finite binary64 magnitude fits 1,024 bits. No instance digit export
668
+ // or Ruby allocation is needed at runtime, and no word buffer is retained.
669
+ fn wide_float_tie(value: Value, rounded: f64) -> Option<Ordering> {
670
+ if rounded.is_infinite() {
671
+ return Some(if rounded.is_sign_negative() {
672
+ Ordering::Less
673
+ } else {
674
+ Ordering::Greater
675
+ });
676
+ }
677
+ let bits = rounded.to_bits();
678
+ let exponent = ((bits >> 52) & 0x7ff) as usize;
679
+ if !(1023 + 127..2047).contains(&exponent) {
680
+ return None;
681
+ }
682
+ let mut words = [0_u64; 16];
683
+ if unsafe { sn_integer1024(value, words.as_mut_ptr()) }.abs() > 1 {
684
+ return None;
685
+ }
686
+ let shift = exponent - 1023 - 52;
687
+ let magnitude = u128::from((bits & ((1_u64 << 52) - 1)) | (1_u64 << 52)) << (shift % 64);
688
+ let mut float_words = [0_u64; 16];
689
+ float_words[shift / 64] = magnitude as u64;
690
+ if shift / 64 + 1 < float_words.len() {
691
+ float_words[shift / 64 + 1] = (magnitude >> 64) as u64;
692
+ }
693
+ let order = float_words.iter().rev().cmp(words.iter().rev());
694
+ Some(if rounded.is_sign_negative() {
695
+ order.reverse()
696
+ } else {
697
+ order
698
+ })
699
+ }
700
+
701
+ impl Rules {
702
+ // Shared by validity scans and diagnostic prechecks. Numeric traversal
703
+ // borrows only opaque VALUEs and uses the compiled primitive shortcuts.
704
+ pub(super) fn eval_guarded(&self, required: i32, value: Value) -> Verdict {
705
+ let fixnum = value & self.fixnum_flag != 0;
706
+ if fixnum {
707
+ if required != 0 && required & TYPE_INTEGRAL_NUMBER == 0 {
708
+ return Invalid;
709
+ }
710
+ if let Some(multiple) = &self.fast_multiple {
711
+ return multiple.eval(immediate_integer(value));
712
+ }
713
+ if let Some(limits) = &self.fast_limits {
714
+ return fixnum_bounds(immediate_integer(value), limits);
715
+ }
716
+ }
717
+ if self.bounds_view {
718
+ return self.eval_bounds_guarded(required, value);
719
+ }
720
+ let kind = if fixnum {
721
+ TYPE_INTEGRAL_NUMBER
722
+ } else {
723
+ unsafe { sn_kind(value) }
724
+ };
725
+ if kind == TYPE_UNSUPPORTED {
726
+ Unsupported
727
+ } else if required != 0 && kind & required == 0 {
728
+ Invalid
729
+ } else if kind & TYPE_NUMBER == 0 {
730
+ Valid
731
+ } else {
732
+ self.eval(value)
733
+ }
734
+ }
735
+
736
+ pub(super) fn eval_bounds_guarded(&self, required: i32, value: Value) -> Verdict {
737
+ if let [(bit, limit)] = self.limits.as_slice()
738
+ && let Some(bound) = limit.wide_integer
739
+ {
740
+ // Both original handles are checked by the nonallocating Big/Big
741
+ // comparator. No magnitude export is needed for a single bound.
742
+ // Other kinds retain the scalar/type checks and Float tie rules.
743
+ let order = unsafe { sn_big_order(value, bound) };
744
+ if order != 3 {
745
+ return verdict(
746
+ (required == 0 || required & TYPE_INTEGRAL_NUMBER != 0)
747
+ && match *bit {
748
+ MAXIMUM => order <= 0,
749
+ MINIMUM => order >= 0,
750
+ EXCLUSIVE_MAXIMUM => order < 0,
751
+ _ => order > 0,
752
+ },
753
+ );
754
+ }
755
+ }
756
+ let mut words = std::mem::MaybeUninit::<[u64; 2]>::uninit();
757
+ let mut kind = std::mem::MaybeUninit::<i32>::uninit();
758
+ let mut floating = std::mem::MaybeUninit::<f64>::uninit();
759
+ let sign = unsafe {
760
+ sn_number_bounds_view(
761
+ value,
762
+ words.as_mut_ptr().cast(),
763
+ kind.as_mut_ptr(),
764
+ floating.as_mut_ptr(),
765
+ required,
766
+ )
767
+ };
768
+ // The adapter always initializes kind, but only initializes the
769
+ // operand selected by its kind and sign. Never read unused outputs.
770
+ let kind = unsafe { kind.assume_init() };
771
+ if kind == TYPE_UNSUPPORTED {
772
+ return Unsupported;
773
+ }
774
+ if required != 0 && kind & required == 0 {
775
+ return Invalid;
776
+ }
777
+ if kind & TYPE_NUMBER == 0 {
778
+ return Valid;
779
+ }
780
+ if sign == ADAPTER_UNSUPPORTED {
781
+ let floating = unsafe { floating.assume_init() };
782
+ for &(bit, ref limit) in &self.limits {
783
+ let order = match limit.float_order(floating) {
784
+ Ok(order) => order,
785
+ Err(status) => return status,
786
+ };
787
+ if match bit {
788
+ MAXIMUM => order == Some(Ordering::Greater),
789
+ MINIMUM => order == Some(Ordering::Less),
790
+ EXCLUSIVE_MAXIMUM => matches!(order, Some(Ordering::Greater | Ordering::Equal)),
791
+ _ => matches!(order, Some(Ordering::Less | Ordering::Equal)),
792
+ } {
793
+ return Invalid;
794
+ }
795
+ }
796
+ return Valid;
797
+ }
798
+ let integer = if sign.abs() > 1 {
799
+ None
800
+ } else {
801
+ integer_words_status(&unsafe { words.assume_init() }, sign).ok()
802
+ };
803
+ if integer.is_none()
804
+ && let Some(signs) = self.bounded_signs
805
+ {
806
+ // These Integer limits all fit i128. Every larger magnitude is
807
+ // strictly outside them; compilation already combined their bits.
808
+ return verdict(signs[(sign > 0) as usize]);
809
+ }
810
+ for &(bit, ref limit) in &self.limits {
811
+ let Some(order) = limit.integer_order(value, integer, sign) else {
812
+ return Unsupported;
813
+ };
814
+ if match bit {
815
+ MAXIMUM => order == Ordering::Greater,
816
+ MINIMUM => order == Ordering::Less,
817
+ EXCLUSIVE_MAXIMUM => order != Ordering::Less,
818
+ _ => order != Ordering::Greater,
819
+ } {
820
+ return Invalid;
821
+ }
822
+ }
823
+ Valid
824
+ }
825
+
826
+ pub(super) fn eval(&self, value: Value) -> Verdict {
827
+ if let Some(multiple) = &self.fast_multiple
828
+ && value & self.fixnum_flag != 0
829
+ {
830
+ return multiple.eval(immediate_integer(value));
831
+ }
832
+ if let Some(limits) = &self.fast_limits
833
+ && value & self.fixnum_flag != 0
834
+ {
835
+ return fixnum_bounds(immediate_integer(value), limits);
836
+ }
837
+ self.check(value, |_| true)
838
+ }
839
+
840
+ pub(super) fn failures(&self, value: Value) -> Result<i32, Verdict> {
841
+ if let Some(multiple) = &self.fast_multiple
842
+ && value & self.fixnum_flag != 0
843
+ {
844
+ return Ok(multiple.failures(immediate_integer(value)));
845
+ }
846
+ if let Some(limits) = &self.fast_limits
847
+ && value & self.fixnum_flag != 0
848
+ {
849
+ let n = immediate_integer(value);
850
+ return Ok(limits.iter().fold(0, |bits, &(bit, limit)| {
851
+ bits | if match bit {
852
+ MAXIMUM => n > limit,
853
+ MINIMUM => n < limit,
854
+ EXCLUSIVE_MAXIMUM => n >= limit,
855
+ _ => n <= limit,
856
+ } {
857
+ bit
858
+ } else {
859
+ 0
860
+ }
861
+ }));
862
+ }
863
+ let mut bits = 0;
864
+ let status = self.check(value, |bit| {
865
+ bits |= bit;
866
+ false
867
+ });
868
+ if status == Unsupported {
869
+ Err(status)
870
+ } else {
871
+ Ok(bits)
872
+ }
873
+ }
874
+
875
+ fn check_wide_multiple(
876
+ &self,
877
+ value: Value,
878
+ divisor: Fraction,
879
+ sum_words: bool,
880
+ mut failure: impl FnMut(i32) -> bool,
881
+ ) -> Verdict {
882
+ let mut words = [0_u64; 16];
883
+ let sign = unsafe { sn_integer1024(value, words.as_mut_ptr()) };
884
+ let integer = integer_words_status(&words, sign).ok();
885
+ let mut invalid = false;
886
+ for &(bit, ref limit) in &self.limits {
887
+ let Some(order) = limit.integer_order(value, integer, sign) else {
888
+ return Unsupported;
889
+ };
890
+ if match bit {
891
+ MAXIMUM => order == Ordering::Greater,
892
+ MINIMUM => order == Ordering::Less,
893
+ EXCLUSIVE_MAXIMUM => order != Ordering::Less,
894
+ _ => order != Ordering::Greater,
895
+ } {
896
+ invalid = true;
897
+ if failure(bit) {
898
+ return Invalid;
899
+ }
900
+ }
901
+ }
902
+ let result = if let Some(n) = integer {
903
+ verdict(
904
+ Fraction {
905
+ numerator: n,
906
+ denominator: 1,
907
+ }
908
+ .multiple_of(divisor),
909
+ )
910
+ } else if sign.abs() > 1 {
911
+ Unsupported
912
+ } else {
913
+ wide_multiple_words(&words, divisor, sum_words)
914
+ };
915
+ match result {
916
+ Unsupported => return Unsupported,
917
+ Invalid => {
918
+ invalid = true;
919
+ failure(MULTIPLE_OF);
920
+ }
921
+ Valid => {}
922
+ }
923
+ verdict(!invalid)
924
+ }
925
+
926
+ fn check(&self, value: Value, mut failure: impl FnMut(i32) -> bool) -> Verdict {
927
+ let mut invalid = false;
928
+ let float = unsafe { sn_is_float(value) } != 0;
929
+ if !float
930
+ && value & self.fixnum_flag == 0
931
+ && let Some(limit) = &self.multiple
932
+ && let Some(exact) = limit.exact
933
+ && exact.numerator > 1
934
+ {
935
+ return self.check_wide_multiple(value, exact, limit.sum_words, failure);
936
+ }
937
+ let (integer, large_sign) = if float {
938
+ (None, 0)
939
+ } else {
940
+ match read_known_integer_status(value, self.fixnum_flag) {
941
+ Ok(integer) => (Some(integer), 0),
942
+ Err(sign) => (None, sign),
943
+ }
944
+ };
945
+ if !float && integer.is_none() {
946
+ // An ordinary Integer outside i128 is beyond every representable
947
+ // exact limit (whose denominator is positive). Bounds alone need
948
+ // only its sign. Multiples can use a bounded magnitude below.
949
+ if large_sign == 0 {
950
+ return Unsupported;
951
+ }
952
+ for (bit, limit) in &self.limits {
953
+ let Some(order) = limit.integer_order(value, None, large_sign) else {
954
+ return Unsupported;
955
+ };
956
+ let violates = match *bit {
957
+ MAXIMUM => order == Ordering::Greater,
958
+ MINIMUM => order == Ordering::Less,
959
+ EXCLUSIVE_MAXIMUM => order != Ordering::Less,
960
+ _ => order != Ordering::Greater,
961
+ };
962
+ if violates {
963
+ invalid = true;
964
+ if failure(*bit) {
965
+ return Invalid;
966
+ }
967
+ }
968
+ }
969
+ if let Some(limit) = &self.multiple {
970
+ let Some(exact) = limit.exact else {
971
+ return Unsupported;
972
+ };
973
+ match wide_integer_multiple(value, exact, limit.sum_words) {
974
+ Unsupported => return Unsupported,
975
+ Invalid => {
976
+ invalid = true;
977
+ failure(MULTIPLE_OF);
978
+ }
979
+ Valid => {}
980
+ }
981
+ }
982
+ return verdict(!invalid);
983
+ }
984
+ let decimal = if self.multiple.is_some() {
985
+ if let Some(integer) = integer {
986
+ Some(Fraction {
987
+ numerator: integer,
988
+ denominator: 1,
989
+ })
990
+ } else {
991
+ let Some(decimal) = decimal_float(unsafe { sn_float(value) }) else {
992
+ return Unsupported;
993
+ };
994
+ Some(decimal)
995
+ }
996
+ } else {
997
+ None
998
+ };
999
+ for (bit, limit) in &self.limits {
1000
+ let order = if integer.is_some() {
1001
+ let Some(order) = limit.integer_order(value, integer, 0) else {
1002
+ return Unsupported;
1003
+ };
1004
+ Some(order)
1005
+ } else if let Some(decimal) = decimal {
1006
+ if let Some(exact) = limit.exact {
1007
+ Some(decimal.compare(exact))
1008
+ } else if limit.wide_integer.is_some() {
1009
+ // The reduced decimal numerator fits i128 and its
1010
+ // denominator is positive. A bound outside i128 is
1011
+ // strictly beyond it; no Float/Big comparison is needed.
1012
+ Some(if limit.rounded.is_sign_negative() {
1013
+ Ordering::Greater
1014
+ } else {
1015
+ Ordering::Less
1016
+ })
1017
+ } else {
1018
+ return Unsupported;
1019
+ }
1020
+ } else {
1021
+ let n = unsafe { sn_float(value) };
1022
+ if limit.integer {
1023
+ match limit.float_order(n) {
1024
+ Ok(order) => order,
1025
+ Err(status) => return status,
1026
+ }
1027
+ } else {
1028
+ n.partial_cmp(&limit.rounded)
1029
+ }
1030
+ };
1031
+ let violates = match *bit {
1032
+ MAXIMUM => order == Some(Ordering::Greater),
1033
+ MINIMUM => order == Some(Ordering::Less),
1034
+ EXCLUSIVE_MAXIMUM => matches!(order, Some(Ordering::Greater | Ordering::Equal)),
1035
+ _ => matches!(order, Some(Ordering::Less | Ordering::Equal)),
1036
+ };
1037
+ if violates {
1038
+ invalid = true;
1039
+ if failure(*bit) {
1040
+ return Invalid;
1041
+ }
1042
+ }
1043
+ }
1044
+ if let Some(limit) = &self.multiple {
1045
+ let Some(exact) = limit.exact else {
1046
+ return Unsupported;
1047
+ };
1048
+ if !decimal.unwrap().multiple_of(exact) {
1049
+ invalid = true;
1050
+ failure(MULTIPLE_OF);
1051
+ }
1052
+ }
1053
+ verdict(!invalid)
1054
+ }
1055
+ }