Skip to content

Commit edcce85

Browse files
authored
Merge pull request #4267 from pleroy/MoreTests
Change the Sin/Cos test to use multithreading
2 parents 8c3d7de + 85d3efc commit edcce85

10 files changed

Lines changed: 148 additions & 60 deletions

functions/multiprecision.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,19 @@ namespace internal {
77

88
// The designers of the Boost multiprecision library, in their confusion, have
99
// decided that nobody would use angles greater than about 1 / ε, so here I am,
10-
// using a 1000-bit rational approximation of 2π to do a first round of
10+
// using a 2000-bit rational approximation of 2π to do a first round of
1111
// reduction before calling their library. Sigh.
1212
cpp_rational const two_π(
13-
"74778118356356393099550700487949553821548169522945304260092193625666650672"
14-
"12764554292012625975645707431952036396539900828099263122542425779444638301"
15-
"713",
16-
"11901307171524915725967286244795184945313912891969281885856459971598729125"
17-
"90781909108254956448232159392109804270301891472482302074906413637232243084"
18-
"548");
13+
"85501617327051614281806889507174341018563881715359722726460240448778253362"
14+
"28033574412989010393845826881894399169689030062477829223652983133581682809"
15+
"07521562450510217951931573900693175198362946680952656259522439979797047648"
16+
"97394953111420464708282657298132250506331749517560155388580727329016311453"
17+
"71910",
18+
"13608005039951911862130979398099086168163224029757063948525789956741291980"
19+
"85326865353012588752613375780472488965315733291185793228273924312743154885"
20+
"30992776567452143477216307689797948122382208381130766696418513975898402329"
21+
"95420184160961245582170902313515516285224226723935852853290026779141303674"
22+
"08461");
1923

2024
cpp_bin_float_50 Sin(cpp_rational const& α) {
2125
auto const k = static_cast<cpp_int>(α / two_π);

functions/sin_cos_test.cpp

Lines changed: 111 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
#include <algorithm>
44
#include <limits>
55
#include <random>
6+
#include <vector>
67

8+
#include "base/bundle.hpp"
79
#include "boost/multiprecision/cpp_int.hpp"
810
#include "functions/multiprecision.hpp"
911
#include "glog/logging.h"
@@ -21,6 +23,7 @@
2123
namespace principia {
2224
namespace functions_test {
2325

26+
using namespace principia::base::_bundle;
2427
using namespace boost::multiprecision;
2528
using namespace principia::functions::_multiprecision;
2629
using namespace principia::numerics::_next;
@@ -30,32 +33,34 @@ namespace sin_cos = principia::numerics::_sin_cos;
3033

3134
class SinCosTest : public ::testing::Test {
3235
protected:
36+
struct FunctionStatistics {
37+
cpp_bin_float_50 max_ulps_error = 0;
38+
double worst_argument = 0;
39+
cpp_bin_float_50 boost_fn_worst_argument = 0;
40+
double principia_fn_worst_argument = 0;
41+
std::int64_t incorrectly_rounded = 0;
42+
};
43+
44+
struct SinCosStatistics {
45+
FunctionStatistics sin;
46+
FunctionStatistics cos;
47+
};
48+
3349
static void SetUpTestCase() {
3450
sin_cos::StaticInitialization();
3551
}
3652

37-
double a_ = 1.0;
38-
39-
static void RandomArgumentTest(double const lower_bound,
40-
double const upper_bound) {
41-
std::mt19937_64 random(42);
53+
template<std::int64_t iterations_quantum>
54+
static SinCosStatistics RandomArgumentTest(double const lower_bound,
55+
double const upper_bound,
56+
std::int64_t const seed) {
57+
std::mt19937_64 random(seed);
4258
std::uniform_real_distribution<> uniformly_at(lower_bound, upper_bound);
4359
std::uniform_int_distribution<> uniform_sign(0, 1);
4460

45-
cpp_bin_float_50 max_sin_ulps_error = 0;
46-
cpp_bin_float_50 max_cos_ulps_error = 0;
47-
double worst_sin_argument = 0;
48-
double worst_cos_argument = 0;
49-
std::int64_t incorrectly_rounded_sin = 0;
50-
std::int64_t incorrectly_rounded_cos = 0;
51-
52-
#if _DEBUG
53-
static constexpr std::int64_t iterations = 100;
54-
#else
55-
static constexpr std::int64_t iterations = 250'000;
56-
#endif
61+
SinCosStatistics s;
5762

58-
for (std::int64_t i = 0; i < iterations; ++i) {
63+
for (std::int64_t i = 0; i < iterations_quantum; ++i) {
5964
double const principia_argument =
6065
uniformly_at(random) * ((uniform_sign(random) << 1) - 1);
6166
auto const boost_argument = cpp_rational(principia_argument);
@@ -66,12 +71,14 @@ class SinCosTest : public ::testing::Test {
6671
abs(boost_sin - static_cast<cpp_bin_float_50>(principia_sin));
6772
auto const ulp = NextUp(principia_sin) - principia_sin;
6873
auto const sin_ulps_error = sin_error / ulp;
69-
if (sin_ulps_error > max_sin_ulps_error) {
70-
max_sin_ulps_error = sin_ulps_error;
71-
worst_sin_argument = principia_argument;
74+
if (sin_ulps_error > s.sin.max_ulps_error) {
75+
s.sin.max_ulps_error = sin_ulps_error;
76+
s.sin.worst_argument = principia_argument;
77+
s.sin.boost_fn_worst_argument = boost_sin;
78+
s.sin.principia_fn_worst_argument = principia_sin;
7279
}
7380
if (sin_ulps_error > 0.5) {
74-
++incorrectly_rounded_sin;
81+
++s.sin.incorrectly_rounded;
7582
LOG(ERROR) << "Sin: " << sin_ulps_error << " ulps at "
7683
<< std::setprecision(25) << principia_argument;
7784
}
@@ -83,34 +90,91 @@ class SinCosTest : public ::testing::Test {
8390
abs(boost_cos - static_cast<cpp_bin_float_50>(principia_cos));
8491
auto const ulp = NextUp(principia_cos) - principia_cos;
8592
auto const cos_ulps_error = cos_error / ulp;
86-
if (cos_ulps_error > max_cos_ulps_error) {
87-
max_cos_ulps_error = cos_ulps_error;
88-
worst_cos_argument = principia_argument;
93+
if (cos_ulps_error > s.cos.max_ulps_error) {
94+
s.cos.max_ulps_error = cos_ulps_error;
95+
s.cos.worst_argument = principia_argument;
96+
s.cos.boost_fn_worst_argument = boost_cos;
97+
s.cos.principia_fn_worst_argument = principia_cos;
8998
}
9099
if (cos_ulps_error > 0.5) {
91-
++incorrectly_rounded_cos;
100+
++s.cos.incorrectly_rounded;
92101
LOG(ERROR) << "Cos: " << cos_ulps_error << " ulps at "
93102
<< std::setprecision(25) << principia_argument;
94103
}
95104
}
96105
}
97106

98-
// This implementation is not quite correctly rounded, but not far from it.
99-
EXPECT_LE(max_sin_ulps_error, 0.5);
100-
EXPECT_LE(max_cos_ulps_error, 0.5);
101-
EXPECT_EQ(incorrectly_rounded_sin, 0);
102-
EXPECT_EQ(incorrectly_rounded_cos, 0);
103-
104-
LOG(ERROR) << "Sin error: " << max_sin_ulps_error << std::setprecision(25)
105-
<< " ulps for argument: " << worst_sin_argument
106-
<< " value: " << Sin(worst_sin_argument)
107-
<< "; incorrectly rounded: " << std::setprecision(3)
108-
<< incorrectly_rounded_sin / static_cast<double>(iterations);
109-
LOG(ERROR) << "Cos error: " << max_cos_ulps_error << std::setprecision(25)
110-
<< " ulps for argument: " << worst_cos_argument
111-
<< " value: " << Cos(worst_cos_argument)
112-
<< "; incorrectly rounded: " << std::setprecision(3)
113-
<< incorrectly_rounded_cos / static_cast<double>(iterations);
107+
EXPECT_LE(s.sin.max_ulps_error, 0.5);
108+
EXPECT_LE(s.cos.max_ulps_error, 0.5);
109+
EXPECT_EQ(s.sin.incorrectly_rounded, 0);
110+
EXPECT_EQ(s.cos.incorrectly_rounded, 0);
111+
112+
return s;
113+
}
114+
115+
void ParallelRandomArgumentTest(double const lower_bound,
116+
double const upper_bound) {
117+
#if _DEBUG
118+
static constexpr std::int64_t iterations = 30'000;
119+
static constexpr std::int64_t iterations_quantum = 100;
120+
#else
121+
static constexpr std::int64_t iterations = 10'000'000;
122+
static constexpr std::int64_t iterations_quantum = 10'000;
123+
#endif
124+
static_assert(iterations % iterations_quantum == 0);
125+
126+
Bundle bundle;
127+
128+
std::vector<SinCosStatistics> statistics(iterations / iterations_quantum);
129+
for (std::int64_t i = 0; i < statistics.size(); ++i) {
130+
bundle.Add([i, lower_bound, upper_bound, &statistics]() {
131+
statistics[i] = RandomArgumentTest<iterations_quantum>(
132+
lower_bound, upper_bound, /*seed=*/i);
133+
return absl::OkStatus();
134+
});
135+
}
136+
absl::Status const status = bundle.Join();
137+
138+
SinCosStatistics final_statistics;
139+
for (auto const& s : statistics) {
140+
if (s.sin.max_ulps_error > final_statistics.sin.max_ulps_error) {
141+
final_statistics.sin.max_ulps_error = s.sin.max_ulps_error;
142+
final_statistics.sin.worst_argument = s.sin.worst_argument;
143+
final_statistics.sin.boost_fn_worst_argument =
144+
s.sin.boost_fn_worst_argument;
145+
final_statistics.sin.principia_fn_worst_argument =
146+
s.sin.principia_fn_worst_argument;
147+
}
148+
final_statistics.sin.incorrectly_rounded += s.sin.incorrectly_rounded;
149+
if (s.cos.max_ulps_error > final_statistics.cos.max_ulps_error) {
150+
final_statistics.cos.max_ulps_error = s.cos.max_ulps_error;
151+
final_statistics.cos.worst_argument = s.cos.worst_argument;
152+
final_statistics.cos.boost_fn_worst_argument =
153+
s.cos.boost_fn_worst_argument;
154+
final_statistics.cos.principia_fn_worst_argument =
155+
s.cos.principia_fn_worst_argument;
156+
}
157+
final_statistics.cos.incorrectly_rounded += s.cos.incorrectly_rounded;
158+
}
159+
160+
auto log_statistics = [](std::string_view const fn_name,
161+
FunctionStatistics const& s) {
162+
LOG(ERROR) << fn_name << " error: " << s.max_ulps_error
163+
<< std::setprecision(25)
164+
<< " ulps for argument: " << s.worst_argument << " ("
165+
<< std::hexfloat << s.worst_argument << std::defaultfloat
166+
<< ") value: " << s.principia_fn_worst_argument << " ("
167+
<< std::hexfloat << s.principia_fn_worst_argument
168+
<< std::defaultfloat << ") vs. expected "
169+
<< s.boost_fn_worst_argument << " (" << std::hexfloat
170+
<< static_cast<double>(s.boost_fn_worst_argument)
171+
<< std::defaultfloat << "); incorrectly rounded probability: "
172+
<< std::setprecision(3)
173+
<< s.incorrectly_rounded / static_cast<double>(iterations);
174+
};
175+
176+
log_statistics("Sin", final_statistics.sin);
177+
log_statistics("Cos", final_statistics.cos);
114178
}
115179
};
116180

@@ -169,19 +233,20 @@ TEST_F(SinCosTest, ReduceIndex) {
169233
}
170234

171235
TEST_F(SinCosTest, RandomSmall) {
172-
RandomArgumentTest(0, π / 4);
236+
ParallelRandomArgumentTest(0, π / 4);
173237
}
174238

175239
TEST_F(SinCosTest, RandomTwoTerms) {
176-
RandomArgumentTest(π / 4, 1 << 8);
240+
ParallelRandomArgumentTest(π / 4, 1 << 8);
177241
}
178242

179243
TEST_F(SinCosTest, RandomThreeTerms) {
180-
RandomArgumentTest(1 << 8, 1 << 18);
244+
ParallelRandomArgumentTest(1 << 8, 1 << 18);
181245
}
182246

183247
TEST_F(SinCosTest, RandomLarge) {
184-
RandomArgumentTest(1 << 18, std::numeric_limits<double>::max() / 1.0e30);
248+
ParallelRandomArgumentTest(1 << 18,
249+
std::numeric_limits<double>::max() / 1.0e30);
185250
}
186251

187252
// Values for which the base algorithm gives an error of 1 ULP.

mathematica/accurate_tables.wl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
(*};*)
6464
(**)
6565
(*constexpr std::array<SinCosAccurateValues, " <> ToString[max + 1] <> "> SinCosAccurateTable{{\n" <>*)
66-
(*StringRiffle[Join[Table[" /*"<>StringPadLeft[ToString[i],width]<>"*/{.x = std::numeric_limits<double>::signaling_NaN(),\n"<>StringRepeat[" ",width+9]<>".sin_x = std::numeric_limits<double>::signaling_NaN(),\n"<>StringRepeat[" ",width+9]<>".cos_x = std::numeric_limits<double>::signaling_NaN()",{i,0,min-1}],Table[" /*"<>StringPadLeft[ToString[i],width]<>"*/{.x = " <> hexFloatLiteral[sincos`accurateTables[i][[1]],False] <> ",\n"<>StringRepeat[" ",width+9]<>".sin_x = " <> hexFloatLiteral[sincos`accurateTables[i][[2]],False] <> ",\n"<>StringRepeat[" ",width+9]<>".cos_x = "<> hexFloatLiteral[sincos`accurateTables[i][[3]],False], {i,indices}]],"},\n"]<>"}}};*)
66+
(*StringRiffle[Join[Table[" /*"<>StringPadLeft[ToString[i],width]<>"*/{.x = 0,\n"<>StringRepeat[" ",width+9]<>".sin_x = 0,\n"<>StringRepeat[" ",width+9]<>".cos_x = 1",{i,0,min-1}],Table[" /*"<>StringPadLeft[ToString[i],width]<>"*/{.x = " <> hexFloatLiteral[sincos`accurateTables[i][[1]],False] <> ",\n"<>StringRepeat[" ",width+9]<>".sin_x = " <> hexFloatLiteral[sincos`accurateTables[i][[2]],False] <> ",\n"<>StringRepeat[" ",width+9]<>".cos_x = "<> hexFloatLiteral[sincos`accurateTables[i][[3]],False], {i,indices}]],"},\n"]<>"}}};*)
6767
(**)
6868
(*} // namespace internal*)
6969
(**)

mathematica/mathematica.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<ClInclude Include="mathematica_body.hpp" />
2929
</ItemGroup>
3030
<ItemGroup>
31+
<None Include="accurate_tables.wl" />
3132
<None Include="associated_legendre_function.wl" />
3233
<None Include="elliptic_integrals.wl" />
3334
<None Include="ieee754_floating_point.wl" />

mathematica/mathematica.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,8 @@
102102
<None Include="ieee754_floating_point_test.wl">
103103
<Filter>Scripts</Filter>
104104
</None>
105+
<None Include="accurate_tables.wl">
106+
<Filter>Scripts</Filter>
107+
</None>
105108
</ItemGroup>
106109
</Project>

numerics/accurate_tables.mathematica.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ struct SinCosAccurateValues {
1616
};
1717

1818
constexpr std::array<SinCosAccurateValues, 403> SinCosAccurateTable{{
19-
/* 0*/{.x = std::numeric_limits<double>::signaling_NaN(),
20-
.sin_x = std::numeric_limits<double>::signaling_NaN(),
21-
.cos_x = std::numeric_limits<double>::signaling_NaN()},
19+
/* 0*/{.x = 0,
20+
.sin_x = 0,
21+
.cos_x = 1},
2222
/* 1*/{.x = 0x0.007f'ffce'7f29'afe0'0000'0000p0,
2323
.sin_x = 0x0.007f'ffc9'29da'9bb4'0000'db97p0,
2424
.cos_x = 0x0.ffff'e000'196b'1000'0148'882dp0},

numerics/double_precision_body.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,11 @@ template<typename T, typename U>
102102
requires convertible_to_quantity<T> && convertible_to_quantity<U>
103103
struct ComponentwiseComparator<T, U> {
104104
static bool GreaterThanOrEqualOrZero(T const& left, U const& right) {
105-
return Abs(left) >= Abs(right) || left == T{} || !IsFinite(left);
105+
// In the elementary functions, we use NaN to fall back to the CORE-MATH
106+
// implementation. We don't want to die because of the weird comparisons of
107+
// NaNs.
108+
return Abs(left) >= Abs(right) || left == T{} ||
109+
!IsFinite(left) || !IsFinite(right);
106110
}
107111
};
108112

numerics/numerics.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
</ClCompile>
2727
</ItemDefinitionGroup>
2828
<ItemGroup>
29+
<ClInclude Include="accurate_tables.mathematica.h" />
2930
<ClInclude Include="angle_reduction.hpp" />
3031
<ClInclude Include="angle_reduction_body.hpp" />
3132
<ClInclude Include="approximation.hpp" />

numerics/numerics.vcxproj.filters

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,12 @@
272272
<ClInclude Include="sin_cos.hpp">
273273
<Filter>Header Files</Filter>
274274
</ClInclude>
275+
<ClInclude Include="osaca.hpp">
276+
<Filter>Header Files</Filter>
277+
</ClInclude>
278+
<ClInclude Include="accurate_tables.mathematica.h">
279+
<Filter>Header Files</Filter>
280+
</ClInclude>
275281
</ItemGroup>
276282
<ItemGroup>
277283
<ClCompile Include="fixed_arrays_test.cpp">

numerics/sin_cos.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,15 +159,19 @@ double FusedNegatedMultiplyAdd(double const a, double const b, double const c) {
159159
// negative.
160160
template<FMAPolicy fma_policy, double e>
161161
double DetectDangerousRounding(double const x, double const Δx) {
162+
// We don't check that `Δx` is not NaN because that's how we trigger fallback
163+
// to the slow path.
164+
DCHECK(x == x);
162165
DoublePrecision<double> const sum = QuickTwoSum(x, Δx);
163166
double const& value = sum.value;
164167
double const& error = sum.error;
165168
OSACA_IF(value == FusedMultiplyAdd<fma_policy>(error, e, value)) {
166169
return value;
167170
} else {
168171
#if _DEBUG
169-
LOG(ERROR) << std::setprecision(25) << x << " " << std::hexfloat << value
170-
<< " " << error << " " << e;
172+
LOG_IF(ERROR, value == value && error == error)
173+
<< std::setprecision(25) << x << " " << std::hexfloat << value << " "
174+
<< error << " " << e;
171175
#endif
172176
return std::numeric_limits<double>::quiet_NaN();
173177
}

0 commit comments

Comments
 (0)