14
14
15
15
#pragma once
16
16
17
+ #include < stddef.h>
17
18
#include < stdint.h>
18
19
19
20
#include < type_traits>
@@ -43,13 +44,19 @@ template <class T>
43
44
requires (std::is_integral_v<T> && sizeof (T) <= 8 )
44
45
sus_always_inline constexpr auto high_bit () noexcept {
45
46
if constexpr (sizeof (T) == 1 )
46
- return T{ 0x80 } ;
47
+ return static_cast <T>( 0x80 ) ;
47
48
else if constexpr (sizeof (T) == 2 )
48
- return T{ 0x8000 } ;
49
+ return static_cast <T>( 0x8000 ) ;
49
50
else if constexpr (sizeof (T) == 4 )
50
- return T{ 0x80000000 } ;
51
+ return static_cast <T>( 0x80000000 ) ;
51
52
else
52
- return T{0x8000000000000000 };
53
+ return static_cast <T>(0x8000000000000000 );
54
+ }
55
+
56
+ template <class T >
57
+ requires (std::is_integral_v<T> && !std::is_signed_v<T>)
58
+ sus_always_inline constexpr auto max_value () noexcept {
59
+ return ~T{0 };
53
60
}
54
61
55
62
template <class T >
@@ -65,10 +72,16 @@ sus_always_inline constexpr auto max_value() noexcept {
65
72
return T{0x7fffffffffffffff };
66
73
}
67
74
75
+ template <class T >
76
+ requires (std::is_integral_v<T> && !std::is_signed_v<T>)
77
+ sus_always_inline constexpr auto min_value () noexcept {
78
+ return T{0 };
79
+ }
80
+
68
81
template <class T >
69
82
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 8 )
70
83
sus_always_inline constexpr auto min_value () noexcept {
71
- return -max_value<T>() - 1 ;
84
+ return -max_value<T>() - T{ 1 } ;
72
85
}
73
86
74
87
template <class T >
@@ -344,6 +357,17 @@ sus_always_inline constexpr auto into_unsigned(T x) noexcept {
344
357
return static_cast <uint64_t >(x);
345
358
}
346
359
360
+ template <class T >
361
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> && sizeof (T) <= 4 )
362
+ sus_always_inline constexpr auto into_widened (T x) noexcept {
363
+ if constexpr (sizeof (x) == 1 )
364
+ return static_cast <uint16_t >(x);
365
+ else if constexpr (sizeof (x) == 2 )
366
+ return static_cast <uint32_t >(x);
367
+ else
368
+ return static_cast <uint64_t >(x);
369
+ }
370
+
347
371
template <class T >
348
372
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 4 )
349
373
sus_always_inline constexpr auto into_widened (T x) noexcept {
@@ -381,11 +405,21 @@ sus_always_inline constexpr bool sign_bit(T x) noexcept {
381
405
return x & (T (1 ) << 63 ) != 0 ;
382
406
}
383
407
408
+ template <class T >
409
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> && sizeof (T) <= 8 )
410
+ sus_always_inline
411
+ constexpr OverflowOut<T> add_with_overflow (T x, T y) noexcept {
412
+ return OverflowOut<T>{
413
+ .overflow = x > max_value<T>() - y,
414
+ .value = x + y,
415
+ };
416
+ }
417
+
384
418
template <class T >
385
419
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 8 )
386
420
sus_always_inline
387
421
constexpr OverflowOut<T> add_with_overflow (T x, T y) noexcept {
388
- auto out = into_signed (into_unsigned (x) + into_unsigned (y));
422
+ const auto out = into_signed (into_unsigned (x) + into_unsigned (y));
389
423
return OverflowOut<T>{
390
424
.overflow = y >= 0 != out >= x,
391
425
.value = out,
@@ -397,18 +431,28 @@ template <class T, class U = decltype(to_unsigned(std::declval<T>()))>
397
431
sizeof (T) == sizeof (U))
398
432
sus_always_inline
399
433
constexpr OverflowOut<T> add_with_overflow_unsigned (T x, U y) noexcept {
400
- auto out = into_signed (into_unsigned (x) + y);
434
+ const auto out = into_signed (into_unsigned (x) + y);
401
435
return OverflowOut<T>{
402
436
.overflow = static_cast <U>(max_value<T>()) - static_cast <U>(x) < y,
403
437
.value = out,
404
438
};
405
439
}
406
440
441
+ template <class T >
442
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> && sizeof (T) <= 8 )
443
+ sus_always_inline
444
+ constexpr OverflowOut<T> sub_with_overflow (T x, T y) noexcept {
445
+ return OverflowOut<T>{
446
+ .overflow = x < min_value<T>() + y,
447
+ .value = x - y,
448
+ };
449
+ }
450
+
407
451
template <class T >
408
452
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 8 )
409
453
sus_always_inline
410
454
constexpr OverflowOut<T> sub_with_overflow (T x, T y) noexcept {
411
- auto out = into_signed (into_unsigned (x) - into_unsigned (y));
455
+ const auto out = into_signed (into_unsigned (x) - into_unsigned (y));
412
456
return OverflowOut<T>{
413
457
.overflow = y >= 0 != out <= x,
414
458
.value = out,
@@ -420,47 +464,63 @@ template <class T, class U = decltype(to_unsigned(std::declval<T>()))>
420
464
sizeof (T) == sizeof (U))
421
465
sus_always_inline
422
466
constexpr OverflowOut<T> sub_with_overflow_unsigned (T x, U y) noexcept {
423
- auto out = into_signed (into_unsigned (x) - y);
467
+ const auto out = into_signed (into_unsigned (x) - y);
424
468
return OverflowOut<T>{
425
469
.overflow = static_cast <U>(x) - static_cast <U>(min_value<T>()) < y,
426
470
.value = out,
427
471
};
428
472
}
429
473
474
+ template <class T >
475
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> && sizeof (T) <= 4 )
476
+ sus_always_inline
477
+ constexpr OverflowOut<T> mul_with_overflow (T x, T y) noexcept {
478
+ // TODO: Can we use compiler intrinsics?
479
+ auto out = into_widened (x) * into_widened (y);
480
+ using Wide = decltype (out);
481
+ return OverflowOut{.overflow = out > Wide{max_value<T>()}, .value = static_cast <T>(out)};
482
+ }
483
+
484
+ template <class T >
485
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> && sizeof (T) == 8 )
486
+ sus_always_inline
487
+ constexpr OverflowOut<T> mul_with_overflow (T x, T y) noexcept {
488
+ // TODO: For GCC/Clang, use __uint128_t:
489
+ // https://quuxplusone.github.io/blog/2019/02/28/is-int128-integral/
490
+ // For MSVC, use _umul128, but what about constexpr?? If we can't do
491
+ // it then make the whole function non-constexpr?
492
+ // https://docs.microsoft.com/en-us/cpp/intrinsics/umul128
493
+ static_assert (sizeof (T) != 8 );
494
+ return OverflowOut<T>(false , T (0 ));
495
+ }
496
+
430
497
template <class T >
431
498
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 4 )
432
499
sus_always_inline
433
500
constexpr OverflowOut<T> mul_with_overflow (T x, T y) noexcept {
434
- constexpr auto max = max_value<T>();
435
- constexpr auto min = min_value<T>();
436
- // TODO: Optimize this. Use compiler intrinsics.
501
+ // TODO: Can we use compiler intrinsics?
437
502
auto out = into_widened (x) * into_widened (y);
438
503
using Wide = decltype (out);
439
- if (out <= max && out >= min) [[likely]] {
440
- return OverflowOut{.overflow = false , .value = static_cast <T>(out)};
441
- } else {
442
- // TODO: Do we really need to loop here though, we just a signed modulo.
443
- while (out > Wide{max}) out -= Wide{max} - Wide{min} + 1 ;
444
- while (out < Wide{min}) out += Wide{max} - Wide{min} + 1 ;
445
- return OverflowOut<T>{.overflow = true , .value = static_cast <T>(out)};
446
- }
504
+ return OverflowOut{
505
+ .overflow = out > Wide{max_value<T>()} || out < Wide{min_value<T>()},
506
+ .value = static_cast <T>(out)};
447
507
}
448
508
449
509
template <class T >
450
510
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) == 8 )
451
511
sus_always_inline
452
512
constexpr OverflowOut<T> mul_with_overflow (T x, T y) noexcept {
453
- // TODO: For GCC/Clang, use __int128 :
513
+ // TODO: For GCC/Clang, use __int128_t :
454
514
// https://quuxplusone.github.io/blog/2019/02/28/is-int128-integral/
455
- // For MSVC, use _mult128 , but what about constexpr?? If we can't do
515
+ // For MSVC, use _mul128 , but what about constexpr?? If we can't do
456
516
// it then make the whole function non-constexpr?
457
- // https://docs.microsoft.com/en-us/cpp/intrinsics/mul128?view=msvc-170
517
+ // https://docs.microsoft.com/en-us/cpp/intrinsics/mul128
458
518
static_assert (sizeof (T) != 8 );
459
519
return OverflowOut<T>(false , T (0 ));
460
520
}
461
521
462
522
template <class T >
463
- requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 8 )
523
+ requires (std::is_integral_v<T> && sizeof (T) <= 8 )
464
524
sus_always_inline
465
525
constexpr OverflowOut<T> pow_with_overflow (T base, uint32_t exp) noexcept {
466
526
if (exp == 0 ) return OverflowOut<T>{.overflow = false , .value = T{1 }};
@@ -481,6 +541,21 @@ sus_always_inline
481
541
return OverflowOut<T>{.overflow = overflow || r.overflow , .value = r.value };
482
542
}
483
543
544
+ template <class T >
545
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> &&
546
+ (sizeof (T) == 1 || sizeof (T) == 2 || sizeof (T) == 4 ||
547
+ sizeof (T) == 8 ))
548
+ sus_always_inline
549
+ constexpr OverflowOut<T> shl_with_overflow (T x, uint32_t shift) noexcept {
550
+ // Using `num_bits<T>() - 1` as a mask only works if num_bits<T>() is a power
551
+ // of two, so we verify that sizeof(T) is a power of 2, which implies the
552
+ // number of bits is as well (since each byte is 2^3 bits).
553
+ const bool overflow = shift >= num_bits<T>();
554
+ if (overflow) [[unlikely]]
555
+ shift = shift & (num_bits<T>() - 1 );
556
+ return OverflowOut<T>{.overflow = overflow, .value = x << shift};
557
+ }
558
+
484
559
template <class T >
485
560
requires (std::is_integral_v<T> && std::is_signed_v<T> &&
486
561
(sizeof (T) == 1 || sizeof (T) == 2 || sizeof (T) == 4 ||
@@ -497,6 +572,21 @@ sus_always_inline
497
572
.value = into_signed (into_unsigned (x) << shift)};
498
573
}
499
574
575
+ template <class T >
576
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> &&
577
+ (sizeof (T) == 1 || sizeof (T) == 2 || sizeof (T) == 4 ||
578
+ sizeof (T) == 8 ))
579
+ sus_always_inline
580
+ constexpr OverflowOut<T> shr_with_overflow (T x, uint32_t shift) noexcept {
581
+ // Using `num_bits<T>() - 1` as a mask only works if num_bits<T>() is a power
582
+ // of two, so we verify that sizeof(T) is a power of 2, which implies the
583
+ // number of bits is as well (since each byte is 2^3 bits).
584
+ const bool overflow = shift >= num_bits<T>();
585
+ if (overflow) [[unlikely]]
586
+ shift = shift & (num_bits<T>() - 1 );
587
+ return OverflowOut<T>{.overflow = overflow, .value = x >> shift};
588
+ }
589
+
500
590
template <class T >
501
591
requires (std::is_integral_v<T> && std::is_signed_v<T> &&
502
592
(sizeof (T) == 1 || sizeof (T) == 2 || sizeof (T) == 4 ||
@@ -513,6 +603,17 @@ sus_always_inline
513
603
.value = into_signed (into_unsigned (x) >> shift)};
514
604
}
515
605
606
+ template <class T >
607
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> && sizeof (T) <= 8 )
608
+ sus_always_inline constexpr T saturating_add (T x, T y) noexcept {
609
+ // TODO: Optimize this? Use intrinsics?
610
+ const auto out = add_with_overflow (x, y);
611
+ if (!out.overflow ) [[likely]]
612
+ return out.value ;
613
+ else
614
+ return max_value<T>();
615
+ }
616
+
516
617
template <class T >
517
618
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 8 )
518
619
sus_always_inline constexpr T saturating_add (T x, T y) noexcept {
@@ -530,6 +631,17 @@ sus_always_inline constexpr T saturating_add(T x, T y) noexcept {
530
631
}
531
632
}
532
633
634
+ template <class T >
635
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> && sizeof (T) <= 8 )
636
+ sus_always_inline constexpr T saturating_sub (T x, T y) noexcept {
637
+ // TODO: Optimize this? Use intrinsics?
638
+ const auto out = sub_with_overflow (x, y);
639
+ if (!out.overflow ) [[likely]]
640
+ return out.value ;
641
+ else
642
+ return min_value<T>();
643
+ }
644
+
533
645
template <class T >
534
646
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 8 )
535
647
sus_always_inline constexpr T saturating_sub (T x, T y) noexcept {
@@ -547,11 +659,22 @@ sus_always_inline constexpr T saturating_sub(T x, T y) noexcept {
547
659
}
548
660
}
549
661
662
+ template <class T >
663
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> && sizeof (T) <= 8 )
664
+ sus_always_inline constexpr T saturating_mul (T x, T y) noexcept {
665
+ // TODO: Optimize this? Use intrinsics?
666
+ const auto out = mul_with_overflow (x, y);
667
+ if (!out.overflow ) [[likely]]
668
+ return out.value ;
669
+ else
670
+ return max_value<T>();
671
+ }
672
+
550
673
template <class T >
551
674
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 8 )
552
675
sus_always_inline constexpr T saturating_mul (T x, T y) noexcept {
553
676
// TODO: Optimize this? Use intrinsics?
554
- auto out = mul_with_overflow (x, y);
677
+ const auto out = mul_with_overflow (x, y);
555
678
if (!out.overflow ) [[likely]]
556
679
return out.value ;
557
680
else if (x > 0 == y > 0 )
@@ -560,27 +683,53 @@ sus_always_inline constexpr T saturating_mul(T x, T y) noexcept {
560
683
return min_value<T>();
561
684
}
562
685
686
+ template <class T >
687
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> && sizeof (T) <= 8 )
688
+ sus_always_inline constexpr T wrapping_add (T x, T y) noexcept {
689
+ return x + y;
690
+ }
691
+
563
692
template <class T >
564
693
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 8 )
565
694
sus_always_inline constexpr T wrapping_add (T x, T y) noexcept {
566
695
// TODO: Are there cheaper intrinsics?
567
696
return add_with_overflow (x, y).value ;
568
697
}
569
698
699
+ template <class T >
700
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> && sizeof (T) <= 8 )
701
+ sus_always_inline constexpr T wrapping_sub (T x, T y) noexcept {
702
+ return x - y;
703
+ }
704
+
570
705
template <class T >
571
706
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 8 )
572
707
sus_always_inline constexpr T wrapping_sub (T x, T y) noexcept {
573
708
// TODO: Are there cheaper intrinsics?
574
709
return sub_with_overflow (x, y).value ;
575
710
}
576
711
712
+ template <class T >
713
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> && sizeof (T) <= 8 )
714
+ sus_always_inline constexpr T wrapping_mul (T x, T y) noexcept {
715
+ return x * y;
716
+ }
717
+
577
718
template <class T >
578
719
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 8 )
579
720
sus_always_inline constexpr T wrapping_mul (T x, T y) noexcept {
580
721
// TODO: Are there cheaper intrinsics?
581
722
return mul_with_overflow (x, y).value ;
582
723
}
583
724
725
+ template <class T >
726
+ requires (std::is_integral_v<T> && !std::is_signed_v<T> && sizeof (T) <= 8 )
727
+ sus_always_inline constexpr T wrapping_pow (T base, uint32_t exp) noexcept {
728
+ // TODO: Don't need to track overflow and unsigned wraps by default, so this
729
+ // can be cheaper.
730
+ return pow_with_overflow (base, exp ).value ;
731
+ }
732
+
584
733
template <class T >
585
734
requires (std::is_integral_v<T> && std::is_signed_v<T> && sizeof (T) <= 8 )
586
735
sus_always_inline constexpr T wrapping_pow (T base, uint32_t exp) noexcept {
0 commit comments