Skip to main content

nanvm_lib/vm/bigint/
shr.rs

1use core::ops::Shr;
2
3use crate::{
4    common::sized_index::SizedIndex,
5    sign::Sign,
6    vm::{Any, BigInt, IVm},
7};
8
9use super::{ShiftAmount, add_one};
10
11/// The result of shifting a nonzero-magnitude `self` right by a shift
12/// amount that reaches or exceeds `self`'s own bit length, i.e. the entire
13/// magnitude is shifted away: exactly `0` for a non-negative `self`, since
14/// truncating division floors toward zero already; exactly `-1` for a
15/// negative one, since flooring a negative quotient toward `-infinity`
16/// never reaches `0` no matter how far right the shift goes (the same
17/// reason `-1n >> 1_000_000n` is `-1n` in JS, not `0n`).
18fn shifted_to_extreme<A: IVm>(sign: Sign) -> BigInt<A> {
19    match sign {
20        Sign::Positive => BigInt::default(),
21        Sign::Negative => BigInt::unchecked_new(Sign::Negative, [1u64]),
22    }
23}
24
25/// `>>`. <https://tc39.es/ecma262/#sec-numeric-types-bigint-signedRightShift>
26///
27/// Conceptually a shift over `self`'s infinite-precision two's-complement
28/// bit string — equivalently, floor division by `2^rhs` — not a shift of
29/// `self`'s magnitude with the sign carried along unchanged: those agree
30/// for a non-negative `self` (floor division truncates toward zero there
31/// anyway) but not for a negative one, where flooring rounds *away* from
32/// zero whenever a nonzero bit is shifted out. `-1n >> 1n` is `-1n` (all
33/// bits are `1`, so shifting right forever still reads all `1`s), not `0n`
34/// the way carrying the sign over a plain magnitude shift would give.
35impl<A: IVm> Shr for BigInt<A> {
36    type Output = Result<Self, Any<A>>;
37
38    fn shr(self, rhs: Self) -> Self::Output {
39        // A negative shift amount is a left shift by its magnitude.
40        if rhs.sign() == Sign::Negative {
41            return self << -rhs;
42        }
43
44        let sign = self.sign();
45        let (word_shift, bit_shift) = match self.shift_amount(&rhs) {
46            ShiftAmount::Noop => return Ok(self),
47            // `rhs` alone (>= 2^64) already exceeds any representable
48            // `self`'s bit length.
49            ShiftAmount::TooWide => return Ok(shifted_to_extreme(sign)),
50            ShiftAmount::Words(word_shift, bit_shift) => (word_shift, bit_shift),
51        };
52        let n_len = self.length();
53        if word_shift >= n_len as u64 {
54            return Ok(shifted_to_extreme(sign));
55        }
56        let word_shift = word_shift as u32;
57
58        // Whether any bit being shifted away is set — the "was this exact"
59        // check flooring a negative result needs.
60        let sticky = (0..word_shift).any(|i| self[i] != 0)
61            || (bit_shift > 0 && self[word_shift] & ((1u64 << bit_shift) - 1) != 0);
62
63        let mut value: Vec<u64> = (word_shift..n_len).map(|i| self[i]).collect();
64        if bit_shift > 0 {
65            let mut carry = 0u64;
66            for digit in value.iter_mut().rev() {
67                let new_carry = *digit << (64 - bit_shift);
68                *digit = (*digit >> bit_shift) | carry;
69                carry = new_carry;
70            }
71        }
72
73        if sign == Sign::Negative && sticky {
74            // Round the magnitude up (away from zero): see the doc comment
75            // above for why.
76            add_one(&mut value);
77        }
78
79        Ok(Self::normalize_new(sign, value))
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use crate::vm::bigint::test_util::{T, neg, pos};
86
87    #[test]
88    fn zero_shr_zero() {
89        let a: T = 0u64.into();
90        let b: T = 0u64.into();
91        assert_eq!((a >> b).unwrap(), T::default());
92    }
93
94    #[test]
95    fn value_shr_zero() {
96        let a: T = 42u64.into();
97        let b: T = 0u64.into();
98        assert_eq!((a >> b).unwrap(), 42u64.into());
99    }
100
101    #[test]
102    fn zero_shr_value() {
103        let a: T = 0u64.into();
104        let b: T = 5u64.into();
105        assert_eq!((a >> b).unwrap(), T::default());
106    }
107
108    #[test]
109    fn shr_by_one() {
110        let a: T = 100u64.into();
111        let b: T = 1u64.into();
112        assert_eq!((a >> b).unwrap(), 50u64.into());
113    }
114
115    #[test]
116    fn shr_odd_number() {
117        // Positive `self`: floor division truncates toward zero, same as a
118        // plain magnitude shift — the lost bit is simply dropped.
119        let a: T = 101u64.into();
120        let b: T = 1u64.into();
121        assert_eq!((a >> b).unwrap(), 50u64.into());
122    }
123
124    #[test]
125    fn shr_full_word() {
126        // [0, 1] represents 2^64; shifting right by 64 gives 1
127        let a = pos(vec![0, 1]);
128        let b: T = 64u64.into();
129        assert_eq!((a >> b).unwrap(), 1u64.into());
130    }
131
132    #[test]
133    fn shr_more_than_word() {
134        // [0, 4] represents 4 * 2^64; shifting right by 65 gives 2
135        let a = pos(vec![0, 4]);
136        let b: T = 65u64.into();
137        assert_eq!((a >> b).unwrap(), 2u64.into());
138    }
139
140    #[test]
141    fn shr_shift_exceeds_length() {
142        let a: T = 1u64.into();
143        let b: T = 64u64.into();
144        assert_eq!((a >> b).unwrap(), T::default());
145    }
146
147    #[test]
148    fn shr_shift_exceeds_length_large() {
149        let a: T = 1u64.into();
150        let b: T = 128u64.into();
151        assert_eq!((a >> b).unwrap(), T::default());
152    }
153
154    #[test]
155    fn shr_multi_word_shift_amount() {
156        // rhs with more than one word always shifts the entire magnitude
157        // away; for a positive `self` that floors to zero.
158        let a: T = u64::MAX.into();
159        let b = pos(vec![0, 1]);
160        assert_eq!((a >> b).unwrap(), T::default());
161    }
162
163    #[test]
164    fn shr_multi_word_shift_amount_negative_floors_to_minus_one() {
165        // Same shift-amount shape as above, but a negative `self` floors to
166        // -1, not 0 — flooring a negative quotient never reaches zero.
167        let a: T = (-1i64).into();
168        let b = pos(vec![0, 1]);
169        assert_eq!((a >> b).unwrap(), neg(vec![1]));
170    }
171
172    #[test]
173    fn shr_preserves_positive_sign() {
174        let a = pos(vec![200]);
175        let b: T = 1u64.into();
176        let result = (a >> b).unwrap();
177        assert_eq!(result, 100u64.into());
178    }
179
180    #[test]
181    fn shr_preserves_negative_sign() {
182        // Evenly divisible: flooring and truncating agree, no rounding needed.
183        let a = neg(vec![200]);
184        let b: T = 1u64.into();
185        assert_eq!((a >> b).unwrap(), neg(vec![100]));
186    }
187
188    #[test]
189    fn shr_cross_word_boundary() {
190        // [3, 1] >> 1: bit 0 of word 1 shifts into MSB of word 0
191        // word 0: (3 >> 1) | (1 << 63) = 1 | 0x8000..0 = 0x8000..0001
192        // word 1: 1 >> 1 = 0 (normalized away)
193        let a = pos(vec![3, 1]);
194        let b: T = 1u64.into();
195        assert_eq!((a >> b).unwrap(), pos(vec![0x8000_0000_0000_0001]));
196    }
197
198    #[test]
199    fn shr_normalizes_result() {
200        // [0, 1] >> 64 = 1 (should not have trailing zero words)
201        let a = pos(vec![0, 1]);
202        let b: T = 64u64.into();
203        let result = (a >> b).unwrap();
204        assert_eq!(result, 1u64.into());
205    }
206
207    #[test]
208    fn shr_large_bit_shift() {
209        // 0xFF >> 4 = 0xF
210        let a: T = 0xFFu64.into();
211        let b: T = 4u64.into();
212        assert_eq!((a >> b).unwrap(), 0xFu64.into());
213    }
214
215    #[test]
216    fn shr_63_bits() {
217        let a: T = (1u64 << 63).into();
218        let b: T = 63u64.into();
219        assert_eq!((a >> b).unwrap(), 1u64.into());
220    }
221
222    #[test]
223    fn shr_three_words_by_two_words() {
224        // [0, 0, 7] represents 7 * 2^128; >> 128 gives 7
225        let a = pos(vec![0, 0, 7]);
226        let b: T = 128u64.into();
227        assert_eq!((a >> b).unwrap(), 7u64.into());
228    }
229
230    #[test]
231    fn shr_three_words_by_word_plus_bits() {
232        // [0, 0, 0x80] >> 65 = [0, 0x40]
233        let a = pos(vec![0, 0, 0x80]);
234        let b: T = 65u64.into();
235        assert_eq!((a >> b).unwrap(), pos(vec![0, 0x40]));
236    }
237
238    #[test]
239    fn shr_by_u64_max() {
240        let a = pos(vec![u64::MAX, u64::MAX, u64::MAX]);
241        let b: T = u64::MAX.into();
242        assert_eq!((a >> b).unwrap(), T::default());
243    }
244
245    #[test]
246    fn shr_four_words_by_192() {
247        // [0, 0, 0, 5] >> 192 = 5
248        let a = pos(vec![0, 0, 0, 5]);
249        let b: T = 192u64.into();
250        assert_eq!((a >> b).unwrap(), 5u64.into());
251    }
252
253    #[test]
254    fn shr_negative_floors_instead_of_truncating_to_zero() {
255        // -1n >> 64n is -1n in JS, not 0n: -1's infinite two's-complement
256        // pattern is all `1`s, so shifting it right (with sign extension)
257        // by any amount still reads all `1`s.
258        let a = neg(vec![1]);
259        let b: T = 64u64.into();
260        assert_eq!((a >> b).unwrap(), neg(vec![1]));
261    }
262
263    #[test]
264    fn shr_multi_word_carry_propagation() {
265        // [0, 0, 3] >> 1
266        // carry starts at 0.
267        // word 2: 3 >> 1 = 1, carry = (3 << 63) as u64 = 0x8000_0000_0000_0000
268        // word 1: (0 >> 1) | 0x8000_0000_0000_0000, carry = 0
269        // word 0: (0 >> 1) | 0 = 0
270        let a = pos(vec![0, 0, 3]);
271        let b: T = 1u64.into();
272        let expected = pos(vec![0, 0x8000_0000_0000_0000, 1]);
273        assert_eq!((a >> b).unwrap(), expected);
274    }
275
276    #[test]
277    fn shr_exact_word_boundary_no_remainder() {
278        // [42, 99] >> 64 = 99
279        let a = pos(vec![42, 99]);
280        let b: T = 64u64.into();
281        assert_eq!((a >> b).unwrap(), 99u64.into());
282    }
283
284    #[test]
285    fn shr_large_single_word_shift_no_truncation() {
286        // shift = (u32::MAX as u64 + 1) * 64 = 274877906944
287        // word_shift = 2^32 which exceeds any u32 n_len; result must be zero.
288        let a = pos(vec![u64::MAX, u64::MAX]);
289        let b: T = 274_877_906_944u64.into();
290        assert_eq!((a >> b).unwrap(), T::default());
291    }
292
293    #[test]
294    fn shr_negative_rounds_up_on_bit_shift_remainder() {
295        // neg([1]) >> 1: the single set bit is shifted away, so the
296        // magnitude rounds up from 0 to 1 before negating — floor(-1/2) is
297        // -1, not -0 (which BigInt doesn't have) or 0.
298        let a = neg(vec![1]);
299        let b: T = 1u64.into();
300        assert_eq!((a >> b).unwrap(), neg(vec![1]));
301    }
302
303    #[test]
304    fn shr_negative_word_shift_with_remainder_rounds_up() {
305        // magnitude = 7 * 2^64 + 5; >> 64 drops the low word (5, nonzero)
306        // entirely, so the remaining magnitude (7) rounds up to 8:
307        // floor(-(7 * 2^64 + 5) / 2^64) = floor(-7.000...) = -8.
308        let a = neg(vec![5, 7]);
309        let b: T = 64u64.into();
310        assert_eq!((a >> b).unwrap(), neg(vec![8]));
311    }
312
313    #[test]
314    fn shr_negative_by_negative_shift_is_left_shift() {
315        // x >> -y is x << y.
316        let a = neg(vec![5]);
317        let b: T = (-3i64).into();
318        assert_eq!((a >> b).unwrap(), neg(vec![40]));
319    }
320
321    #[test]
322    fn shr_by_very_negative_shift_delegates_to_too_large_shl() {
323        // shift = -(2^64): `self >> shift` is `self << 2^64`, which needs
324        // ~2^58 result words — well past the `bigint_too_large` guard `<<` already
325        // has, reached here through `>>`'s negative-shift delegation.
326        let a: T = 1u64.into();
327        let b = neg(vec![0, 1]);
328        assert!((a >> b).is_err());
329    }
330}