Document Number: | P3161R1 |

Date: | 2024/02/16 |

Reply-to: | cpp@kaotic.software |

Authors: | Tiago Freire |

Audience: | SG6, LWG |

C++26

Addition and uniformization of integer arithmetic functions with overflow behavior

# | Description |
---|---|

0 | Initial draft |

1 | Corrected suggested implementation of would_cast_modify, corrected div_wide definition, and made minor editorial changes. |

1. Motivation

2. Logical organization

3. List of functions

3.1. add_carry

3.2. sub_borrow

3.3. mul_wide

3.4. div_wide

3.5. div

3.6. would_cast_modify

3.7. is_div_defined

3.8. is_div_wide_defined

4. Why is safe overflow excluded?

4.1. add_overflow/sub_overflow

4.2. div_overflow

4.3. mul_overflow

5. The problem with division

6. Design choice analysis

6.1. Library header

6.2. Return type

6.3. Extra classifications

7. Naming

8. Feature test macro

9. Wording

10. Acknowledgements

11. References

In many applications sometimes one has to deal with integer arithmetic with numbers whose width far exceeds that for which the defined standard types can support, or ever will be able to support since the width of the representable numbers are application-specific and can grow arbitrarily large (within the finite capabilities of the underlying device). In other applications even if extended precision is not required it might be important to know if the result of an operation is valid (i.e. that it did not overflow).

Algorithms to deal with these are quite trivial, and most CPUs offer a good range of support for the required instructions (with these exact usages in mind), but there are no equivalent abstractions that are available in the standard C++. It's a rather cumbersome and error prone to implement similar functionality using only C++, resulting in extremely inefficient code for what could often be a couple or even a single line of assembly.

The paper [P0543] has already been accepted and is on track for C++26, however this is just a narrow type of overflow behavior which is insufficient to implement things like multi-word integers.

Taking into consideration that:

- Some multi-word operations require a combination of multiple instances of such functions for a specific type.
- CPUs (such as x86-64) may require certain operands to be preloaded into specific registers, and this is typically achieved by mov instruction for both preparation and the recovery of the result.
- And in many of these cases the output register just so happens to be perfectly aligned with the register where the value needs to be on a follow up operation, making a mov operations unnecessary.

The integer arithmetic function featured in this paper can be organized in the following way:

They can be divided in terms of related operations

- addition
- subtraction
- multiplication
- division/remainder - reciprocal operation to multiplication
- casting - The conversion from one machine representation of an integer type to another

Or divided in terms of families of overflow behavior:

- Standard - No explicit overflow behavior, this are the standard cpp operations (+, -, *, /, %)
- Saturation - Value saturates on overflow, as adopted by paper [P0543]
- Reporting - Operations reports when an overflow occurs
- Extended width - Output of operation has extended width to avoid overflow

The functional grouping of functions can be summarized by the following table:

Type of operation | Standard | Saturated | Safe overflow | Wide arithmetic |
---|---|---|---|---|

Addition | + | add_sat | add_carry | |

Subtraction | - | sub_sat | sub_borrow | |

Multiplication | * | mul_sat | mul_wide | |

Division, Remainder | /, %, div | div_sat* | is_div_defined | div_wide, is_div_wide_defined |

Type casting | static_cast | saturate_cast | would_cast_modify | N/A |

* maybe removed

The saturation family has been addressed in paper [P0543] and has already been adopted, explanation is thus skipped.

template<class T> constexpr add_carry_result<T> add_carry(T, T, bool carry) noexcept;

x86-64 | ARM64 |
---|---|

ADC, ADCX, ADOX, ADD | ADC, ADCS, ADDS |

clang | msvc | intel intrinsic |
---|---|---|

__builtin_addcb, __builtin_addcs, __builtin_addc, __builtin_addcl, __builtin_addcll |
- | _addcarry_u8, _addcarry_u16, _addcarry_u32, _addcarry_u64, _addcarryx_u32, _addcarryx_u64 |

Addition with carry

With *T* an integer input type, and inputs:

- V1 :T
- V2 :T
- carry : bool

and outputting:

- low_result :T - the low bits of
*result*with the same width of T - overflow :bool - a bool flag that is set to true if
*low_result*does not represent the value of*result*(i.e. overflow)

template<class T> constexpr sub_borrow_result<T> sub_borrow(T, T, bool borrow) noexcept;

x86-64 | ARM64 |
---|---|

SBB, SUB | SBC, SBCS, SUBS |

clang | msvc | intel intrinsic |
---|---|---|

__builtin_subcb, __builtin_subcs, __builtin_subc, __builtin_subcl, __builtin_subcll |
- | _subborrow_u8, _subborrow_u16, _subborrow_u32, _subborrow_u64 |

Subtraction with borrow

With *T* an integer input type, and inputs:

- V1 :T
- V2 :T
- borrow : bool

and outputting:

- low_result :T - the low bits of
*result*with the same width of T - overflow :bool - a bool flag that is set to true if
*low_result*does not represent the value of*result*(i.e. overflow)

template<class T> constexpr mul_wide_result<T> mul_wide(T, T) noexcept;

x86-64 | ARM64 |
---|---|

IMUL, MUL, MULX | SMULL, UMULL, (MUL, SMULH, UMULH) |

clang | msvc | intel intrinsic |
---|---|---|

- |
_mul128, _umul128, __emul, __emulu, __mulh, __umulh |
mulx_u32, mulx_u64 |

Multiplication with twice the width of inputs.
With *T* an integer input type, and inputs:

- V1 :T
- V2 :T

and outputting:

- low_result :T - the low-bits of
*result* - high_result :T - the high-bits of
*result*

template<class T> constexpr div_result<T> div_wide(T dividend_high, T dividend_low, T divisor) noexcept;

x86-64 | ARM64 |
---|---|

DIV, IDIV | - |

clang | msvc | intel intrinsic |
---|---|---|

- |
_udiv64, _udiv128, _div64, _div128, | - |

The reciprocal operation to mul_wide
With *T* an integer input type, and inputs:

- dividend_high :T
- dividend_low :T
- divisor :T

dividend = (dividend_high << sizeof(T)*8) | dividend_low;

result_quo = dividend / divisor;

result_rem = dividend % divisor;

and outputting:

- output_quo :T -
*result_quo*truncated to the width of T - output_rem :T -
*result_rem*

- If divisor is 0 or if output_quo overflows the behaviour is undefined. This can be pre-checked with
*!is_div_wide_defined* - result_quo is the value such that abs(result_quo) has the lowest possible value that satisfies abs(dividend - divisor * result_quo) < abs(divisor)

template<class T> constexpr div_result<T> div(T dividend, T divisor) noexcept;

Performs a fused division with remainder.

std::div already exists in the standard. This paper only notes insufficient design and proposes a signature change in future development.

template<class T, class U> constexpr bool would_cast_modify(U) noexcept;

Checks if input value of type U is not representable in type T (i.e. that a cast would modify the value)

template<class T, class U> [[nodiscard]] inline constexpr bool would_cast_modify([[maybe_unused]] U const x) { if constexpr (std::is_signed_v<T> == std::is_signed_v<U>) { if constexpr (sizeof(T) >= sizeof(U)) { return false; } else { if constexpr(std::is_signed_v<T>) { return (x < std::numeric_limits<T>::min()) || (x > std::numeric_limits<T>::max()); } else { return (x > std::numeric_limits<T>::max()); } } } else { if constexpr(std::is_signed_v<T>) { return x > std::numeric_limits<T>::max(); } else { if constexpr(sizeof(T) >= sizeof(U)) { return (x < 0); } else { return (x < 0) || x > std::numeric_limits<T>::max(); } } } }

template<class T>

constexpr bool is_div_defined(T dividend, T divisor) noexcept;

Checks if std::div is defined for the input arguments

i.e. Checks that the divisor is not 0, and in case of signed division that result would not overflow, i.e.
inputs are not the degenerate case std::numeric_limits

template<typename T> [[nodiscard]] inline bool is_div_defined([[maybe_unused]] T const dividend, T const divisor) { if constexpr(std::is_signed_v<T>) { return divisor != 0 && (dividend != std::numeric_limits<T>::min() || divisor != -1); } else { return divisor != 0; } }

template<class T> constexpr bool is_div_wide_defined(T dividend_high, T dividend_low, T divisor) noexcept;

Checks if std::div_wide is defined for the input arguments

i.e. Checks that the divisor is not 0, and that result would not overflow

template<typename T> [[nodiscard]] bool is_div_wide_defined(T const hi_dividend, [[maybe_unused]] T const low_dividend, T const divisor) { if constexpr(std::is_signed_v<T>) { using uint_t = std::make_unsigned_t<T>; constexpr uintptr_t sign_offset = (sizeof(uint_t) * 8) - 1; constexpr uint_t lower_mask = static_cast<uint_t>(~(uint_t{1} << sign_offset)); uint_t const hi = std::bit_cast<uint_t>(hi_dividend); uint_t const low = std::bit_cast<uint_t>(low_dividend); uint_t const div = std::bit_cast<uint_t>(divisor); uint_t const hi_flag = static_cast<uint_t>((hi << 1) | (low >> sign_offset)); if(hi_dividend < 0) { if(divisor < 0) { return (hi_flag > div) || ((hi_flag == div) && (low & lower_mask)); } else { uint_t const mirror = ~div; return (hi_flag > mirror) || ((hi_flag == mirror) && ((low & lower_mask) > ((mirror & lower_mask) + 1))); } } else { if(divisor < 0) { uint_t const mirror = (~div) + 1; return (hi_flag < mirror) || ((hi_flag == mirror) && ((low & lower_mask) < mirror)); } else { return hi_flag < div; } } } else { return hi_dividend < divisor; } }

This is intentional, after better analysis of the problem, I have came to the conclusion that perhaps it is better not to. And here is why.

add_overflow and sub_overflow, are essentially the same as add_carry/sub_borrow except that the carry/borrow bit
are fixed to 0. And indeed specialized CPU instructions exists to perform this behavior which are different from those required by a generic add_carry/sub_borrow (when the carry/borrow bit aren't known at compile time).

However, I feel that this is best solved with a compiler optimization. If the compiler can deduce at compile that when
add_carry/sub_borrow is used the carry/borrow bit is a constant equal to 0, it is free to decide to use the cheapest instruction, i.e. to degenerate add_carry/sub_borrow to what
add_overflow/sub_overflow would have been without these instructions actually being provided.

Unsigned integer division never overflows, and signed integer division only overflows in the degenerate case INT_MAX/-1 which is trivial to check.

One may also note that division by 0 is still undefined, and a user is expect to protect against this case before trying to divide.

In addition there's no CPU instruction that would give the right result in the degenerate case; a library implementer would always need to explicitly check for the degenerate case before performing the division.

Given these, a user would be better served by being provided with a **is_div_defined** to the check themselves and then decide what behavior their application should have.

Although CPU instruction support exists in the form of overflow flag on multiplication, and although the behavior makes sense, the use cases are questionable.

One could still implement a sub-optimal "mul_overflow" by using "mul_wide" if need be.
But it is still the odd one out, it is better left for a future proposal if a use case exist.

All functions in this paper have well defined behavior regardless of the input with the exceptions of those related to division.
Undefined behavior occurs when either dividing by zero or when the resulting value would overflow.

Functions such as the trivial division (/), std::div, and std::div_sat already expects the user to check and avoid calling the function if it would trigger undefined behavior.

Platforms that need to implement such function in software can decide to inflict only mild consequences upon the user.
But since the intention is to utilize specialized CPU instructions, and those instructions trap in these conditions, it is expected that division
in practice will have that behavior. And unhandled trap results in a panic by the operation system which promptly ejects the application from execution.

While most cases are trivial to check, and an average user would be able to write checks by themselves, signed div_wide on the other hand is not trivial,
and I would not expect the average user to so easily be able to write it.

Providing such functions without the means to check for undefined behavior would be irresponsible.
As we wouldn't so much be providing a useful function to implement algorithms, as we would be providing a function that would sporadically and ungracefully eject the user's code
without much in the way a user can do to prevent it and still allow legitimate use cases.

The question then poses, should the functions themselves always check the inputs before attempting the division proper?
The answer here is no. Many algorithms can guarantee that no undefined behavior is ever triggered by the way that they are setup without the need to check, and we would be unjustifiably penalizing such users with unneeded overhead.
Take for a example an algorithm that tries to reduce a multi-word unsigned number by dividing it with a divisor that is a compile time constant not equal to 0.
Such an algorithm would start with an std::div of the highest order word, and then feeding the remainder (which is guaranteed < divisor) as the dividend_high of subsequent calls to div_wide to reduce lower order words,
at no point in such an algorithm would it ever be possible to trigger undefined behavior.

Another question that is raised regarding std::div_sat is either or not it should be removed? Unsigned division never overflows (and thus would never cause std::div_sat to express its undefined behavior), and signed division only overflows in the degenerate case (which is trivial to check), and since division by 0 is still expected to be checked by the user anyway would it not be easier for the user to also check for signed overflow (and implement the overflow behavior themselves)? The function is valid, and still makes sense, but may not actually be usefull.

We also have to address the problem of std::div, (which currently exists for C feature parity) but only supports int, long, long long, and std::intmax_t. It would be ideal to upgrade the definition of std::div to achieve feature parity, however this cannot be done without breaking backwards compatibility.

Since removing std::div_sat is a neutral problem independent of the addition of new features, and because fixing std::div may prove controversial, fixing them will not be part of this proposal. I shall only be raising the issue now, proposal to fix it to come at a later date.

I agree with the approach presented by [P0543] which adds the new functionality to the <numeric> library.

These are functions that performs numerical operations, a new library is not required.

The use of template arguments is preferable over the alternatives for the following reasons:

- In contexts where we are working with template types, it would be much easier to just use the name of the function and let the type system figure out which "specialization" to used based on the types being provided. This excludes named functions as it would require to create specific overloads depending on type being used or create my own facility that wraps around these types that does the same job. If I have to create my own facility, and that is what actually gets used in replacement of the standard, then why not make that the standard?
- Using specific names for each type can be error prone, as one not only needs to be careful and specific with the type selection within the context being used. Which increases the risk of picking the wrong type, or have input types not match, and then have input types being silently promoted without warning to do something unintended. If input types are not consistent it should definitely be a compiler error, and if they disagree the user should be forced to explicitly cast the inputs to the intended type before proceeding.
- There's not a 1 to 1 correspondence between all integer types and all types of bit-width that integers can have (a more detailed explanation bellow), and not all types of integers are mandatory to be available in all platforms. Just stating in the standard that the input parameter is a templated type, and that the only allowed templated types must be integers is much simpler in order to ensure that all intended cases are covered, while named or overloaded definition would require a disclaimer regarding which integers are supported and which versions are available depending on the combination of integers available for that platform.
- If you require a specific instance of the function for a specific type to be explicitly declared this cannot be easily done with overloads, but can still be easily done with templates as one can just explicitly declare the exact templated type and this happens in a much more natural language e.x. mul_wide<uint32_t> is much more explicit than ui32_mul_wide.

There are several ways to return multiple output values. For the cases where there's only 1 output value, just return the value as is with no additional structure. For the other cases these were the options considered:

- Using an existing data structure other than a tuple, such as std::pair
- Using a new dedicated data structure
- Use a tuple

In my opinion it is best to stay away from using types like std::pair given their confusing ambiguity, take for example "mul_wide" return a std::pair assigned to a variable named val, what would be the meaning of "val.first" or "val.second"? Do you put the high bits in ".first" or ".second"? Unless the names of the member variables explicitly spell their meaning (ex. result_high and result_low) the whole thing is just hard to read, a dedicated data structure has better properties for this purpose.

If we use a new dedicated data structure, now the problem is what do we name them?
They are relatively specific to the function that they are associated with, so one could for example use the pattern <function_name>_result (similar to what happens with std::from_chars), example "mul_wide_result".

In addition, the type would need to be templated since the function parameters and consequentially the types of the expected output is also templated.
This would require defining at least 4 new templated data types, in most cases supporting 10 different integer types, for a potential of 40 new data structures in practice.
This isn't a big deal, compilers can certainly handle that quite easily, and the effort for implementing those is proportional to the effort of implementing the new features, but can we do better?

I lament the fact that C++ doesn't have a better syntax and support features for multiple return values.
std::tuple has become the closest thing to it, which for this case has some nice properties.
There's no name confusion because there are no names, it doesn't require defining new types, presenting the signature of the function is almost completely sufficient,
needing only a side note specifying which value means what (and only for a couple of cases).

Using std::tuple also provides some quite satisfying syntax in order to split the return values such as:

auto [value, remainder] = div_wide(...); std::tie(value, remainder) = div_wide(...);

Performing std::get<0>(func(...)) would provide the same result as the trivial operators (+, -, *, /) would in most platforms,
performing std::get<1>(func(...)); would recover information lost by the trivial operators (+, -, *, /)
(i.e. carry/borrow bits, high bits in mul_wide, and the remainder on division).

While not perfect, it is quite suitable for the intended purpose, without visible undesirable features except in one specific case "mul_wide".

"mul_wide" has to output high and low bits of the resulting multiplication, in platforms that don't have direct hardware support for a specific type but has a wider integer type
an implementer may want to implement "mul_wide" by internally casting the inputs to the wider integer, doing a trivial multiplication and then splitting the high and low bits.
By defining that the low bits are to be return on the first element of the tuple, on little-endian machines it just so happens that the memory layout matches the fact that low bytes of an integer come first allowing a compiler to optimize away any bit manipulation in all cases.
But we need to acknowledge that big-endian machines also exist, and it just so happens that the expected byte order is reversed, a compiler can still optimize away bit manipulation depending on what happens on assignment,
but it might not be possible do so in all circumstances if the value needs to be passed to an unknow context (like taking an address). Using a new specialized data structure "mul_wide_result" that only defines that "result_high" and "result_low" must be members of it without specifying
the order in which they should appear, an implementer would be free to swap the values around to pick the best memory layout for their specific platform.

In my opinion, considering that big-endian devices are now a days relatively rare, and considering the rare situations where a compiler couldn't just optimize the whole thing anyway, that most use cases are for a widest type available on that platform where this trick couldn't be used anyway, and it's just "mul_wide" that has this problem,
this shouldn't justify using new data type over a std::tuple.

However, feedback from the initial draft led to the conclusion that tuples are not popular among developers given the ambiguity of the placement of outputs of mul_wide and dive_wide in anonymous structures. And thus, named structures were selected for the sake of improved clarity.

With the exception of div_wide, all functions have "well" defined behaviour, and they can all be made **constexpr** and **noexcept**.
There's a clear benefit to computing things at compile time if possible, and if one can optimize assuming that they don't throw exceptions, then why shouldn't they be?

Names like **add**, **sub**, **mul**, **div**, have long standing unwritten conventions as to what they should mean, its usage is unambiguous, there is precedent for it,
and they are all exactly 3 characters long which makes them look really nice when aligning them together. So I propose to keep that.
The rest of the text in the function name are mostly plain English and are well know in regards to their meaning.

Some may object to the naming of sub_borrow as opposed to sub_carry, as this maybe seen as "an endorsement of intel's naming convention".
My counterargument to this objection is "intel is not wrong", in mathematical lingo "borrow" is used for subtraction not "carry",
and it makes sense from a clarity perspective in terms of what the flag means. When the flag is 1 "carry" means the flag "adds" to the value, "borrow" means the flag "subtracts", hence add_carry and sub_borrow.
Not sub_carry (i.e. flag adds 1 after subtracting), and not add_borrow (i.e. flag subtracts 1 after adding).

The name of the accompanying data structures used for return types will use the name of the function with the post-fix "_result", this is practice that is common in the standard (for example std::from_chars_result).
One noticeable exception to this rule is the return structure for div_wide which drops the "_wide", i.e. div_result.

The reasoning for this is two fold, first because the name is available, secondly because the returning output of div_wide and potentially extended future version of std::div are exactly the same,
and the same type can be used for both without the need to proliferate unnecessary identical structures.

I propose the usage of __cpp_lib_overflow_arithmetic as a test feature for all families and remove __cpp_lib_saturation_arithmetic.

In subclause 27.9 [numeric.ops.overview], change <numeric> as indicated:

~~// 27.10.17, saturation arithmetic~~

// 27.10.17, overflow arithmetic

In subclause 27.9 [numeric.ops.overview], add to header

template<class T, class U> constexpr T saturate_cast(U x) noexcept; // freestanding

template<class T> struct add_carry_result { // freestanding T low_bits; bool overflow; }; template<class T> using sub_borrow_result = add_carry_result; template<class T> struct mul_wide_result { // freestanding T low_bits; T high_bits; }; template<class T> struct div_result { // freestanding T quotient; T remainder; }; template<class T> constexpr add_carry_result<T> add_carry(T x, T y, bool carry) noexcept; //freestanding template<class T> constexpr sub_borrow_result<T> sub_borrow(T left, T right, bool borrow) noexcept; //freestanding template<class T> constexpr mul_wide_result<T> mul_wide(T x, T y) noexcept; //freestanding template<class T> constexpr div_result<T> div_wide(T dividend_high, T dividend_low, T divisor ) noexcept; //freestanding template<class T, class U> constexpr bool would_cast_modify(U x) noexcept; //freestanding template<class T> constexpr bool is_div_defined(T dividend, T divisor) noexcept //freestanding template<class T> constexpr bool is_div_wide_defined(T dividend_high, T dividend_low, T divisor) noexcept; //freestanding

}

Rename subclause 27.10.17 ~~[numeric.sat]~~ to [numeric.overflow]

Amend subclauses 27.10.17 as follows

~~27.10.17.1 Arithmetic functions [numerics.sat.func]~~

27.10.17.1 Arithmetic typedefs [numerics.overflow.typedefs] template<class T> struct add_carry_result { T low_bits; bool overflow; }; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). template<class T> using sub_borrow_result = add_carry_result; template<class T> struct mul_wide_result { T low_bits; T high_bits; }; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). template<class T> struct div_result { T quotient; T remainder; }; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). 27.10.17.2 Arithmetic functions [numerics.overflow.func]

[Note 1: In the following descriptions, an arithmetic operation is performed as a mathematical operation with infinite range and then it is determined whether the mathematical result fits into the result type. — end note] template<class T> constexpr T add_sat(T x, T y) noexcept; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). Returns: If x + y is representable as a value of type T, x + y; otherwise, either the largest or smallest representable value of type T, whichever is closer to the value of x + y. template<class T> constexpr T sub_sat(T x, T y) noexcept; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). Returns: If x - y is representable as a value of type T, x - y; otherwise, either the largest or smallest representable value of type T, whichever is closer to the value of x - y. template<class T> constexpr T mul_sat(T x, T y) noexcept; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). Returns: If x × y is representable as a value of type T, x × y; otherwise, either the largest or smallest representable value of type T, whichever is closer to the value of x × y. template<class T> constexpr T div_sat(T x, T y) noexcept; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). Preconditions: y != 0 is true. Returns: If T is a signed integer type and x == numeric_limits::min() && y == -1 is true, numeric_limits ::max(), otherwise, x / y. Remarks: A function call expression that violates the precondition in the Preconditions element is not a core constant expression ([expr.const]).

template<class T> constexpr add_carry_result<T> add_carry(T x, T y, bool carry) noexcept; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). Returns: add_carry_result<T> with the memberlow_bitsset to the result x + y + (carry ? 1 : 0) truncated to the size of T, the memberoverflowis set to true if result is not representable as a value of type T and false if otherwise. template<class T> constexpr sub_borrow_result<T> sub_borrow(T left, T right, bool borrow) noexcept; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). Returns: sub_borrow_result<T> with the memberlow_bitsset to the e result left - right - (borrow ? 1 : 0), the memberoverflowis set to true if result is not representable as a value of type T and false if otherwise. template<class T> constexpr mul_wide_result<T> mul_wide(T x, T y) noexcept; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). Returns: mul_wide_result<T> set with the result of x × y, the memberlow_bitsis set with the least significant bits that can fit in a value of type T and the memberhigh_bitsis set with the most significant bits. template<class T> constexpr div_result<T> div_wide(T dividend_high, T dividend_low, T divisor) noexcept; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). Returns: div_result with the memberquotientset to the result of (dividend_high × 2^{8sizeof(T)}| dividend_low) / divisor, and the memberremainderset to the remainder of the same division. Preconditions: is_div_wide_defined(dividend_high, dividend_low, divisor) evaluates to true Remarks: A function call expression that violates the precondition in the Preconditions element is not a core constant expression ([expr.const]). 27.10.17.3 Checking[numeric.overflow.check] template<class T, class U> constexpr bool would_cast_modify(U x) noexcept; Constraints: T and U are signed or unsigned integer type ([basic.fundamental]). Returns: true if x is representable as a value of type T, false if otherwise template<class T, class U> constexpr bool is_div_defined(T dividend, T divisor) noexcept; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). Returns: true if T is unsigned and divisor != 0 or true if divisor != 0 and the result of (dividend / divisor) is representable as a value of type T, false if otherwise template<class T, class U> constexpr bool is_div_wide_defined(T dividend_high, T dividend_low, T divisor) noexcept; Constraints: T is a signed or unsigned integer type ([basic.fundamental]). Returns: true if divisor != 0 and the result of (dividend_high × 2^{size_in_bits_of(T)}| dividend_low) / divisor is representable as a value of type T, false if otherwise

~~27.10.17.2 Casting[numeric.sat.cast]~~

27.10.17.4 Casting[numeric.overflow.cast]

template<class R, class T> constexpr R saturate_cast(T x) noexcept; Constraints: R and T are signed or unsigned integer types ([basic.fundamental]). Returns: If x is representable as a value of type R, x; otherwise, either the largest or smallest representable value of type R, whichever is closer to the value of x.

Amend a feature-test macro in [version.syn]:

~~#define __cpp_lib_saturation_arithmetic~~

#define __cpp_lib_overflow_arithmetic

Thanks to Jan Schultke for the feedback on function return types and editorial review.

- ISO/IEC JTC1 SC22 WG21 P0543R3: Saturation arithmetic by Jens Maurer
- ISO/IEC JTC1 SC22 WG21 P3018R0: Low-Level Integer Arithmetic by Andreas Weis
- Unified integer overflow arithmetic analysis paper
- Intel® 64 and IA-32 Architectures Software Developer’s Manual Vol.2
- Arm® Architecture Reference Manual
- MSVC intrinsics
- Clang builtins