1. Revision History
R1 => R2
-
Updated to use new names for functions (e.g.,
instead ofsaturating_add ).add_sat -
Updated to latest names from working draft (e.g.,
)basic_vec -
Fixed some rendering issues.
R0 => R1
-
Added implementation experience.
-
Improved wording.
2. Motivation
The Working Draft of C++26 includes data parallel types. It mostly provides operators
which work on or with types, but it also includes overloads of
useful functions from other parts of C++ (e.g., sin, cos, abs). In [P0543R3] a
proposal was made to provide saturating operation support for some basic
arithmetic operations and casts. In particular, , , and are provided. These
perform saturating arithmetic operations which are effectively performed in
infinite precision, and will return the smallest or largest value when it is too
large to be represented in that type. In addition, is also
provided to convert to a new type, and to saturate to the range of that type if
required.
These saturating functions should be provided in as element-wise
operations.
Note: Previous versions of this proposal used different names, which were updated as per NB comment FR-026-265.
3. Implementation Experience
The most common types of saturating operations are addition, subtraction, and
casting. All three of these functions have been implemented in Intel’s reference
implementation and used in our software products. Where hardware support is
available for a data type these functions compile into native instructions
(e.g., 16-bit integer saturations compile into , , and respectively). For data types which have no saturating support in the
hardware for those three functions (e.g., large integers) the compiler can
generate efficient code to perform the operation (in the case of LLVM the function is used to hand this task to the compiler, rather
than having the library itself generate the required code sequence). Examples of
native versus non-native instruction sequences are given here:
| Source | Output from clang 20 |
|
|
|
|
|
|
The other saturating operations haven’t been implemented in the reference software as they are rarely needed. However, they can be trivially implemented in terms of the existing draft C++26 support for scalar saturating operations, or an optimized equivalent can be synthesized.
4. Wording
4.1. Modify [version.syn]
In [version.syn] bump the version.
4.2. Modify [simd.syn]
In the header synopsis - [simd.syn] - add at the end after the "Complex Math" functions.
template < simd - floating - point V > rebind_t < complex < typename V :: value_type > , V > polar ( const V & x , const V & y = {}); template < simd - complex V > constexpr V pow ( const V & x , const V & y ); // [simd.saturating.math], saturating math functions template < vec - simd - type V > constexpr V saturating_add ( const V & x , const V & y ) noexcept ; template < vec - simd - type V > constexpr V saturating_sub ( const V & x , const V & y ) noexcept ; template < vec - simd - type V > constexpr V saturating_mul ( const V & x , const V & y ) noexcept ; template < vec - simd - type V > constexpr V saturating_div ( const V & x , const V & y ) noexcept ; template < class U , vec - simd - type V > constexpr rebind_t < U , V > saturating_cast ( const V & v ) noexcept ;
Add the following to the end of the using declarations:
// See [simd.complex.math], simd complex math using simd :: real ; using simd :: imag ; using simd :: arg ; using simd :: norm ; using simd :: conj ; using simd :: proj ; using simd :: polar ; // See [simd.saturating.math], saturating math functions using simd :: saturating_add ; using simd :: saturating_sub ; using simd :: saturating_mul ; using simd :: saturating_div ; using simd :: saturating_cast ;
4.3. Add new section [simd.saturating.math]
Add the following section after [simd.complex.math].
�
saturating math functions [simd.saturating.math]basic_vec template < vec - simd - type V > constexpr V saturating_add ( const V & x , const V & y ) noexcept ; template < vec - simd - type V > constexpr V saturating_sub ( const V & x , const V & y ) noexcept ; template < vec - simd - type V > constexpr V saturating_mul ( const V & x , const V & y ) noexcept ; Constraints: The type
is a signed or unsigned integer type ([basic.fundamental]).V :: value_type Returns: A
object where the ith element is initialized to the result ofbasic_vec for all i in the rangesat - func ( x [ i ], y [ i ]) , where[ 0 , V :: size ()) is the corresponding function from [numerics.sat.func].sat - func template < vec - simd - type V > constexpr V saturating_div ( const V & x , const V & y ) noexcept ; Constraints: The type
is a signed or unsigned integer type ([basic.fundamental]).V :: value_type Preconditions: For all i in the range of
,[ 0. . V :: size ()) isy [ i ] == 0 false.Returns: A
where the ith element isbasic_vec for all i in the range ofsaturating_div ( x [ i ], y [ i ]) .[ 0. . V :: size ()) Remarks: If any
, the invocation is not a core constant expression.y [ i ] == 0 template < class U , vec - simd - type V > constexpr rebind_t < U , V > saturating_cast ( const V & v ) noexcept ; Constraints: Both
andU are signed or unsigned integer types ([basic.fundamental]).typename V :: value_type Returns: A
where the ith element isrebind_t < U , V > for all i in the range ofsaturating_cast < U > ( v [ i ]) .[ 0. . V :: size ())