Skip to main content

const_num_traits/ops/
mixed.rs

1//! Mixed-signedness arithmetic: adding/subtracting a signed value to an
2//! unsigned one and vice versa, plus the signed difference of unsigned
3//! values. Mirrors the `*_add_signed` / `*_sub_signed` / `*_add_unsigned` /
4//! `*_sub_unsigned` / `checked_signed_diff` inherent methods.
5//!
6//! These follow the crate-wide flavor-per-trait convention
7//! (`CheckedAddSigned`, `WrappingAddSigned`, …): one method per trait, so a
8//! type can implement exactly the flavors it supports — e.g. a
9//! constant-time type implements only the `Wrapping*` (Tier A) and
10//! `Overflowing*`/`Checked*` (Tier B) members and never has to expose the
11//! panicking `Strict*` ones.
12//!
13//! Stability in std: the `add_signed` and `add/sub_unsigned` families are
14//! stable since 1.66 (delegated); the `sub_signed` family (1.90), the
15//! `strict_*` variants and `checked_signed_diff` (both 1.91) are newer than
16//! the crate's MSRV and hand-rolled with the same algorithm as core.
17//!
18//! **CT tiers**: the `Wrapping*`/`Overflowing*`/`Saturating*` flavors are
19//! Tier A, the `Checked*` flavors Tier B, the `Strict*` flavors Tier C.
20
21/// Declares a one-method mixed-signedness trait with a counterpart
22/// associated type.
23macro_rules! declare_mixed_trait {
24    (
25        $(#[$tdoc:meta])*
26        trait $name:ident, type $assoc:ident;
27        $(#[$mdoc:meta])*
28        fn $method:ident -> $ret:ty;
29    ) => {
30        c0nst::c0nst! {
31        $(#[$tdoc])*
32        pub c0nst trait $name: Sized {
33            /// The counterpart type of opposite signedness.
34            type $assoc;
35
36            /// The (owned) result type.
37            type Output;
38
39            $(#[$mdoc])*
40            fn $method(self, rhs: Self::$assoc) -> $ret;
41        }
42        }
43    };
44}
45
46// ---- unsigned receivers, signed right-hand side ----
47
48declare_mixed_trait! {
49    /// Checked addition of a signed value to an unsigned value.
50    trait CheckedAddSigned, type Signed;
51    /// Computes `self + rhs`, returning `None` if overflow occurred.
52    ///
53    /// ```
54    /// use const_num_traits::CheckedAddSigned;
55    ///
56    /// assert_eq!(CheckedAddSigned::checked_add_signed(1u8, 2), Some(3));
57    /// assert_eq!(CheckedAddSigned::checked_add_signed(1u8, -2), None);
58    /// ```
59    fn checked_add_signed -> Option<Self::Output>;
60}
61
62declare_mixed_trait! {
63    /// Wrapping (modular) addition of a signed value to an unsigned value.
64    trait WrappingAddSigned, type Signed;
65    /// Computes `self + rhs`, wrapping around at the boundary of the type.
66    fn wrapping_add_signed -> Self::Output;
67}
68
69declare_mixed_trait! {
70    /// Overflowing addition of a signed value to an unsigned value.
71    trait OverflowingAddSigned, type Signed;
72    /// Computes `self + rhs`, returning the wrapped sum and whether overflow
73    /// occurred.
74    fn overflowing_add_signed -> (Self::Output, bool);
75}
76
77declare_mixed_trait! {
78    /// Saturating addition of a signed value to an unsigned value.
79    trait SaturatingAddSigned, type Signed;
80    /// Computes `self + rhs`, saturating at the numeric bounds.
81    fn saturating_add_signed -> Self::Output;
82}
83
84declare_mixed_trait! {
85    /// Strict addition of a signed value to an unsigned value.
86    trait StrictAddSigned, type Signed;
87    /// Computes `self + rhs`, panicking on overflow even in release builds.
88    fn strict_add_signed -> Self::Output;
89}
90
91declare_mixed_trait! {
92    /// Checked subtraction of a signed value from an unsigned value.
93    trait CheckedSubSigned, type Signed;
94    /// Computes `self - rhs`, returning `None` if overflow occurred.
95    ///
96    /// ```
97    /// use const_num_traits::CheckedSubSigned;
98    ///
99    /// assert_eq!(CheckedSubSigned::checked_sub_signed(1u8, 2), None);
100    /// assert_eq!(CheckedSubSigned::checked_sub_signed(1u8, -2), Some(3));
101    /// ```
102    fn checked_sub_signed -> Option<Self::Output>;
103}
104
105declare_mixed_trait! {
106    /// Wrapping (modular) subtraction of a signed value from an unsigned value.
107    trait WrappingSubSigned, type Signed;
108    /// Computes `self - rhs`, wrapping around at the boundary of the type.
109    fn wrapping_sub_signed -> Self::Output;
110}
111
112declare_mixed_trait! {
113    /// Overflowing subtraction of a signed value from an unsigned value.
114    trait OverflowingSubSigned, type Signed;
115    /// Computes `self - rhs`, returning the wrapped difference and whether
116    /// overflow occurred.
117    fn overflowing_sub_signed -> (Self::Output, bool);
118}
119
120declare_mixed_trait! {
121    /// Saturating subtraction of a signed value from an unsigned value.
122    trait SaturatingSubSigned, type Signed;
123    /// Computes `self - rhs`, saturating at the numeric bounds.
124    fn saturating_sub_signed -> Self::Output;
125}
126
127declare_mixed_trait! {
128    /// Strict subtraction of a signed value from an unsigned value.
129    trait StrictSubSigned, type Signed;
130    /// Computes `self - rhs`, panicking on overflow even in release builds.
131    fn strict_sub_signed -> Self::Output;
132}
133
134// ---- signed receivers, unsigned right-hand side ----
135
136declare_mixed_trait! {
137    /// Checked addition of an unsigned value to a signed value.
138    trait CheckedAddUnsigned, type Unsigned;
139    /// Computes `self + rhs`, returning `None` if overflow occurred.
140    ///
141    /// ```
142    /// use const_num_traits::CheckedAddUnsigned;
143    ///
144    /// assert_eq!(CheckedAddUnsigned::checked_add_unsigned(1i8, 2), Some(3));
145    /// assert_eq!(CheckedAddUnsigned::checked_add_unsigned(i8::MAX, 1), None);
146    /// ```
147    fn checked_add_unsigned -> Option<Self::Output>;
148}
149
150declare_mixed_trait! {
151    /// Wrapping (modular) addition of an unsigned value to a signed value.
152    trait WrappingAddUnsigned, type Unsigned;
153    /// Computes `self + rhs`, wrapping around at the boundary of the type.
154    fn wrapping_add_unsigned -> Self::Output;
155}
156
157declare_mixed_trait! {
158    /// Overflowing addition of an unsigned value to a signed value.
159    trait OverflowingAddUnsigned, type Unsigned;
160    /// Computes `self + rhs`, returning the wrapped sum and whether overflow
161    /// occurred.
162    fn overflowing_add_unsigned -> (Self::Output, bool);
163}
164
165declare_mixed_trait! {
166    /// Saturating addition of an unsigned value to a signed value.
167    trait SaturatingAddUnsigned, type Unsigned;
168    /// Computes `self + rhs`, saturating at the numeric bounds.
169    fn saturating_add_unsigned -> Self::Output;
170}
171
172declare_mixed_trait! {
173    /// Strict addition of an unsigned value to a signed value.
174    trait StrictAddUnsigned, type Unsigned;
175    /// Computes `self + rhs`, panicking on overflow even in release builds.
176    fn strict_add_unsigned -> Self::Output;
177}
178
179declare_mixed_trait! {
180    /// Checked subtraction of an unsigned value from a signed value.
181    trait CheckedSubUnsigned, type Unsigned;
182    /// Computes `self - rhs`, returning `None` if overflow occurred.
183    ///
184    /// ```
185    /// use const_num_traits::CheckedSubUnsigned;
186    ///
187    /// assert_eq!(CheckedSubUnsigned::checked_sub_unsigned(1i8, 2), Some(-1));
188    /// assert_eq!(CheckedSubUnsigned::checked_sub_unsigned(i8::MIN, 1), None);
189    /// ```
190    fn checked_sub_unsigned -> Option<Self::Output>;
191}
192
193declare_mixed_trait! {
194    /// Wrapping (modular) subtraction of an unsigned value from a signed value.
195    trait WrappingSubUnsigned, type Unsigned;
196    /// Computes `self - rhs`, wrapping around at the boundary of the type.
197    fn wrapping_sub_unsigned -> Self::Output;
198}
199
200declare_mixed_trait! {
201    /// Overflowing subtraction of an unsigned value from a signed value.
202    trait OverflowingSubUnsigned, type Unsigned;
203    /// Computes `self - rhs`, returning the wrapped difference and whether
204    /// overflow occurred.
205    fn overflowing_sub_unsigned -> (Self::Output, bool);
206}
207
208declare_mixed_trait! {
209    /// Saturating subtraction of an unsigned value from a signed value.
210    trait SaturatingSubUnsigned, type Unsigned;
211    /// Computes `self - rhs`, saturating at the numeric bounds.
212    fn saturating_sub_unsigned -> Self::Output;
213}
214
215declare_mixed_trait! {
216    /// Strict subtraction of an unsigned value from a signed value.
217    trait StrictSubUnsigned, type Unsigned;
218    /// Computes `self - rhs`, panicking on overflow even in release builds.
219    fn strict_sub_unsigned -> Self::Output;
220}
221
222// ---- impls ----
223
224macro_rules! mixed_signed_impl {
225    ($($t:ty => $s:ty;)*) => {$(
226        c0nst::c0nst! {
227        c0nst impl CheckedAddSigned for $t {
228            type Signed = $s;
229            type Output = $t;
230
231            #[inline]
232            fn checked_add_signed(self, rhs: $s) -> Option<$t> {
233                <$t>::checked_add_signed(self, rhs)
234            }
235        }
236        }
237
238        c0nst::c0nst! {
239        c0nst impl WrappingAddSigned for $t {
240            type Signed = $s;
241            type Output = $t;
242
243            #[inline]
244            fn wrapping_add_signed(self, rhs: $s) -> $t {
245                <$t>::wrapping_add_signed(self, rhs)
246            }
247        }
248        }
249
250        c0nst::c0nst! {
251        c0nst impl OverflowingAddSigned for $t {
252            type Signed = $s;
253            type Output = $t;
254
255            #[inline]
256            fn overflowing_add_signed(self, rhs: $s) -> ($t, bool) {
257                <$t>::overflowing_add_signed(self, rhs)
258            }
259        }
260        }
261
262        c0nst::c0nst! {
263        c0nst impl SaturatingAddSigned for $t {
264            type Signed = $s;
265            type Output = $t;
266
267            #[inline]
268            fn saturating_add_signed(self, rhs: $s) -> $t {
269                <$t>::saturating_add_signed(self, rhs)
270            }
271        }
272        }
273
274        c0nst::c0nst! {
275        c0nst impl StrictAddSigned for $t {
276            type Signed = $s;
277            type Output = $t;
278
279            // stable since 1.91, newer than the MSRV
280            #[inline]
281            #[track_caller]
282            fn strict_add_signed(self, rhs: $s) -> $t {
283                let (val, overflowed) = <$t>::overflowing_add_signed(self, rhs);
284                if overflowed {
285                    panic!("attempt to add with overflow")
286                }
287                val
288            }
289        }
290        }
291
292        // the whole sub_signed family is newer than the MSRV (1.90/1.91);
293        // same algorithms as core
294        c0nst::c0nst! {
295        c0nst impl OverflowingSubSigned for $t {
296            type Signed = $s;
297            type Output = $t;
298
299            #[inline]
300            fn overflowing_sub_signed(self, rhs: $s) -> ($t, bool) {
301                // subtracting the bit pattern is correct modulo 2^BITS; the
302                // borrow flag just has to flip when rhs was negative
303                let (res, overflow) = <$t>::overflowing_sub(self, rhs as $t);
304                (res, overflow != (rhs < 0))
305            }
306        }
307        }
308
309        c0nst::c0nst! {
310        c0nst impl CheckedSubSigned for $t {
311            type Signed = $s;
312            type Output = $t;
313
314            #[inline]
315            fn checked_sub_signed(self, rhs: $s) -> Option<$t> {
316                let (val, overflowed) =
317                    OverflowingSubSigned::overflowing_sub_signed(self, rhs);
318                if overflowed {
319                    None
320                } else {
321                    Some(val)
322                }
323            }
324        }
325        }
326
327        c0nst::c0nst! {
328        c0nst impl WrappingSubSigned for $t {
329            type Signed = $s;
330            type Output = $t;
331
332            #[inline]
333            fn wrapping_sub_signed(self, rhs: $s) -> $t {
334                <$t>::wrapping_sub(self, rhs as $t)
335            }
336        }
337        }
338
339        c0nst::c0nst! {
340        c0nst impl SaturatingSubSigned for $t {
341            type Signed = $s;
342            type Output = $t;
343
344            #[inline]
345            fn saturating_sub_signed(self, rhs: $s) -> $t {
346                let (val, overflowed) =
347                    OverflowingSubSigned::overflowing_sub_signed(self, rhs);
348                if !overflowed {
349                    val
350                } else if rhs < 0 {
351                    <$t>::MAX
352                } else {
353                    0
354                }
355            }
356        }
357        }
358
359        c0nst::c0nst! {
360        c0nst impl StrictSubSigned for $t {
361            type Signed = $s;
362            type Output = $t;
363
364            #[inline]
365            #[track_caller]
366            fn strict_sub_signed(self, rhs: $s) -> $t {
367                let (val, overflowed) =
368                    OverflowingSubSigned::overflowing_sub_signed(self, rhs);
369                if overflowed {
370                    panic!("attempt to subtract with overflow")
371                }
372                val
373            }
374        }
375        }
376    )*};
377}
378
379mixed_signed_impl! {
380    u8 => i8;
381    u16 => i16;
382    u32 => i32;
383    u64 => i64;
384    usize => isize;
385    u128 => i128;
386}
387
388macro_rules! mixed_unsigned_impl {
389    ($($t:ty => $u:ty;)*) => {$(
390        c0nst::c0nst! {
391        c0nst impl CheckedAddUnsigned for $t {
392            type Unsigned = $u;
393            type Output = $t;
394
395            #[inline]
396            fn checked_add_unsigned(self, rhs: $u) -> Option<$t> {
397                <$t>::checked_add_unsigned(self, rhs)
398            }
399        }
400        }
401
402        c0nst::c0nst! {
403        c0nst impl WrappingAddUnsigned for $t {
404            type Unsigned = $u;
405            type Output = $t;
406
407            #[inline]
408            fn wrapping_add_unsigned(self, rhs: $u) -> $t {
409                <$t>::wrapping_add_unsigned(self, rhs)
410            }
411        }
412        }
413
414        c0nst::c0nst! {
415        c0nst impl OverflowingAddUnsigned for $t {
416            type Unsigned = $u;
417            type Output = $t;
418
419            #[inline]
420            fn overflowing_add_unsigned(self, rhs: $u) -> ($t, bool) {
421                <$t>::overflowing_add_unsigned(self, rhs)
422            }
423        }
424        }
425
426        c0nst::c0nst! {
427        c0nst impl SaturatingAddUnsigned for $t {
428            type Unsigned = $u;
429            type Output = $t;
430
431            #[inline]
432            fn saturating_add_unsigned(self, rhs: $u) -> $t {
433                <$t>::saturating_add_unsigned(self, rhs)
434            }
435        }
436        }
437
438        c0nst::c0nst! {
439        c0nst impl StrictAddUnsigned for $t {
440            type Unsigned = $u;
441            type Output = $t;
442
443            #[inline]
444            #[track_caller]
445            fn strict_add_unsigned(self, rhs: $u) -> $t {
446                let (val, overflowed) = <$t>::overflowing_add_unsigned(self, rhs);
447                if overflowed {
448                    panic!("attempt to add with overflow")
449                }
450                val
451            }
452        }
453        }
454
455        c0nst::c0nst! {
456        c0nst impl CheckedSubUnsigned for $t {
457            type Unsigned = $u;
458            type Output = $t;
459
460            #[inline]
461            fn checked_sub_unsigned(self, rhs: $u) -> Option<$t> {
462                <$t>::checked_sub_unsigned(self, rhs)
463            }
464        }
465        }
466
467        c0nst::c0nst! {
468        c0nst impl WrappingSubUnsigned for $t {
469            type Unsigned = $u;
470            type Output = $t;
471
472            #[inline]
473            fn wrapping_sub_unsigned(self, rhs: $u) -> $t {
474                <$t>::wrapping_sub_unsigned(self, rhs)
475            }
476        }
477        }
478
479        c0nst::c0nst! {
480        c0nst impl OverflowingSubUnsigned for $t {
481            type Unsigned = $u;
482            type Output = $t;
483
484            #[inline]
485            fn overflowing_sub_unsigned(self, rhs: $u) -> ($t, bool) {
486                <$t>::overflowing_sub_unsigned(self, rhs)
487            }
488        }
489        }
490
491        c0nst::c0nst! {
492        c0nst impl SaturatingSubUnsigned for $t {
493            type Unsigned = $u;
494            type Output = $t;
495
496            #[inline]
497            fn saturating_sub_unsigned(self, rhs: $u) -> $t {
498                <$t>::saturating_sub_unsigned(self, rhs)
499            }
500        }
501        }
502
503        c0nst::c0nst! {
504        c0nst impl StrictSubUnsigned for $t {
505            type Unsigned = $u;
506            type Output = $t;
507
508            #[inline]
509            #[track_caller]
510            fn strict_sub_unsigned(self, rhs: $u) -> $t {
511                let (val, overflowed) = <$t>::overflowing_sub_unsigned(self, rhs);
512                if overflowed {
513                    panic!("attempt to subtract with overflow")
514                }
515                val
516            }
517        }
518        }
519    )*};
520}
521
522mixed_unsigned_impl! {
523    i8 => u8;
524    i16 => u16;
525    i32 => u32;
526    i64 => u64;
527    isize => usize;
528    i128 => u128;
529}
530
531c0nst::c0nst! {
532/// Computes the signed difference of two unsigned values.
533pub c0nst trait CheckedSignedDiff: Sized {
534    /// The signed counterpart type (e.g. `i32` for `u32`).
535    type Signed;
536
537    /// Checked signed difference. Computes `self - rhs` as a signed value,
538    /// returning `None` if the result doesn't fit in the signed type.
539    ///
540    /// ```
541    /// use const_num_traits::CheckedSignedDiff;
542    ///
543    /// assert_eq!(CheckedSignedDiff::checked_signed_diff(10u8, 14), Some(-4));
544    /// assert_eq!(CheckedSignedDiff::checked_signed_diff(u8::MAX, 0), None);
545    /// ```
546    fn checked_signed_diff(self, rhs: Self) -> Option<Self::Signed>;
547}
548}
549
550macro_rules! checked_signed_diff_impl {
551    ($($t:ty => $s:ty;)*) => {$(
552        c0nst::c0nst! {
553        c0nst impl CheckedSignedDiff for $t {
554            type Signed = $s;
555
556            // stable since 1.91, newer than the MSRV; same algorithm as core
557            #[inline]
558            fn checked_signed_diff(self, rhs: Self) -> Option<$s> {
559                let res = <$t>::wrapping_sub(self, rhs) as $s;
560                let overflow = (self >= rhs) == (res < 0);
561                if !overflow {
562                    Some(res)
563                } else {
564                    None
565                }
566            }
567        }
568        }
569    )*};
570}
571
572checked_signed_diff_impl! {
573    u8 => i8;
574    u16 => i16;
575    u32 => i32;
576    u64 => i64;
577    usize => isize;
578    u128 => i128;
579}
580
581#[cfg(test)]
582mod tests {
583    use super::*;
584
585    #[test]
586    fn add_signed() {
587        assert_eq!(CheckedAddSigned::checked_add_signed(1u8, -2), None);
588        assert_eq!(CheckedAddSigned::checked_add_signed(254u8, 1), Some(255));
589        assert_eq!(WrappingAddSigned::wrapping_add_signed(1u8, -2), 255);
590        assert_eq!(
591            OverflowingAddSigned::overflowing_add_signed(255u8, 1),
592            (0, true)
593        );
594        assert_eq!(SaturatingAddSigned::saturating_add_signed(1u8, -2), 0);
595        assert_eq!(SaturatingAddSigned::saturating_add_signed(250u8, 100), 255);
596        assert_eq!(StrictAddSigned::strict_add_signed(250u8, 5), 255);
597    }
598
599    #[test]
600    fn sub_signed() {
601        assert_eq!(CheckedSubSigned::checked_sub_signed(1u8, 2), None);
602        assert_eq!(CheckedSubSigned::checked_sub_signed(1u8, -2), Some(3));
603        assert_eq!(CheckedSubSigned::checked_sub_signed(u8::MAX, -1), None);
604        assert_eq!(WrappingSubSigned::wrapping_sub_signed(0u8, 1), 255);
605        assert_eq!(
606            OverflowingSubSigned::overflowing_sub_signed(0u8, 1),
607            (255, true)
608        );
609        assert_eq!(
610            OverflowingSubSigned::overflowing_sub_signed(255u8, -1),
611            (0, true)
612        );
613        assert_eq!(
614            OverflowingSubSigned::overflowing_sub_signed(10u8, -5),
615            (15, false)
616        );
617        assert_eq!(SaturatingSubSigned::saturating_sub_signed(0u8, 1), 0);
618        assert_eq!(SaturatingSubSigned::saturating_sub_signed(255u8, -1), 255);
619        assert_eq!(StrictSubSigned::strict_sub_signed(10u8, -5), 15);
620    }
621
622    #[test]
623    #[should_panic(expected = "attempt to subtract with overflow")]
624    fn strict_sub_signed_panics() {
625        let _ = StrictSubSigned::strict_sub_signed(0u8, 1);
626    }
627
628    #[test]
629    fn add_sub_unsigned() {
630        assert_eq!(CheckedAddUnsigned::checked_add_unsigned(i8::MAX, 1), None);
631        assert_eq!(
632            WrappingAddUnsigned::wrapping_add_unsigned(i8::MAX, 1),
633            i8::MIN
634        );
635        assert_eq!(
636            OverflowingAddUnsigned::overflowing_add_unsigned(-1i8, 2),
637            (1, false)
638        );
639        assert_eq!(
640            SaturatingAddUnsigned::saturating_add_unsigned(100i8, 100),
641            i8::MAX
642        );
643        assert_eq!(StrictAddUnsigned::strict_add_unsigned(-1i8, 128), 127);
644        assert_eq!(CheckedSubUnsigned::checked_sub_unsigned(i8::MIN, 1), None);
645        assert_eq!(
646            WrappingSubUnsigned::wrapping_sub_unsigned(i8::MIN, 1),
647            i8::MAX
648        );
649        assert_eq!(
650            OverflowingSubUnsigned::overflowing_sub_unsigned(1i8, 2),
651            (-1, false)
652        );
653        assert_eq!(
654            SaturatingSubUnsigned::saturating_sub_unsigned(-100i8, 100),
655            i8::MIN
656        );
657        assert_eq!(StrictSubUnsigned::strict_sub_unsigned(0i8, 128), i8::MIN);
658    }
659
660    #[test]
661    fn signed_diff() {
662        assert_eq!(CheckedSignedDiff::checked_signed_diff(10u8, 14), Some(-4));
663        assert_eq!(CheckedSignedDiff::checked_signed_diff(14u8, 10), Some(4));
664        assert_eq!(CheckedSignedDiff::checked_signed_diff(u8::MAX, 0), None);
665        assert_eq!(CheckedSignedDiff::checked_signed_diff(0u8, u8::MAX), None);
666        assert_eq!(
667            CheckedSignedDiff::checked_signed_diff(0u8, 128),
668            Some(i8::MIN)
669        );
670        assert_eq!(
671            CheckedSignedDiff::checked_signed_diff(127u8, 0),
672            Some(i8::MAX)
673        );
674    }
675}