@@ -1006,15 +1006,37 @@ constexpr auto into_unsigned_integer(T x) noexcept {
1006
1006
return std::bit_cast<uint64_t >(x);
1007
1007
}
1008
1008
1009
+ // Prefer the non-constexpr `into_float()` to avoid problems where you get a
1010
+ // different value in a constexpr context from a runtime context. It's safe to
1011
+ // call this function if the argument is not a NaN. Otherwise, you must ensure
1012
+ // the NaN is exactly in the form that would be produced in a constexpr context
1013
+ // in order to avoid problems.
1009
1014
template <class T >
1010
1015
requires (std::is_integral_v<T> && sizeof (T) <= 8 )
1011
- constexpr auto into_float (T x) noexcept {
1016
+ constexpr auto into_float_constexpr (::sus::marker::UnsafeFnMarker,
1017
+ T x) noexcept {
1012
1018
if constexpr (sizeof (T) == sizeof (float ))
1013
1019
return std::bit_cast<float >(x);
1014
1020
else
1015
1021
return std::bit_cast<double >(x);
1016
1022
}
1017
1023
1024
+ // This is NOT constexpr because it can produce different results in a constexpr
1025
+ // context than in a runtime one. For example.
1026
+ //
1027
+ // ```
1028
+ // constexpr float x = into_float(uint32_t{0x7f800001});
1029
+ // const float y = into_float(uint32_t{0x7f800001});
1030
+ // ```
1031
+ // In this case `x` is `7fc00001` (the quiet bit became set), but `y` is
1032
+ // `0x7f800001`.
1033
+ template <class T >
1034
+ requires (std::is_integral_v<T> && sizeof (T) <= 8 )
1035
+ inline auto into_float (T x) noexcept {
1036
+ // SAFETY: Since this isn't a constexpr context, we're okay.
1037
+ return into_float_constexpr (unsafe_fn, x);
1038
+ }
1039
+
1018
1040
template <class T >
1019
1041
requires (std::is_floating_point_v<T>)
1020
1042
sus_always_inline constexpr T min_positive_value () noexcept {
@@ -1087,10 +1109,13 @@ sus_always_inline constexpr uint32_t num_digits() noexcept {
1087
1109
template <class T >
1088
1110
requires (std::is_floating_point_v<T>)
1089
1111
sus_always_inline constexpr T nan () noexcept {
1112
+ // SAFETY: We must take care that the value returned here is the same in both
1113
+ // a constexpr and non-constexpr context. The quiet bit is always set in a
1114
+ // constexpr context, so we return a quiet bit here.
1090
1115
if constexpr (sizeof (T) == sizeof (float ))
1091
- return into_float ( uint32_t {0x7fffffff });
1116
+ return into_float_constexpr (unsafe_fn, uint32_t {0x7fc00000 });
1092
1117
else
1093
- return into_float ( uint64_t {0x7ff0000000000001 });
1118
+ return into_float_constexpr (unsafe_fn, uint64_t {0x7ff8000000000000 });
1094
1119
}
1095
1120
1096
1121
template <class T >
@@ -1149,6 +1174,32 @@ constexpr bool float_is_inf_or_nan(double x) noexcept {
1149
1174
return (into_unsigned_integer (x) & mask) == mask;
1150
1175
}
1151
1176
1177
+ constexpr bool float_is_nan (float x) noexcept {
1178
+ constexpr auto inf_mask = uint32_t {0x7f800000 };
1179
+ constexpr auto nan_mask = uint32_t {0x7fffffff };
1180
+ return (into_unsigned_integer (x) & nan_mask) > inf_mask;
1181
+ }
1182
+
1183
+ constexpr bool float_is_nan (double x) noexcept {
1184
+ constexpr auto inf_mask = uint64_t {0x7ff0000000000000 };
1185
+ constexpr auto nan_mask = uint64_t {0x7fffffffffffffff };
1186
+ return (into_unsigned_integer (x) & nan_mask) > inf_mask;
1187
+ }
1188
+
1189
+ // Assumes that x is a NaN.
1190
+ constexpr bool float_is_nan_quiet (float x) noexcept {
1191
+ // The quiet bit is the highest bit in the mantissa.
1192
+ constexpr auto quiet_mask = uint32_t {uint32_t {1 } << (23 - 1 )};
1193
+ return (into_unsigned_integer (x) & quiet_mask) != 0 ;
1194
+ }
1195
+
1196
+ // Assumes that x is a NaN.
1197
+ constexpr bool float_is_nan_quiet (double x) noexcept {
1198
+ // The quiet bit is the highest bit in the mantissa.
1199
+ constexpr auto quiet_mask = uint64_t {uint64_t {1 } << (52 - 1 )};
1200
+ return (into_unsigned_integer (x) & quiet_mask) != 0 ;
1201
+ }
1202
+
1152
1203
template <class T >
1153
1204
requires (std::is_floating_point_v<T> && sizeof (T) <= 8 )
1154
1205
constexpr T truncate_float (T x) noexcept {
@@ -1174,7 +1225,19 @@ constexpr T truncate_float(T x) noexcept {
1174
1225
const uint32_t trim_bits = mantissa_width - static_cast <uint32_t >(exp);
1175
1226
const auto shr = unchecked_shr (into_unsigned_integer (x), trim_bits);
1176
1227
const auto shl = unchecked_shl (shr, trim_bits);
1177
- return into_float (shl);
1228
+ // SAFETY: The value here is not a NaN, so will give the same value in
1229
+ // constexpr and non-constexpr contexts.
1230
+ return into_float_constexpr (unsafe_fn, shl);
1231
+ }
1232
+
1233
+ template <class T >
1234
+ requires (std::is_floating_point_v<T> && sizeof (T) <= 8 )
1235
+ constexpr T float_signum (T x) noexcept {
1236
+ // TODO: Can this be done without a branch?
1237
+ if (float_is_nan (x)) [[unlikely]]
1238
+ return x;
1239
+ const auto signbit = unchecked_and (into_unsigned_integer (x), high_bit<T>());
1240
+ return into_float (unchecked_add (into_unsigned_integer (T{1 }), signbit));
1178
1241
}
1179
1242
1180
1243
} // namespace sus::num::__private
0 commit comments