[libcxx-commits] [mlir] [libcxx] [lldb] [lld] [clang] [compiler-rt] [flang] [llvm] [clang-tools-extra] [libc++][numeric] P0543R3: Saturation arithmetic (PR #77967)

Hristo Hristov via libcxx-commits libcxx-commits at lists.llvm.org
Sat Jan 13 10:24:20 PST 2024


https://github.com/H-G-Hristov updated https://github.com/llvm/llvm-project/pull/77967

>From 48c4463e8817c8ee0f00ffa7422e6fafbe838275 Mon Sep 17 00:00:00 2001
From: Zingam <zingam at outlook.com>
Date: Wed, 10 Jan 2024 13:46:19 +0200
Subject: [PATCH 1/4] [libc++][numeric] P0543R3: Saturation arithmetic

Implements: https://wg21.link/P0543R3
- https://eel.is/c++draft/numeric.sat

Additional notes:
- Division: https://eel.is/c++draft/expr.mul#4
- Arithmetic conversions: https://eel.is/c++draft/expr.arith.conv#1.5
- Clang builtins: https://clang.llvm.org/docs/LanguageExtensions.html#builtin-functions
---
 libcxx/docs/FeatureTestMacroTable.rst         |   2 +-
 libcxx/docs/ReleaseNotes/18.rst               |   7 +-
 libcxx/docs/Status/Cxx2cPapers.csv            |   2 +-
 libcxx/include/CMakeLists.txt                 |   1 +
 .../include/__numeric/saturation_arithmetic.h | 135 ++++++++++++++++++
 libcxx/include/module.modulemap.in            |   1 +
 libcxx/include/numeric                        |  13 ++
 libcxx/include/version                        |   2 +-
 libcxx/modules/std/numeric.inc                |  10 ++
 .../numeric.version.compile.pass.cpp          |  16 +--
 .../version.version.compile.pass.cpp          |  16 +--
 .../numeric.ops.sat/add_sat.pass.cpp          | 129 +++++++++++++++++
 .../numeric.ops.sat/add_sat.verify.cpp        |  39 +++++
 .../numeric.ops.sat/div_sat.assert.pass.cpp   |  53 +++++++
 .../numeric.ops.sat/div_sat.pass.cpp          | 108 ++++++++++++++
 .../numeric.ops.sat/div_sat.verify.cpp        |  39 +++++
 .../numeric.ops.sat/mul_sat.pass.cpp          | 119 +++++++++++++++
 .../numeric.ops.sat/mul_sat.verify.cpp        |  39 +++++
 .../numeric.ops.sat/saturate_cast.pass.cpp    | 132 +++++++++++++++++
 .../numeric.ops.sat/saturate_cast.verify.cpp  |  44 ++++++
 .../numeric.ops.sat/sub_sat.pass.cpp          | 107 ++++++++++++++
 .../numeric.ops.sat/sub_sat.verify.cpp        |  39 +++++
 .../generate_feature_test_macro_components.py |   5 +-
 23 files changed, 1026 insertions(+), 32 deletions(-)
 create mode 100644 libcxx/include/__numeric/saturation_arithmetic.h
 create mode 100644 libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/add_sat.pass.cpp
 create mode 100644 libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/add_sat.verify.cpp
 create mode 100644 libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.assert.pass.cpp
 create mode 100644 libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.pass.cpp
 create mode 100644 libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.verify.cpp
 create mode 100644 libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/mul_sat.pass.cpp
 create mode 100644 libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/mul_sat.verify.cpp
 create mode 100644 libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturate_cast.pass.cpp
 create mode 100644 libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturate_cast.verify.cpp
 create mode 100644 libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/sub_sat.pass.cpp
 create mode 100644 libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/sub_sat.verify.cpp

diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 893a3b13ca06e0..9dd9c0c023bc8a 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -432,7 +432,7 @@ Status
     --------------------------------------------------- -----------------
     ``__cpp_lib_rcu``                                   *unimplemented*
     --------------------------------------------------- -----------------
-    ``__cpp_lib_saturation_arithmetic``                 *unimplemented*
+    ``__cpp_lib_saturation_arithmetic``                 ``202311L``
     --------------------------------------------------- -----------------
     ``__cpp_lib_smart_ptr_owner_equality``              *unimplemented*
     --------------------------------------------------- -----------------
diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst
index 6de7d07e454d34..877e387d9280fd 100644
--- a/libcxx/docs/ReleaseNotes/18.rst
+++ b/libcxx/docs/ReleaseNotes/18.rst
@@ -54,12 +54,13 @@ Implemented Papers
 - P2905R2 - Runtime format strings
 - P2918R2 - Runtime format strings II
 - P2871R3 - Remove Deprecated Unicode Conversion Facets from C++26
-- P2870R3 - Remove basic_string::reserve()
+- P2870R3 - Remove ``basic_string::reserve()``
 - P2909R4 - Fix formatting of code units as integers (Dude, where’s my ``char``?)
-- P2821R5 - span.at()
-- P0521R0 - Proposed Resolution for CA 14 (shared_ptr use_count/unique)
+- P2821R5 - ``span.at()``
+- P0521R0 - Proposed Resolution for CA 14 (``shared_ptr`` ``use_count/unique``)
 - P1759R6 - Native handles and file streams
 - P2517R1 - Add a conditional ``noexcept`` specification to ``std::apply``
+- P0543R3 - Saturation arithmetic
 
 
 Improvements and New Features
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index 5701717f39766c..b38b3028863fee 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -27,7 +27,7 @@
 "`P2714R1 <https://wg21.link/P2714R1>`__","LWG","Bind front and back to NTTP callables","Varna June 2023","","",""
 "`P2630R4 <https://wg21.link/P2630R4>`__","LWG","``submdspan``","Varna June 2023","","",""
 "","","","","","",""
-"`P0543R3 <https://wg21.link/P0543R3>`__","LWG","Saturation arithmetic","Kona November 2023","","",""
+"`P0543R3 <https://wg21.link/P0543R3>`__","LWG","Saturation arithmetic","Kona November 2023","|Complete|","18.0",""
 "`P2407R5 <https://wg21.link/P2407R5>`__","LWG","Freestanding Library: Partial Classes","Kona November 2023","","",""
 "`P2546R5 <https://wg21.link/P2546R5>`__","LWG","Debugging Support","Kona November 2023","","",""
 "`P2905R2 <https://wg21.link/P2905R2>`__","LWG","Runtime format strings","Kona November 2023","|Complete|","18.0","|format| |DR|"
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 0fe3ab44d2466e..c4d8a9f092de14 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -569,6 +569,7 @@ set(files
   __numeric/pstl_reduce.h
   __numeric/pstl_transform_reduce.h
   __numeric/reduce.h
+  __numeric/saturation_arithmetic.h
   __numeric/transform_exclusive_scan.h
   __numeric/transform_inclusive_scan.h
   __numeric/transform_reduce.h
diff --git a/libcxx/include/__numeric/saturation_arithmetic.h b/libcxx/include/__numeric/saturation_arithmetic.h
new file mode 100644
index 00000000000000..61ae35f3ee02d5
--- /dev/null
+++ b/libcxx/include/__numeric/saturation_arithmetic.h
@@ -0,0 +1,135 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___NUMERIC_SATURATION_ARITHMETIC_H
+#define _LIBCPP___NUMERIC_SATURATION_ARITHMETIC_H
+
+#include <__concepts/arithmetic.h>
+#include <__config>
+#include <__type_traits/decay.h>
+#include <__utility/cmp.h>
+#include <limits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 26
+
+template <typename _Tp>
+// concept __libcpp_standard_integer = __libcpp_unsigned_integer<_Tp> || __libcpp_signed_integer<_Tp>;
+// concept __libcpp_standard_integer =
+//     __libcpp_unsigned_integer<remove_cv<_Tp>> || __libcpp_signed_integer<remove_cv<_Tp>>;
+concept __libcpp_standard_integer = __libcpp_unsigned_integer<decay_t<_Tp>> || __libcpp_signed_integer<decay_t<_Tp>>;
+
+template <__libcpp_standard_integer _Tp>
+//   requires __libcpp_standard_integer<_Tp>
+_LIBCPP_HIDE_FROM_ABI constexpr _Tp add_sat(_Tp __x, _Tp __y) noexcept {
+  // builtins: clang/docs/LanguageExtensions.rst
+  // builtins:
+  // https://github.com/llvm/llvm-project/blob/7b45c549670a8e8b6fe90f4382b0699dd20707d3/clang/docs/LanguageExtensions.rst#L3500
+  if (_Tp __sum; !__builtin_add_overflow(__x, __y, &__sum))
+    return __sum;
+  // Handle overflow
+  if constexpr (__libcpp_unsigned_integer<_Tp>) {
+    return std::numeric_limits<_Tp>::max();
+  } else {
+    // Signed addition overflow
+    if (__x > 0)
+      // Overflows if (x > 0 && y > 0)
+      return std::numeric_limits<_Tp>::max();
+    else
+      // Overflows if  (x < 0 && y < 0)
+      return std::numeric_limits<_Tp>::min();
+  }
+}
+
+template <__libcpp_standard_integer _Tp>
+_LIBCPP_HIDE_FROM_ABI constexpr _Tp sub_sat(_Tp __x, _Tp __y) noexcept {
+  if (_Tp __sub; !__builtin_sub_overflow(__x, __y, &__sub))
+    return __sub;
+  // Handle overflow
+  if constexpr (__libcpp_unsigned_integer<_Tp>) {
+    // Overflows if (x < y)
+    return std::numeric_limits<_Tp>::min();
+  } else {
+    // Signed subtration overflow
+    if (__x > 0)
+      // Overflows if (x > 0 && y < 0)
+      return std::numeric_limits<_Tp>::max();
+    else
+      // Overflows if (x < 0 && y > 0)
+      return std::numeric_limits<_Tp>::min();
+  }
+}
+
+template <__libcpp_standard_integer _Tp>
+_LIBCPP_HIDE_FROM_ABI constexpr _Tp mul_sat(_Tp __x, _Tp __y) noexcept {
+  if (_Tp __mul; !__builtin_mul_overflow(__x, __y, &__mul))
+    return __mul;
+  // Handle overflow
+  if constexpr (__libcpp_unsigned_integer<_Tp>) {
+    return std::numeric_limits<_Tp>::max();
+  } else {
+    // Signed multiplication overflow
+    // if (__x > 0 && __y > 0)
+    //   // Overflows if (x > 0 && y > 0)
+    //   return std::numeric_limits<_Tp>::max();
+    // else if (__y > 0)
+    //   // Overflows if (x > 0 && y < 0)
+    //   return std::numeric_limits<_Tp>::max();
+    if (__x > 0) {
+      if (__y > 0)
+        // Overflows if (x > 0 && y > 0)
+        return std::numeric_limits<_Tp>::max();
+      // Overflows if (x > 0 && y < 0)
+      return std::numeric_limits<_Tp>::min();
+    }
+    if (__y > 0)
+      // Overflows if (x < 0 && y > 0)
+      return std::numeric_limits<_Tp>::min();
+    // Overflows if (x < 0 && y < 0)
+    return std::numeric_limits<_Tp>::max();
+  }
+}
+
+template <__libcpp_standard_integer _Tp>
+_LIBCPP_HIDE_FROM_ABI constexpr _Tp div_sat(_Tp __x, _Tp __y) noexcept {
+  _LIBCPP_ASSERT_UNCATEGORIZED(__y != 0, "Division by 0 is undefined");
+  if constexpr (__libcpp_unsigned_integer<_Tp>) {
+    return __x / __y;
+  } else {
+    // Handle signed division overflow
+    if (__x == std::numeric_limits<_Tp>::min() && __y == _Tp{-1})
+      return std::numeric_limits<_Tp>::max();
+    return __x / __y;
+  }
+}
+
+template <__libcpp_standard_integer _Rp, __libcpp_standard_integer _Tp>
+_LIBCPP_HIDE_FROM_ABI constexpr _Rp saturate_cast(_Tp __x) noexcept {
+  // if (std::in_range<_Rp>(__x)) {
+  //   return _Rp{__x};
+  // }
+  // Handle overflow
+  if (std::cmp_less_equal(__x, std::numeric_limits<_Rp>::min()))
+    return std::numeric_limits<_Rp>::min();
+  if (std::cmp_greater_equal(__x, std::numeric_limits<_Rp>::max()))
+    return std::numeric_limits<_Rp>::max();
+  // No overflow
+  return static_cast<_Rp>(__x);
+}
+
+#endif // _LIBCPP_STD_VER >= 26
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___NUMERIC_SATURATION_ARITHMETIC_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index d10670d4faaffc..194a74a1e07b14 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1580,6 +1580,7 @@ module std_private_numeric_pstl_transform_reduce    [system] {
   export *
 }
 module std_private_numeric_reduce                   [system] { header "__numeric/reduce.h" }
+module std_private_numeric_saturation_arithmetic    [system] { header "__numeric/saturation_arithmetic.h" }
 module std_private_numeric_transform_exclusive_scan [system] { header "__numeric/transform_exclusive_scan.h" }
 module std_private_numeric_transform_inclusive_scan [system] { header "__numeric/transform_inclusive_scan.h" }
 module std_private_numeric_transform_reduce         [system] { header "__numeric/transform_reduce.h" }
diff --git a/libcxx/include/numeric b/libcxx/include/numeric
index d09d0a81fcc03a..0fe7115f1c666e 100644
--- a/libcxx/include/numeric
+++ b/libcxx/include/numeric
@@ -140,6 +140,18 @@ template<class T>
 template<class T>
     constexpr T* midpoint(T* a, T* b);        // C++20
 
+// [numeric.sat], saturation arithmetic
+template<class T>
+constexpr T add_sat(T x, T y) noexcept;                     // freestanding, Since C++26
+template<class T>
+constexpr T sub_sat(T x, T y) noexcept;                     // freestanding, Since C++26
+template<class T>
+constexpr T mul_sat(T x, T y) noexcept;                     // freestanding, Since C++26
+template<class T>
+constexpr T div_sat(T x, T y) noexcept;                     // freestanding, Since C++26
+template<class T, class U>
+constexpr T saturate_cast(U x) noexcept;                    // freestanding, Since C++26
+
 }  // std
 
 */
@@ -160,6 +172,7 @@ template<class T>
 #include <__numeric/pstl_reduce.h>
 #include <__numeric/pstl_transform_reduce.h>
 #include <__numeric/reduce.h>
+#include <__numeric/saturation_arithmetic.h>
 #include <__numeric/transform_exclusive_scan.h>
 #include <__numeric/transform_inclusive_scan.h>
 #include <__numeric/transform_reduce.h>
diff --git a/libcxx/include/version b/libcxx/include/version
index c96647894dce63..b8d0c4e6f3c898 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -504,7 +504,7 @@ __cpp_lib_within_lifetime                               202306L <type_traits>
 // # define __cpp_lib_out_ptr                              202311L
 # define __cpp_lib_ratio                                202306L
 // # define __cpp_lib_rcu                                  202306L
-// # define __cpp_lib_saturation_arithmetic                202311L
+# define __cpp_lib_saturation_arithmetic                202311L
 // # define __cpp_lib_smart_ptr_owner_equality             202306L
 # define __cpp_lib_span_at                              202311L
 // # define __cpp_lib_span_initializer_list                202311L
diff --git a/libcxx/modules/std/numeric.inc b/libcxx/modules/std/numeric.inc
index d2b7688d4e5f10..0800319de820d9 100644
--- a/libcxx/modules/std/numeric.inc
+++ b/libcxx/modules/std/numeric.inc
@@ -54,4 +54,14 @@ export namespace std {
 
   // [numeric.ops.midpoint], midpoint
   using std::midpoint;
+
+#if _LIBCPP_STD_VER >= 26
+  // [numeric.sat], saturation arithmetic
+  using std::add_sat;
+  using std::sub_sat;
+  using std::mul_sat;
+  using std::div_sat;
+  using std::saturate_cast;
+#endif
+
 } // namespace std
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/numeric.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/numeric.version.compile.pass.cpp
index b510eefc69a5d3..d132b7c7b9c4f5 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/numeric.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/numeric.version.compile.pass.cpp
@@ -263,17 +263,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_saturation_arithmetic
-#     error "__cpp_lib_saturation_arithmetic should be defined in c++26"
-#   endif
-#   if __cpp_lib_saturation_arithmetic != 202311L
-#     error "__cpp_lib_saturation_arithmetic should have the value 202311L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_saturation_arithmetic
-#     error "__cpp_lib_saturation_arithmetic should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_saturation_arithmetic
+#   error "__cpp_lib_saturation_arithmetic should be defined in c++26"
+# endif
+# if __cpp_lib_saturation_arithmetic != 202311L
+#   error "__cpp_lib_saturation_arithmetic should have the value 202311L in c++26"
 # endif
 
 #endif // TEST_STD_VER > 23
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index d5a0839b30f824..edb36da28bbe15 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -7167,17 +7167,11 @@
 #   error "__cpp_lib_sample should have the value 201603L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_saturation_arithmetic
-#     error "__cpp_lib_saturation_arithmetic should be defined in c++26"
-#   endif
-#   if __cpp_lib_saturation_arithmetic != 202311L
-#     error "__cpp_lib_saturation_arithmetic should have the value 202311L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_saturation_arithmetic
-#     error "__cpp_lib_saturation_arithmetic should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_saturation_arithmetic
+#   error "__cpp_lib_saturation_arithmetic should be defined in c++26"
+# endif
+# if __cpp_lib_saturation_arithmetic != 202311L
+#   error "__cpp_lib_saturation_arithmetic should have the value 202311L in c++26"
 # endif
 
 # ifndef __cpp_lib_scoped_lock
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/add_sat.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/add_sat.pass.cpp
new file mode 100644
index 00000000000000..2964ce11d8b654
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/add_sat.pass.cpp
@@ -0,0 +1,129 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <numeric>
+
+// template<class T>
+// constexpr T add_sat(T x, T y) noexcept;                     // freestanding
+
+#include <cassert>
+#include <concepts>
+#include <limits>
+#include <numeric>
+
+template <typename IntegerT>
+constexpr bool test_signed() {
+  constexpr auto minVal = std::numeric_limits<IntegerT>::min();
+  constexpr auto maxVal = std::numeric_limits<IntegerT>::max();
+
+  static_assert(noexcept(std::div_sat(minVal, maxVal)));
+
+  // No saturation
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::add_sat(IntegerT{3}, IntegerT{4});
+    assert(sum == IntegerT{7});
+  }
+
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::add_sat(IntegerT{-3}, IntegerT{4});
+    assert(sum == IntegerT{1});
+  }
+
+  // Saturation - max - both arguments positive
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::add_sat(maxVal, IntegerT{4});
+    assert(sum == maxVal);
+  }
+
+  // Saturation - min - both arguments negative
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::add_sat(minVal, IntegerT{-4});
+    assert(sum == minVal);
+  }
+
+  return true;
+}
+
+template <typename IntegerT>
+constexpr bool test_unsigned() {
+  constexpr auto minVal = std::numeric_limits<IntegerT>::min();
+  constexpr auto maxVal = std::numeric_limits<IntegerT>::max();
+
+  static_assert(noexcept(std::div_sat(minVal, maxVal)));
+
+  // No Saturation
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::add_sat(IntegerT{3}, IntegerT{4});
+    assert(sum == IntegerT{7});
+  }
+
+  // Saturation - max only
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::add_sat(maxVal, IntegerT{4});
+    assert(sum == maxVal);
+  }
+
+  return true;
+}
+
+constexpr bool test() {
+  // signed
+  test_signed<signed char>();
+  test_signed<short int>();
+  test_signed<int>();
+  test_signed<long int>();
+  test_signed<long long int>();
+  // unsigned
+  test_unsigned<unsigned char>();
+  test_unsigned<unsigned short int>();
+  test_unsigned<unsigned int>();
+  test_unsigned<unsigned long int>();
+  test_unsigned<unsigned long long int>();
+
+  return true;
+}
+
+// ADDITIONAL_COMPILE_FLAGS: -Wno-constant-conversion
+
+constexpr void cppreference_test() {
+  {
+    constexpr int a = std::add_sat(3, 4); // no saturation occurs, T = int
+    static_assert(a == 7);
+
+    constexpr unsigned char b = std::add_sat<unsigned char>(UCHAR_MAX, 4); // saturated
+    static_assert(b == UCHAR_MAX);
+
+    constexpr unsigned char c = std::add_sat(UCHAR_MAX, 4); // not saturated, T = int
+                                                            // add_sat(int, int) returns int tmp == 259,
+                                                            // then assignment truncates 259 % 256 == 3
+    static_assert(c == 3);
+
+    //  unsigned char d = std::add_sat(252, c); // Error: inconsistent deductions for T
+
+    constexpr unsigned char e = std::add_sat<unsigned char>(251, a); // saturated
+    static_assert(e == UCHAR_MAX);
+    // 251 is of type T = unsigned char, `a` is converted to unsigned char value;
+    // might yield an int -> unsigned char conversion warning for `a`
+
+    constexpr signed char f = std::add_sat<signed char>(-123, -3); // not saturated
+    static_assert(f == -126);
+
+    constexpr signed char g = std::add_sat<signed char>(-123, -13); // saturated
+    static_assert(g == std::numeric_limits<signed char>::min());    // g == -128
+  }
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  cppreference_test();
+
+  return 0;
+}
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/add_sat.verify.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/add_sat.verify.cpp
new file mode 100644
index 00000000000000..b05b30abea6fdb
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/add_sat.verify.cpp
@@ -0,0 +1,39 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <numeric>
+
+// template<class T>
+// constexpr T add_sat(T x, T y) noexcept;                     // freestanding
+
+#include <cstdint>
+#include <numeric>
+
+#include "test_macros.h"
+
+template <typename IntegerT>
+constexpr void test_constraint() {
+  // expected-error-re@*:* 0-2 {{constant expression evaluates to {{.*}} which cannot be narrowed to type {{.*}}}}
+  // expected-error@*:* 0-3 {{no matching function for call to 'add_sat'}}
+  // expected-error@*:* 0-2 {{expected unqualified-id}}
+  [[maybe_unused]] auto sum = std::add_sat(IntegerT{3}, IntegerT{4});
+}
+
+constexpr bool test() {
+  test_constraint<bool>();
+  test_constraint<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_constraint<wchar_t>();
+#endif
+  test_constraint<std::char16_t>();
+  test_constraint<std::char32_t>();
+
+  return true;
+}
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.assert.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.assert.pass.cpp
new file mode 100644
index 00000000000000..98616d2c1c427c
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.assert.pass.cpp
@@ -0,0 +1,53 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// REQUIRES: has-unix-headers
+// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
+// XFAIL: availability-verbose_abort-missing
+
+// <numeric>
+
+// template<class T>
+// constexpr T div_sat(T x, T y) noexcept;                     // freestanding
+
+#include <cassert>
+#include <concepts>
+#include <limits>
+#include <numeric>
+
+#include "check_assertion.h"
+
+template <typename IntegerT>
+constexpr void test() {
+  TEST_LIBCPP_ASSERT_FAILURE((void)std::div_sat(IntegerT{3}, IntegerT{0}), "Division by 0 is undefined");
+}
+
+constexpr bool test() {
+  // signed
+  test<signed char>();
+  test<short int>();
+  test<int>();
+  test<long int>();
+  test<long long int>();
+  // unsigned
+  test<unsigned char>();
+  test<unsigned short int>();
+  test<unsigned int>();
+  test<unsigned long int>();
+  test<unsigned long long int>();
+
+  return true;
+}
+
+int main(int, char**) {
+  assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.pass.cpp
new file mode 100644
index 00000000000000..0411002d7c199e
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.pass.cpp
@@ -0,0 +1,108 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <numeric>
+
+// template<class T>
+// constexpr T div_sat(T x, T y) noexcept;                     // freestanding
+
+#include <cassert>
+#include <concepts>
+#include <limits>
+#include <numeric>
+
+template <typename IntegerT>
+constexpr bool test_signed() {
+  constexpr auto minVal = std::numeric_limits<IntegerT>::min();
+  constexpr auto maxVal = std::numeric_limits<IntegerT>::max();
+
+  static_assert(noexcept(std::div_sat(minVal, maxVal)));
+
+  // No saturation
+  {
+    std::same_as<IntegerT> decltype(auto) div = std::div_sat(IntegerT{3}, IntegerT{4});
+    assert(div == IntegerT{0});
+  }
+
+  {
+    std::same_as<IntegerT> decltype(auto) div = std::div_sat(maxVal, minVal);
+    assert(div == (maxVal / minVal));
+  }
+
+  {
+    std::same_as<IntegerT> decltype(auto) div = std::div_sat(minVal, maxVal);
+    assert(div == (minVal / maxVal));
+  }
+
+  // Saturation - max only
+  {
+    std::same_as<IntegerT> decltype(auto) div = std::div_sat(minVal, IntegerT{-1});
+    assert(div == maxVal);
+  }
+
+  return true;
+}
+
+template <typename IntegerT>
+constexpr bool test_unsigned() {
+  constexpr auto minVal = std::numeric_limits<IntegerT>::min();
+  constexpr auto maxVal = std::numeric_limits<IntegerT>::max();
+
+  static_assert(noexcept(std::div_sat(minVal, maxVal)));
+
+  // No saturation
+  {
+    std::same_as<IntegerT> decltype(auto) div = std::div_sat(IntegerT{3}, IntegerT{4});
+    assert(div == IntegerT{0});
+  }
+
+  {
+    std::same_as<IntegerT> decltype(auto) div = std::div_sat(minVal, maxVal);
+    assert(div == (minVal / maxVal));
+  }
+
+  // Unsigned integer devision never overflow
+
+  return true;
+}
+
+constexpr bool test() {
+  // signed
+  test_signed<signed char>();
+  test_signed<short int>();
+  test_signed<int>();
+  test_signed<long int>();
+  test_signed<long long int>();
+  // unsigned
+  test_unsigned<unsigned char>();
+  test_unsigned<unsigned short int>();
+  test_unsigned<unsigned int>();
+  test_unsigned<unsigned long int>();
+  test_unsigned<unsigned long long int>();
+
+  return true;
+}
+
+constexpr void cppreference_test() {
+  {
+  static_assert("" && (std::div_sat<int>(6, 3) == 2)           // not saturated
+                && (std::div_sat<int>(INT_MIN, -1) == INT_MAX) // saturated
+                && (std::div_sat<unsigned>(6, 3) == 2)         // not saturated
+  );
+  }
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  cppreference_test();
+
+  return 0;
+}
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.verify.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.verify.cpp
new file mode 100644
index 00000000000000..e8ab8ef0c148f4
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.verify.cpp
@@ -0,0 +1,39 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <numeric>
+
+// template<class T>
+// constexpr T div_sat(T x, T y) noexcept;                     // freestanding
+
+#include <cstdint>
+#include <numeric>
+
+#include "test_macros.h"
+
+template <typename IntegerT>
+constexpr void test_constraint() {
+  // expected-error-re@*:* 0-2 {{constant expression evaluates to {{.*}} which cannot be narrowed to type {{.*}}}}
+  // expected-error@*:* 0-3 {{no matching function for call to 'div_sat'}}
+  // expected-error@*:* 0-2 {{expected unqualified-id}}
+  [[maybe_unused]] auto sum = std::div_sat(IntegerT{3}, IntegerT{4});
+}
+
+constexpr bool test() {
+  test_constraint<bool>();
+  test_constraint<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_constraint<wchar_t>();
+#endif
+  test_constraint<std::char16_t>();
+  test_constraint<std::char32_t>();
+
+  return true;
+}
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/mul_sat.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/mul_sat.pass.cpp
new file mode 100644
index 00000000000000..1192b6b4a61dc0
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/mul_sat.pass.cpp
@@ -0,0 +1,119 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <numeric>
+
+// template<class T>
+// constexpr T mul_sat(T x, T y) noexcept;                     // freestanding
+
+#include <cassert>
+#include <concepts>
+#include <limits>
+#include <numeric>
+
+template <typename IntegerT>
+constexpr bool test_signed() {
+  constexpr auto minVal = std::numeric_limits<IntegerT>::min();
+  constexpr auto maxVal = std::numeric_limits<IntegerT>::max();
+
+  static_assert(noexcept(std::mul_sat(minVal, maxVal)));
+
+  // No saturation
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::mul_sat(IntegerT{3}, IntegerT{4});
+    assert(sum == IntegerT{12});
+  }
+
+  // Saturation - max - both arguments positive
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::mul_sat(maxVal, IntegerT{4});
+    assert(sum == maxVal);
+  }
+
+  // Saturation - max - both arguments negative
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::mul_sat(minVal, IntegerT{-4});
+    assert(sum == maxVal);
+  }
+
+  // Saturation - min - left positive, right negative
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::mul_sat(maxVal, IntegerT{-4});
+    assert(sum == minVal);
+  }
+
+  // Saturation - min - left negative, right positive
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::mul_sat(minVal, IntegerT{4});
+    assert(sum == minVal);
+  }
+
+  return true;
+}
+
+template <typename IntegerT>
+constexpr bool test_unsigned() {
+  constexpr auto minVal = std::numeric_limits<IntegerT>::min();
+  constexpr auto maxVal = std::numeric_limits<IntegerT>::max();
+
+  static_assert(noexcept(std::mul_sat(minVal, maxVal)));
+
+  // No saturation
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::mul_sat(IntegerT{3}, IntegerT{4});
+    assert(sum == IntegerT{12});
+  }
+
+  // Saturation
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::mul_sat(maxVal, IntegerT{4});
+    assert(sum == maxVal);
+  }
+
+  return true;
+}
+
+constexpr bool test() {
+  // signed
+  test_signed<signed char>();
+  test_signed<short int>();
+  test_signed<int>();
+  test_signed<long int>();
+  test_signed<long long int>();
+  // unsigned
+  test_unsigned<unsigned char>();
+  test_unsigned<unsigned short int>();
+  test_unsigned<unsigned int>();
+  test_unsigned<unsigned long int>();
+  test_unsigned<unsigned long long int>();
+
+  return true;
+}
+
+constexpr void cppreference_test() {
+  {
+    static_assert(
+        "" && (std::mul_sat<int>(2, 3) == 6)                     // not saturated
+        && (std::mul_sat<int>(INT_MAX / 2, 3) == INT_MAX)        // saturated
+        && (std::mul_sat<int>(-2, 3) == -6)                      // not saturated
+        && (std::mul_sat<int>(INT_MIN / -2, -3) == INT_MIN)      // saturated
+        && (std::mul_sat<unsigned>(2, 3) == 6)                   // not saturated
+        && (std::mul_sat<unsigned>(UINT_MAX / 2, 3) == UINT_MAX) // saturated
+    );
+  }
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  cppreference_test();
+
+  return 0;
+}
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/mul_sat.verify.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/mul_sat.verify.cpp
new file mode 100644
index 00000000000000..8113be2fd23e6b
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/mul_sat.verify.cpp
@@ -0,0 +1,39 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <numeric>
+
+// template<class T>
+// constexpr T mul_sat(T x, T y) noexcept;                     // freestanding
+
+#include <cstdint>
+#include <numeric>
+
+#include "test_macros.h"
+
+template <typename IntegerT>
+constexpr void test_constraint() {
+  // expected-error-re@*:* 0-2 {{constant expression evaluates to {{.*}} which cannot be narrowed to type {{.*}}}}
+  // expected-error@*:* 0-3 {{no matching function for call to 'mul_sat'}}
+  // expected-error@*:* 0-2 {{expected unqualified-id}}
+  [[maybe_unused]] auto sum = std::mul_sat(IntegerT{3}, IntegerT{4});
+}
+
+constexpr bool test() {
+  test_constraint<bool>();
+  test_constraint<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_constraint<wchar_t>();
+#endif
+  test_constraint<std::char16_t>();
+  test_constraint<std::char32_t>();
+
+  return true;
+}
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturate_cast.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturate_cast.pass.cpp
new file mode 100644
index 00000000000000..889b1864f128e5
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturate_cast.pass.cpp
@@ -0,0 +1,132 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <numeric>
+
+// template<class R, class T>
+//   constexpr R saturate_cast(T x) noexcept;                     // freestanding
+
+#include <cassert>
+#include <concepts>
+#include <limits>
+#include <numeric>
+
+#include <print>
+
+template <typename IntegerResultT, typename IntegerT>
+constexpr bool test_signed_notsaturated() {
+  constexpr auto minVal = std::numeric_limits<IntegerT>::min();
+  constexpr auto maxVal = std::numeric_limits<IntegerT>::max();
+
+  static_assert(noexcept(std::saturate_cast<IntegerResultT>(minVal)));
+  static_assert(noexcept(std::saturate_cast<IntegerResultT>(maxVal)));
+
+  assert(std::saturate_cast<IntegerResultT>(minVal) == minVal);
+  assert(std::saturate_cast<IntegerResultT>(maxVal) == maxVal);
+
+  return true;
+}
+
+template <typename IntegerResultT, typename IntegerT>
+constexpr bool test_signed_saturated() {
+  constexpr auto minVal = std::numeric_limits<IntegerT>::min();
+  constexpr auto maxVal = std::numeric_limits<IntegerT>::max();
+
+  static_assert(noexcept(std::saturate_cast<IntegerResultT>(minVal)));
+  static_assert(noexcept(std::saturate_cast<IntegerResultT>(maxVal)));
+
+  assert(std::saturate_cast<IntegerResultT>(minVal) == std::numeric_limits<IntegerResultT>::min());
+  assert(std::saturate_cast<IntegerResultT>(maxVal) == std::numeric_limits<IntegerResultT>::max());
+
+  return true;
+}
+
+template <typename IntegerResultT, typename IntegerT>
+constexpr bool test_unsigned_notsaturated() {
+  constexpr auto minVal = std::numeric_limits<IntegerT>::min();
+  constexpr auto maxVal = std::numeric_limits<IntegerT>::max();
+
+  static_assert(noexcept(std::saturate_cast<IntegerResultT>(minVal)));
+  static_assert(noexcept(std::saturate_cast<IntegerResultT>(maxVal)));
+
+  assert(std::saturate_cast<IntegerResultT>(minVal) == minVal);
+  assert(std::saturate_cast<IntegerResultT>(maxVal) == maxVal);
+
+  return true;
+}
+
+template <typename IntegerResultT, typename IntegerT>
+constexpr bool test_unsigned_saturated() {
+  constexpr auto minVal = std::numeric_limits<IntegerT>::min();
+  constexpr auto maxVal = std::numeric_limits<IntegerT>::max();
+
+  static_assert(noexcept(std::saturate_cast<IntegerResultT>(minVal)));
+  static_assert(noexcept(std::saturate_cast<IntegerResultT>(maxVal)));
+
+  assert(std::saturate_cast<IntegerResultT>(maxVal) == std::numeric_limits<IntegerResultT>::max());
+  assert(std::saturate_cast<IntegerResultT>(maxVal) == std::numeric_limits<IntegerResultT>::max());
+
+  return true;
+}
+
+constexpr bool test() {
+  // signed
+  test_signed_notsaturated<long long int, signed char>();
+  test_signed_notsaturated<long long int, short int>();
+  test_signed_notsaturated<long long int, int>();
+  test_signed_notsaturated<long long int, long int>();
+  test_signed_notsaturated<long long int, long long int>();
+  test_signed_saturated<signed char, long long int>();
+  test_signed_saturated<short int, long long int>();
+  test_signed_saturated<int, long long int>();
+  test_signed_saturated<long int, long long int>();
+  test_signed_saturated<long long int, long long int>();
+  // unsigned
+  test_unsigned_notsaturated<unsigned long long int, unsigned char>();
+  test_unsigned_notsaturated<unsigned long long int, unsigned short int>();
+  test_unsigned_notsaturated<unsigned long long int, unsigned int>();
+  test_unsigned_notsaturated<unsigned long long int, unsigned long int>();
+  test_unsigned_notsaturated<unsigned long long int, unsigned long long int>();
+  test_unsigned_saturated<unsigned char, unsigned long long int>();
+  test_unsigned_saturated<unsigned short int, unsigned long long int>();
+  test_unsigned_saturated<unsigned int, unsigned long long int>();
+  test_unsigned_saturated<unsigned long int, unsigned long long int>();
+  test_unsigned_saturated<unsigned long long int, unsigned long long int>();
+
+  return true;
+}
+
+constexpr void cppreference_test() {
+  {
+    constexpr std::int16_t x1{696};
+
+    constexpr std::int8_t x2 = std::saturate_cast<std::int8_t>(x1);
+    static_assert(x2 == std::numeric_limits<std::int8_t>::max());
+
+    constexpr std::uint8_t x3 = std::saturate_cast<std::uint8_t>(x1);
+    static_assert(x3 == std::numeric_limits<std::uint8_t>::max());
+
+    constexpr std::int16_t y1{-696};
+
+    constexpr std::int8_t y2 = std::saturate_cast<std::int8_t>(y1);
+    static_assert(y2 == std::numeric_limits<std::int8_t>::min());
+
+    constexpr std::uint8_t y3 = std::saturate_cast<std::uint8_t>(y1);
+    static_assert(y3 == 0);
+  }
+}
+
+int main(int, char**) {
+  test();
+  // static_assert(test());
+  cppreference_test();
+
+  return 0;
+}
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturate_cast.verify.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturate_cast.verify.cpp
new file mode 100644
index 00000000000000..d8895225e7468e
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturate_cast.verify.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <numeric>
+
+// template<class R, class T>
+//   constexpr R saturate_cast(T x) noexcept;                    // freestanding
+
+#include <cstdint>
+#include <numeric>
+
+#include "test_macros.h"
+
+template <typename ResultIntegerT, typename IntegerT>
+constexpr void test_constraint() {
+  // expected-error-re@*:* 0-2 {{constant expression evaluates to {{.*}} which cannot be narrowed to type {{.*}}}}
+  // expected-error@*:* 0-6 {{no matching function for call to 'saturate_cast'}}
+  // expected-error@*:* 0-4 {{expected unqualified-id}}
+  [[maybe_unused]] auto sum = std::saturate_cast<ResultIntegerT>(IntegerT{4});
+}
+
+constexpr bool test() {
+  test_constraint<bool, int>();
+  test_constraint<char, int>();
+  test_constraint<int, bool>();
+  test_constraint<int, char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_constraint<wchar_t, int>();
+  test_constraint<int, wchar_t>();
+#endif
+  test_constraint<std::char16_t, int>();
+  test_constraint<std::char32_t, int>();
+  test_constraint<int, std::char16_t>();
+  test_constraint<int, std::char32_t>();
+
+  return true;
+}
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/sub_sat.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/sub_sat.pass.cpp
new file mode 100644
index 00000000000000..a3f0d175ec350d
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/sub_sat.pass.cpp
@@ -0,0 +1,107 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <numeric>
+
+// template<class T>
+// constexpr T sub_sat(T x, T y) noexcept;                     // freestanding
+
+#include <cassert>
+#include <concepts>
+#include <limits>
+#include <numeric>
+
+template <typename IntegerT>
+constexpr bool test_signed() {
+  constexpr auto minVal = std::numeric_limits<IntegerT>::min();
+  constexpr auto maxVal = std::numeric_limits<IntegerT>::max();
+
+  static_assert(noexcept(std::sub_sat(minVal, maxVal)));
+
+  // No saturation
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::sub_sat(IntegerT{3}, IntegerT{4});
+    assert(sum == IntegerT{-1});
+  }
+
+  // Saturation - min - left negative, right positive
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::sub_sat(minVal, IntegerT{4});
+    assert(sum == minVal);
+  }
+
+  // Saturation - max - left postitive, right negative
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::sub_sat(maxVal, IntegerT{-4});
+    assert(sum == maxVal);
+  }
+
+  return true;
+}
+
+template <typename IntegerT>
+constexpr bool test_unsigned() {
+  constexpr auto minVal = std::numeric_limits<IntegerT>::min();
+  constexpr auto maxVal = std::numeric_limits<IntegerT>::max();
+
+  static_assert(noexcept(std::sub_sat(minVal, maxVal)));
+
+  // No saturation
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::sub_sat(IntegerT{3}, IntegerT{1});
+    assert(sum == IntegerT{2});
+  }
+
+  // Saturation - min only
+  {
+    std::same_as<IntegerT> decltype(auto) sum = std::sub_sat(minVal, IntegerT{4});
+    assert(sum == minVal);
+  }
+
+  return true;
+}
+
+constexpr bool test() {
+  // signed
+  test_signed<signed char>();
+  test_signed<short int>();
+  test_signed<int>();
+  test_signed<long int>();
+  test_signed<long long int>();
+  // unsigned
+  test_unsigned<unsigned char>();
+  test_unsigned<unsigned short int>();
+  test_unsigned<unsigned int>();
+  test_unsigned<unsigned long int>();
+  test_unsigned<unsigned long long int>();
+
+  return true;
+}
+
+constexpr void cppreference_test() {
+  {
+    static_assert(
+        "" && (std::sub_sat<int>(INT_MIN + 4, 3) == INT_MIN + 1) // not saturated
+        && (std::sub_sat<int>(INT_MIN + 4, 5) == INT_MIN)        // saturated
+        && (std::sub_sat<int>(INT_MAX - 4, -3) == INT_MAX - 1)   // not saturated
+        && (std::sub_sat<int>(INT_MAX - 4, -5) == INT_MAX)       // saturated
+        && (std::sub_sat<unsigned>(4, 3) == 1)                   // not saturated
+        && (std::sub_sat<unsigned>(4, 5) == 0)                   // saturated
+    );
+  }
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  cppreference_test();
+
+  return 0;
+}
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/sub_sat.verify.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/sub_sat.verify.cpp
new file mode 100644
index 00000000000000..a8e85975ab1a1c
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/sub_sat.verify.cpp
@@ -0,0 +1,39 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <numeric>
+
+// template<class T>
+// constexpr T sub_sat(T x, T y) noexcept;                     // freestanding
+
+#include <cstdint>
+#include <numeric>
+
+#include "test_macros.h"
+
+template <typename IntegerT>
+constexpr void test_constraint() {
+  // expected-error-re@*:* 0-2 {{constant expression evaluates to {{.*}} which cannot be narrowed to type {{.*}}}}
+  // expected-error@*:* 0-3 {{no matching function for call to 'sub_sat'}}
+  // expected-error@*:* 0-2 {{expected unqualified-id}}
+  [[maybe_unused]] auto sum = std::sub_sat(IntegerT{3}, IntegerT{4});
+}
+
+constexpr bool test() {
+  test_constraint<bool>();
+  test_constraint<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_constraint<wchar_t>();
+#endif
+  test_constraint<std::char16_t>();
+  ttest_constraintest<std::char32_t>();
+
+  return true;
+}
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 8ee92909dfa53c..6e7e4c2bb478d7 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -1010,10 +1010,7 @@ def add_version_header(tc):
         {
             "name": "__cpp_lib_saturation_arithmetic",
             "values": {"c++26": 202311},  # P0543R3 Saturation arithmetic
-            "headers": [
-                "numeric"  # TODO verify this entry since the paper was underspecified.
-            ],
-            "unimplemented": True,
+            "headers": ["numeric"],
         },
         {
             "name": "__cpp_lib_scoped_lock",

>From 2c939ccf4da507e6eabcd370966eddb105815670 Mon Sep 17 00:00:00 2001
From: Zingam <zingam at outlook.com>
Date: Fri, 12 Jan 2024 21:19:39 +0200
Subject: [PATCH 2/4] Cleanup

---
 .../include/__numeric/saturation_arithmetic.h | 15 --------
 .../numeric.ops.sat/add_sat.pass.cpp          | 35 ++-----------------
 .../numeric.ops.sat/div_sat.pass.cpp          | 14 ++------
 .../numeric.ops.sat/mul_sat.pass.cpp          | 18 ++--------
 .../numeric.ops.sat/saturate_cast.pass.cpp    | 29 ++-------------
 .../numeric.ops.sat/sub_sat.pass.cpp          | 18 ++--------
 6 files changed, 11 insertions(+), 118 deletions(-)

diff --git a/libcxx/include/__numeric/saturation_arithmetic.h b/libcxx/include/__numeric/saturation_arithmetic.h
index 61ae35f3ee02d5..ec265435eb6a64 100644
--- a/libcxx/include/__numeric/saturation_arithmetic.h
+++ b/libcxx/include/__numeric/saturation_arithmetic.h
@@ -25,17 +25,11 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 #if _LIBCPP_STD_VER >= 26
 
 template <typename _Tp>
-// concept __libcpp_standard_integer = __libcpp_unsigned_integer<_Tp> || __libcpp_signed_integer<_Tp>;
-// concept __libcpp_standard_integer =
-//     __libcpp_unsigned_integer<remove_cv<_Tp>> || __libcpp_signed_integer<remove_cv<_Tp>>;
 concept __libcpp_standard_integer = __libcpp_unsigned_integer<decay_t<_Tp>> || __libcpp_signed_integer<decay_t<_Tp>>;
 
 template <__libcpp_standard_integer _Tp>
 //   requires __libcpp_standard_integer<_Tp>
 _LIBCPP_HIDE_FROM_ABI constexpr _Tp add_sat(_Tp __x, _Tp __y) noexcept {
-  // builtins: clang/docs/LanguageExtensions.rst
-  // builtins:
-  // https://github.com/llvm/llvm-project/blob/7b45c549670a8e8b6fe90f4382b0699dd20707d3/clang/docs/LanguageExtensions.rst#L3500
   if (_Tp __sum; !__builtin_add_overflow(__x, __y, &__sum))
     return __sum;
   // Handle overflow
@@ -80,12 +74,6 @@ _LIBCPP_HIDE_FROM_ABI constexpr _Tp mul_sat(_Tp __x, _Tp __y) noexcept {
     return std::numeric_limits<_Tp>::max();
   } else {
     // Signed multiplication overflow
-    // if (__x > 0 && __y > 0)
-    //   // Overflows if (x > 0 && y > 0)
-    //   return std::numeric_limits<_Tp>::max();
-    // else if (__y > 0)
-    //   // Overflows if (x > 0 && y < 0)
-    //   return std::numeric_limits<_Tp>::max();
     if (__x > 0) {
       if (__y > 0)
         // Overflows if (x > 0 && y > 0)
@@ -116,9 +104,6 @@ _LIBCPP_HIDE_FROM_ABI constexpr _Tp div_sat(_Tp __x, _Tp __y) noexcept {
 
 template <__libcpp_standard_integer _Rp, __libcpp_standard_integer _Tp>
 _LIBCPP_HIDE_FROM_ABI constexpr _Rp saturate_cast(_Tp __x) noexcept {
-  // if (std::in_range<_Rp>(__x)) {
-  //   return _Rp{__x};
-  // }
   // Handle overflow
   if (std::cmp_less_equal(__x, std::numeric_limits<_Rp>::min()))
     return std::numeric_limits<_Rp>::min();
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/add_sat.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/add_sat.pass.cpp
index 2964ce11d8b654..ccd3594fd3a652 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/add_sat.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/add_sat.pass.cpp
@@ -74,13 +74,13 @@ constexpr bool test_unsigned() {
 }
 
 constexpr bool test() {
-  // signed
+  // Signed
   test_signed<signed char>();
   test_signed<short int>();
   test_signed<int>();
   test_signed<long int>();
   test_signed<long long int>();
-  // unsigned
+  // Unsigned
   test_unsigned<unsigned char>();
   test_unsigned<unsigned short int>();
   test_unsigned<unsigned int>();
@@ -90,40 +90,9 @@ constexpr bool test() {
   return true;
 }
 
-// ADDITIONAL_COMPILE_FLAGS: -Wno-constant-conversion
-
-constexpr void cppreference_test() {
-  {
-    constexpr int a = std::add_sat(3, 4); // no saturation occurs, T = int
-    static_assert(a == 7);
-
-    constexpr unsigned char b = std::add_sat<unsigned char>(UCHAR_MAX, 4); // saturated
-    static_assert(b == UCHAR_MAX);
-
-    constexpr unsigned char c = std::add_sat(UCHAR_MAX, 4); // not saturated, T = int
-                                                            // add_sat(int, int) returns int tmp == 259,
-                                                            // then assignment truncates 259 % 256 == 3
-    static_assert(c == 3);
-
-    //  unsigned char d = std::add_sat(252, c); // Error: inconsistent deductions for T
-
-    constexpr unsigned char e = std::add_sat<unsigned char>(251, a); // saturated
-    static_assert(e == UCHAR_MAX);
-    // 251 is of type T = unsigned char, `a` is converted to unsigned char value;
-    // might yield an int -> unsigned char conversion warning for `a`
-
-    constexpr signed char f = std::add_sat<signed char>(-123, -3); // not saturated
-    static_assert(f == -126);
-
-    constexpr signed char g = std::add_sat<signed char>(-123, -13); // saturated
-    static_assert(g == std::numeric_limits<signed char>::min());    // g == -128
-  }
-}
-
 int main(int, char**) {
   test();
   static_assert(test());
-  cppreference_test();
 
   return 0;
 }
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.pass.cpp
index 0411002d7c199e..d2891f77c564fa 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/div_sat.pass.cpp
@@ -74,13 +74,13 @@ constexpr bool test_unsigned() {
 }
 
 constexpr bool test() {
-  // signed
+  // Signed
   test_signed<signed char>();
   test_signed<short int>();
   test_signed<int>();
   test_signed<long int>();
   test_signed<long long int>();
-  // unsigned
+  // Unsigned
   test_unsigned<unsigned char>();
   test_unsigned<unsigned short int>();
   test_unsigned<unsigned int>();
@@ -90,19 +90,9 @@ constexpr bool test() {
   return true;
 }
 
-constexpr void cppreference_test() {
-  {
-  static_assert("" && (std::div_sat<int>(6, 3) == 2)           // not saturated
-                && (std::div_sat<int>(INT_MIN, -1) == INT_MAX) // saturated
-                && (std::div_sat<unsigned>(6, 3) == 2)         // not saturated
-  );
-  }
-}
-
 int main(int, char**) {
   test();
   static_assert(test());
-  cppreference_test();
 
   return 0;
 }
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/mul_sat.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/mul_sat.pass.cpp
index 1192b6b4a61dc0..2fbecd2fa55ff6 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/mul_sat.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/mul_sat.pass.cpp
@@ -81,13 +81,13 @@ constexpr bool test_unsigned() {
 }
 
 constexpr bool test() {
-  // signed
+  // Signed
   test_signed<signed char>();
   test_signed<short int>();
   test_signed<int>();
   test_signed<long int>();
   test_signed<long long int>();
-  // unsigned
+  // Unsigned
   test_unsigned<unsigned char>();
   test_unsigned<unsigned short int>();
   test_unsigned<unsigned int>();
@@ -97,23 +97,9 @@ constexpr bool test() {
   return true;
 }
 
-constexpr void cppreference_test() {
-  {
-    static_assert(
-        "" && (std::mul_sat<int>(2, 3) == 6)                     // not saturated
-        && (std::mul_sat<int>(INT_MAX / 2, 3) == INT_MAX)        // saturated
-        && (std::mul_sat<int>(-2, 3) == -6)                      // not saturated
-        && (std::mul_sat<int>(INT_MIN / -2, -3) == INT_MIN)      // saturated
-        && (std::mul_sat<unsigned>(2, 3) == 6)                   // not saturated
-        && (std::mul_sat<unsigned>(UINT_MAX / 2, 3) == UINT_MAX) // saturated
-    );
-  }
-}
-
 int main(int, char**) {
   test();
   static_assert(test());
-  cppreference_test();
 
   return 0;
 }
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturate_cast.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturate_cast.pass.cpp
index 889b1864f128e5..7e75903c46b109 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturate_cast.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturate_cast.pass.cpp
@@ -18,8 +18,6 @@
 #include <limits>
 #include <numeric>
 
-#include <print>
-
 template <typename IntegerResultT, typename IntegerT>
 constexpr bool test_signed_notsaturated() {
   constexpr auto minVal = std::numeric_limits<IntegerT>::min();
@@ -77,7 +75,7 @@ constexpr bool test_unsigned_saturated() {
 }
 
 constexpr bool test() {
-  // signed
+  // Signed
   test_signed_notsaturated<long long int, signed char>();
   test_signed_notsaturated<long long int, short int>();
   test_signed_notsaturated<long long int, int>();
@@ -88,7 +86,7 @@ constexpr bool test() {
   test_signed_saturated<int, long long int>();
   test_signed_saturated<long int, long long int>();
   test_signed_saturated<long long int, long long int>();
-  // unsigned
+  // Unsigned
   test_unsigned_notsaturated<unsigned long long int, unsigned char>();
   test_unsigned_notsaturated<unsigned long long int, unsigned short int>();
   test_unsigned_notsaturated<unsigned long long int, unsigned int>();
@@ -103,30 +101,9 @@ constexpr bool test() {
   return true;
 }
 
-constexpr void cppreference_test() {
-  {
-    constexpr std::int16_t x1{696};
-
-    constexpr std::int8_t x2 = std::saturate_cast<std::int8_t>(x1);
-    static_assert(x2 == std::numeric_limits<std::int8_t>::max());
-
-    constexpr std::uint8_t x3 = std::saturate_cast<std::uint8_t>(x1);
-    static_assert(x3 == std::numeric_limits<std::uint8_t>::max());
-
-    constexpr std::int16_t y1{-696};
-
-    constexpr std::int8_t y2 = std::saturate_cast<std::int8_t>(y1);
-    static_assert(y2 == std::numeric_limits<std::int8_t>::min());
-
-    constexpr std::uint8_t y3 = std::saturate_cast<std::uint8_t>(y1);
-    static_assert(y3 == 0);
-  }
-}
-
 int main(int, char**) {
   test();
-  // static_assert(test());
-  cppreference_test();
+  static_assert(test());
 
   return 0;
 }
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/sub_sat.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/sub_sat.pass.cpp
index a3f0d175ec350d..be337dae275d91 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/sub_sat.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/sub_sat.pass.cpp
@@ -69,13 +69,13 @@ constexpr bool test_unsigned() {
 }
 
 constexpr bool test() {
-  // signed
+  // Signed
   test_signed<signed char>();
   test_signed<short int>();
   test_signed<int>();
   test_signed<long int>();
   test_signed<long long int>();
-  // unsigned
+  // Unsigned
   test_unsigned<unsigned char>();
   test_unsigned<unsigned short int>();
   test_unsigned<unsigned int>();
@@ -85,23 +85,9 @@ constexpr bool test() {
   return true;
 }
 
-constexpr void cppreference_test() {
-  {
-    static_assert(
-        "" && (std::sub_sat<int>(INT_MIN + 4, 3) == INT_MIN + 1) // not saturated
-        && (std::sub_sat<int>(INT_MIN + 4, 5) == INT_MIN)        // saturated
-        && (std::sub_sat<int>(INT_MAX - 4, -3) == INT_MAX - 1)   // not saturated
-        && (std::sub_sat<int>(INT_MAX - 4, -5) == INT_MAX)       // saturated
-        && (std::sub_sat<unsigned>(4, 3) == 1)                   // not saturated
-        && (std::sub_sat<unsigned>(4, 5) == 0)                   // saturated
-    );
-  }
-}
-
 int main(int, char**) {
   test();
   static_assert(test());
-  cppreference_test();
 
   return 0;
 }

>From f5db95e38044caaf7164dd9bb73ba2250f0da9ae Mon Sep 17 00:00:00 2001
From: Zingam <zingam at outlook.com>
Date: Fri, 12 Jan 2024 21:30:22 +0200
Subject: [PATCH 3/4] Fixed "code formatter" error

---
 libcxx/modules/std/numeric.inc | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libcxx/modules/std/numeric.inc b/libcxx/modules/std/numeric.inc
index 0800319de820d9..3bc7b231681584 100644
--- a/libcxx/modules/std/numeric.inc
+++ b/libcxx/modules/std/numeric.inc
@@ -58,10 +58,10 @@ export namespace std {
 #if _LIBCPP_STD_VER >= 26
   // [numeric.sat], saturation arithmetic
   using std::add_sat;
-  using std::sub_sat;
-  using std::mul_sat;
   using std::div_sat;
+  using std::mul_sat;
   using std::saturate_cast;
+  using std::sub_sat;
 #endif
 
 } // namespace std

>From db3e72e22de261712f924f62929a6d499d1827d6 Mon Sep 17 00:00:00 2001
From: Zingam <zingam at outlook.com>
Date: Fri, 12 Jan 2024 23:28:21 +0200
Subject: [PATCH 4/4] Cleanup

---
 libcxx/include/__numeric/saturation_arithmetic.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/libcxx/include/__numeric/saturation_arithmetic.h b/libcxx/include/__numeric/saturation_arithmetic.h
index ec265435eb6a64..6d13ec5b3fd5bc 100644
--- a/libcxx/include/__numeric/saturation_arithmetic.h
+++ b/libcxx/include/__numeric/saturation_arithmetic.h
@@ -28,7 +28,6 @@ template <typename _Tp>
 concept __libcpp_standard_integer = __libcpp_unsigned_integer<decay_t<_Tp>> || __libcpp_signed_integer<decay_t<_Tp>>;
 
 template <__libcpp_standard_integer _Tp>
-//   requires __libcpp_standard_integer<_Tp>
 _LIBCPP_HIDE_FROM_ABI constexpr _Tp add_sat(_Tp __x, _Tp __y) noexcept {
   if (_Tp __sum; !__builtin_add_overflow(__x, __y, &__sum))
     return __sum;



More information about the libcxx-commits mailing list