Skip to content

Commit 56c204f

Browse files
committed
Add methods on f32 and f64. Test total_cmp
1 parent 14c92bf commit 56c204f

File tree

9 files changed

+1064
-27
lines changed

9 files changed

+1064
-27
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ add_library(subspace STATIC
6262
"mem/take.h"
6363
"num/__private/float_consts.h"
6464
"num/__private/float_macros.h"
65+
"num/__private/float_ordering.h"
6566
"num/__private/intrinsics.h"
6667
"num/__private/literals.h"
6768
"num/__private/ptr_type.h"

assertions/check.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
#include "assertions/panic.h"
2020
#include "macros/always_inline.h"
2121

22-
namespace sus {
22+
namespace sus::assertions {
2323

2424
constexpr sus_always_inline void check(bool cond) {
2525
if (!cond) [[unlikely]]
@@ -33,4 +33,10 @@ constexpr sus_always_inline void check_with_message(
3333
::sus::panic_with_message(msg);
3434
}
3535

36-
} // namespace sus
36+
} // namespace sus::assertions
37+
38+
// Promote check() and check_with_message() into the `sus` namespace.
39+
namespace sus {
40+
using ::sus::assertions::check;
41+
using ::sus::assertions::check_with_message;
42+
}

num/__private/float_macros.h

Lines changed: 405 additions & 18 deletions
Large diffs are not rendered by default.

num/__private/float_ordering.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#include <compare>
18+
19+
#include "num/__private/intrinsics.h"
20+
21+
namespace sus::num::__private {
22+
23+
template <class T>
24+
requires(std::is_floating_point_v<T> && sizeof(T) <= 8)
25+
constexpr std::strong_ordering float_strong_ordering(T l, T r) noexcept {
26+
if (into_unsigned_integer(l) == into_unsigned_integer(r))
27+
return std::strong_ordering::equal;
28+
29+
const bool l_neg = (into_unsigned_integer(l) & high_bit<T>()) != 0;
30+
const bool r_neg = (into_unsigned_integer(r) & high_bit<T>()) != 0;
31+
if (l_neg != r_neg) {
32+
if (l_neg)
33+
return std::strong_ordering::less;
34+
else
35+
return std::strong_ordering::greater;
36+
}
37+
38+
const bool l_nan = float_is_nan(l);
39+
const bool r_nan = float_is_nan(r);
40+
if (l_nan != r_nan) {
41+
if (l_neg == l_nan)
42+
return std::strong_ordering::less;
43+
else
44+
return std::strong_ordering::greater;
45+
}
46+
if (l_nan && r_nan) {
47+
const bool l_quiet = float_is_nan_quiet(l);
48+
const bool r_quiet = float_is_nan_quiet(r);
49+
if (l_quiet != r_quiet) {
50+
if (l_neg == float_is_nan_quiet(l))
51+
return std::strong_ordering::less;
52+
else
53+
return std::strong_ordering::greater;
54+
} else {
55+
if (l_neg) {
56+
return std::strong_order(into_unsigned_integer(r),
57+
into_unsigned_integer(l));
58+
} else {
59+
return std::strong_order(into_unsigned_integer(l),
60+
into_unsigned_integer(r));
61+
}
62+
}
63+
}
64+
65+
if (l < r)
66+
return std::strong_ordering::less;
67+
else
68+
return std::strong_ordering::greater;
69+
}
70+
71+
} // namespace sus::num::__private

num/__private/intrinsics.h

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,15 +1006,37 @@ constexpr auto into_unsigned_integer(T x) noexcept {
10061006
return std::bit_cast<uint64_t>(x);
10071007
}
10081008

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.
10091014
template <class T>
10101015
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 {
10121018
if constexpr (sizeof(T) == sizeof(float))
10131019
return std::bit_cast<float>(x);
10141020
else
10151021
return std::bit_cast<double>(x);
10161022
}
10171023

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+
10181040
template <class T>
10191041
requires(std::is_floating_point_v<T>)
10201042
sus_always_inline constexpr T min_positive_value() noexcept {
@@ -1087,10 +1109,13 @@ sus_always_inline constexpr uint32_t num_digits() noexcept {
10871109
template <class T>
10881110
requires(std::is_floating_point_v<T>)
10891111
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.
10901115
if constexpr (sizeof(T) == sizeof(float))
1091-
return into_float(uint32_t{0x7fffffff});
1116+
return into_float_constexpr(unsafe_fn, uint32_t{0x7fc00000});
10921117
else
1093-
return into_float(uint64_t{0x7ff0000000000001});
1118+
return into_float_constexpr(unsafe_fn, uint64_t{0x7ff8000000000000});
10941119
}
10951120

10961121
template <class T>
@@ -1149,6 +1174,32 @@ constexpr bool float_is_inf_or_nan(double x) noexcept {
11491174
return (into_unsigned_integer(x) & mask) == mask;
11501175
}
11511176

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+
11521203
template <class T>
11531204
requires(std::is_floating_point_v<T> && sizeof(T) <= 8)
11541205
constexpr T truncate_float(T x) noexcept {
@@ -1174,7 +1225,19 @@ constexpr T truncate_float(T x) noexcept {
11741225
const uint32_t trim_bits = mantissa_width - static_cast<uint32_t>(exp);
11751226
const auto shr = unchecked_shr(into_unsigned_integer(x), trim_bits);
11761227
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));
11781241
}
11791242

11801243
} // namespace sus::num::__private

0 commit comments

Comments
 (0)