Date: Tue, 8 Apr 2025 17:01:56 +0000
Here is a snippet on Compiler Explorer that demonstrates suboptimal code generation for saturated floating-point to integer cast (implemented with pure C++11 code) with GCC, Clang, and MSVC: https://godbolt.org/z/rrKGTchGv
std::saturated_cast should be extended to allow for saturated floating point to integer conversions.
std::saturated_cast<int>(std::numeric_limits<float>::quiet_NaN()) should be allowed to return an arbitrary, implementation-defined value that is within the range of an int (and that otherwise has well-defined behavior if no trapping floating point exceptions are raised) to avoid unnecessary overhead on ISA's such as POWER ISA where saturated floating-point to integer conversion instructions return a non-zero result for a NaN input value. In other words, std::saturated_cast<int>(std::numeric_limits<float>::quiet_NaN()) should have the same semantics as __builtin_nondeterministic_value(0), except that std::saturated_cast<int>(std::numeric_limits<float>::quiet_NaN()) is permitted to raise floating-point exceptions.
Here is more optimal assembly language codegen for various saturated floating-point to integer casts on POWER8:
/* std::int32_t SafeSatCastF32ToI32(float val); */
SafeSatCastF32ToI32:
fctiwz 1,1
mfvsrwz 3,1
extsw 3,3
blr
/* std::uint32_t SafeSatCastF32ToU32(float val); */
SafeSatCastF32ToU32:
fctiwuz 1,1
mfvsrwz 3,1
blr
/* std::int32_t SafeSatCastF64ToI32(double val); */
SafeSatCastF64ToI32:
fctiwz 1,1
mfvsrwz 3,1
extsw 3,3
blr
/* std::uint32_t SafeSatCastF64ToU32(double val); */
SafeSatCastF64ToU32(double):
fctiwuz 1,1
mfvsrwz 3,1
blr
/* std::int64_t SafeSatCastF64ToI64(double val); */
SafeSatCastF64ToI64(double):
fctidz 0,1
mfvsrd 3,0
blr
/* std::uint64_t SafeSatCastF64ToU64(double val); */
SafeSatCastF64ToU64(double):
fctiduz 0,1
mfvsrd 3,0
blr
Here is more optimal assembly language codegen for various saturated floating-point to integer casts on AArch64:
/* std::int32_t SafeSatCastF32ToI32(float val); */
SafeSatCastF32ToI32:
fcvtzs w0, s0
ret
/* std::uint32_t SafeSatCastF32ToU32(float val); */
SafeSatCastF32ToU32:
fcvtzu w0, s0
ret
/* std::int32_t SafeSatCastF64ToI32(double val); */
SafeSatCastF64ToI32:
fcvtzs w0, d0
ret
/* std::uint32_t SafeSatCastF64ToU32(double val); */
SafeSatCastF64ToU32(double):
fcvtzu w0, d0
ret
/* std::int64_t SafeSatCastF64ToI64(double val); */
SafeSatCastF64ToI64(double):
fcvtzs x0, d0
ret
/* std::uint64_t SafeSatCastF64ToU64(double val); */
SafeSatCastF64ToU64(double):
fcvtzu x0, d0
ret
Here is more optimal assembly language codegen for various saturated floating-point to integer casts on x86 with AVX10.2:
/* std::int32_t SafeSatCastF32ToI32(float val); */
SafeSatCastF32ToI32:
vcvttss2sis eax, xmm0
ret
/* std::uint32_t SafeSatCastF32ToU32(float val); */
SafeSatCastF32ToU32:
vcvttss2usis eax, xmm0
ret
/* std::int32_t SafeSatCastF64ToI32(double val); */
SafeSatCastF64ToI32:
vcvttsd2sis eax, xmm0
ret
/* std::uint32_t SafeSatCastF64ToU32(double val); */
SafeSatCastF64ToU32(double):
vcvttsd2usis eax, xmm0
ret
/* std::int64_t SafeSatCastF64ToI64(double val); */
SafeSatCastF64ToI64(double):
vcvttsd2sis rax, xmm0
ret
/* std::uint64_t SafeSatCastF64ToU64(double val); */
SafeSatCastF64ToU64(double):
vcvttsd2usis rax, xmm0
ret
On POWER8 and AArch64, std::saturated_cast<int>(f64_val) (where f64_val is a double-precision floating point value) should generate the same instruction sequence as static_cast<int>(f64_val), except that the compiler must not assume that the argument of the std::saturated_cast<int>(f64_val) is within the range of an int and that std::saturated_cast<int>(f64_val) has well-defined behavior if no floating point exceptions are raised, even if f64_val is outside of the range of an int.
Implementing optimal std::saturated_cast for float to int conversions requires support from the compiler as a generic, portable pure C++ implementation is suboptimal on many ISA's and as static_cast<int> invokes undefined behavior for values that are outside of the range of an int.
________________________________
From: Std-Proposals <std-proposals-bounces_at_[hidden]> on behalf of Thiago Macieira via Std-Proposals <std-proposals_at_[hidden]>
Sent: Tuesday, April 8, 2025 10:17 AM
To: std-proposals_at_[hidden] <std-proposals_at_[hidden]>
Cc: Thiago Macieira <thiago_at_[hidden]>
Subject: Re: [std-proposals] Adding safe saturated floating-point to integer cast to C++26
On Tuesday, 8 April 2025 07:32:10 Pacific Daylight Time Tiago Freire via Std-
Proposals wrote:
> You can write a proposal.
> Although I don't see why this couldn't easily be done as a library.
We have std::saturate_cast for integers. It should be easy to extend it to
take FP inputs.
The proposal should discuss what happens to a NaN input.
std::saturated_cast should be extended to allow for saturated floating point to integer conversions.
std::saturated_cast<int>(std::numeric_limits<float>::quiet_NaN()) should be allowed to return an arbitrary, implementation-defined value that is within the range of an int (and that otherwise has well-defined behavior if no trapping floating point exceptions are raised) to avoid unnecessary overhead on ISA's such as POWER ISA where saturated floating-point to integer conversion instructions return a non-zero result for a NaN input value. In other words, std::saturated_cast<int>(std::numeric_limits<float>::quiet_NaN()) should have the same semantics as __builtin_nondeterministic_value(0), except that std::saturated_cast<int>(std::numeric_limits<float>::quiet_NaN()) is permitted to raise floating-point exceptions.
Here is more optimal assembly language codegen for various saturated floating-point to integer casts on POWER8:
/* std::int32_t SafeSatCastF32ToI32(float val); */
SafeSatCastF32ToI32:
fctiwz 1,1
mfvsrwz 3,1
extsw 3,3
blr
/* std::uint32_t SafeSatCastF32ToU32(float val); */
SafeSatCastF32ToU32:
fctiwuz 1,1
mfvsrwz 3,1
blr
/* std::int32_t SafeSatCastF64ToI32(double val); */
SafeSatCastF64ToI32:
fctiwz 1,1
mfvsrwz 3,1
extsw 3,3
blr
/* std::uint32_t SafeSatCastF64ToU32(double val); */
SafeSatCastF64ToU32(double):
fctiwuz 1,1
mfvsrwz 3,1
blr
/* std::int64_t SafeSatCastF64ToI64(double val); */
SafeSatCastF64ToI64(double):
fctidz 0,1
mfvsrd 3,0
blr
/* std::uint64_t SafeSatCastF64ToU64(double val); */
SafeSatCastF64ToU64(double):
fctiduz 0,1
mfvsrd 3,0
blr
Here is more optimal assembly language codegen for various saturated floating-point to integer casts on AArch64:
/* std::int32_t SafeSatCastF32ToI32(float val); */
SafeSatCastF32ToI32:
fcvtzs w0, s0
ret
/* std::uint32_t SafeSatCastF32ToU32(float val); */
SafeSatCastF32ToU32:
fcvtzu w0, s0
ret
/* std::int32_t SafeSatCastF64ToI32(double val); */
SafeSatCastF64ToI32:
fcvtzs w0, d0
ret
/* std::uint32_t SafeSatCastF64ToU32(double val); */
SafeSatCastF64ToU32(double):
fcvtzu w0, d0
ret
/* std::int64_t SafeSatCastF64ToI64(double val); */
SafeSatCastF64ToI64(double):
fcvtzs x0, d0
ret
/* std::uint64_t SafeSatCastF64ToU64(double val); */
SafeSatCastF64ToU64(double):
fcvtzu x0, d0
ret
Here is more optimal assembly language codegen for various saturated floating-point to integer casts on x86 with AVX10.2:
/* std::int32_t SafeSatCastF32ToI32(float val); */
SafeSatCastF32ToI32:
vcvttss2sis eax, xmm0
ret
/* std::uint32_t SafeSatCastF32ToU32(float val); */
SafeSatCastF32ToU32:
vcvttss2usis eax, xmm0
ret
/* std::int32_t SafeSatCastF64ToI32(double val); */
SafeSatCastF64ToI32:
vcvttsd2sis eax, xmm0
ret
/* std::uint32_t SafeSatCastF64ToU32(double val); */
SafeSatCastF64ToU32(double):
vcvttsd2usis eax, xmm0
ret
/* std::int64_t SafeSatCastF64ToI64(double val); */
SafeSatCastF64ToI64(double):
vcvttsd2sis rax, xmm0
ret
/* std::uint64_t SafeSatCastF64ToU64(double val); */
SafeSatCastF64ToU64(double):
vcvttsd2usis rax, xmm0
ret
On POWER8 and AArch64, std::saturated_cast<int>(f64_val) (where f64_val is a double-precision floating point value) should generate the same instruction sequence as static_cast<int>(f64_val), except that the compiler must not assume that the argument of the std::saturated_cast<int>(f64_val) is within the range of an int and that std::saturated_cast<int>(f64_val) has well-defined behavior if no floating point exceptions are raised, even if f64_val is outside of the range of an int.
Implementing optimal std::saturated_cast for float to int conversions requires support from the compiler as a generic, portable pure C++ implementation is suboptimal on many ISA's and as static_cast<int> invokes undefined behavior for values that are outside of the range of an int.
________________________________
From: Std-Proposals <std-proposals-bounces_at_[hidden]> on behalf of Thiago Macieira via Std-Proposals <std-proposals_at_[hidden]>
Sent: Tuesday, April 8, 2025 10:17 AM
To: std-proposals_at_[hidden] <std-proposals_at_[hidden]>
Cc: Thiago Macieira <thiago_at_[hidden]>
Subject: Re: [std-proposals] Adding safe saturated floating-point to integer cast to C++26
On Tuesday, 8 April 2025 07:32:10 Pacific Daylight Time Tiago Freire via Std-
Proposals wrote:
> You can write a proposal.
> Although I don't see why this couldn't easily be done as a library.
We have std::saturate_cast for integers. It should be easy to extend it to
take FP inputs.
The proposal should discuss what happens to a NaN input.
-- Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org Principal Engineer - Intel DCAI Platform & System Engineering -- Std-Proposals mailing list Std-Proposals_at_[hidden] https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
Received on 2025-04-08 17:02:02