Skip to content

Commit 57c30ca

Browse files
<cmath>: Restore std::pow(x, 2) accuracy (#5771)
1 parent 616c862 commit 57c30ca

File tree

6 files changed

+145
-4
lines changed

6 files changed

+145
-4
lines changed

stl/inc/cmath

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,17 @@ double frexp(_Ty _Value, _Out_ int* const _Exp) noexcept /* strengthened */ {
567567
return _CSTD frexp(static_cast<double>(_Value), _Exp);
568568
}
569569

570+
template <class _Ty1, class _Ty2, _STD enable_if_t<_STD is_arithmetic_v<_Ty1> && _STD is_arithmetic_v<_Ty2>, int> = 0>
571+
_NODISCARD _STD _Common_float_type_t<_Ty1, _Ty2> pow(_Ty1 _Left, _Ty2 _Right) noexcept /* strengthened */ {
572+
if constexpr (_STD _Is_nonbool_integral<_Ty2>) {
573+
if (_Right == 2) {
574+
return static_cast<double>(_Left) * static_cast<double>(_Left); // TRANSITION, see GH-5768
575+
}
576+
}
577+
578+
return _CSTD pow(static_cast<double>(_Left), static_cast<double>(_Right));
579+
}
580+
570581
template <class _Ty1, class _Ty2, class _Ty3,
571582
_STD enable_if_t<_STD is_arithmetic_v<_Ty1> && _STD is_arithmetic_v<_Ty2> && _STD is_arithmetic_v<_Ty3>, int> = 0>
572583
_NODISCARD _STD _Common_float_type_t<_Ty1, _STD _Common_float_type_t<_Ty2, _Ty3>> fma(
@@ -861,7 +872,7 @@ _GENERIC_MATH1(cbrt)
861872
_GENERIC_MATH1(fabs)
862873
_GENERIC_MATH2(hypot)
863874
// 3-arg hypot() is hand-crafted
864-
_GENERIC_MATH2(pow)
875+
// pow() is hand-crafted
865876
_GENERIC_MATH1(sqrt)
866877
_GENERIC_MATH1(erf)
867878
_GENERIC_MATH1(erfc)

stl/inc/type_traits

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,9 +1116,6 @@ struct _NO_SPECIALIZATIONS_OF_TYPE_TRAITS is_unsigned : bool_constant<_Sign_base
11161116
_EXPORT_STD template <class _Ty>
11171117
_NO_SPECIALIZATIONS_OF_TYPE_TRAITS constexpr bool is_unsigned_v = _Sign_base<_Ty>::_Unsigned;
11181118

1119-
template <class _Ty>
1120-
constexpr bool _Is_nonbool_integral = is_integral_v<_Ty> && !is_same_v<remove_cv_t<_Ty>, bool>;
1121-
11221119
template <bool>
11231120
struct _Select { // Select between aliases that extract either their first or second parameter
11241121
template <class _Ty1, class>

stl/inc/xtr1common

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ _NO_SPECIALIZATIONS_OF_TYPE_TRAITS constexpr bool is_integral_v = _Is_any_of_v<r
197197
_EXPORT_STD template <class _Ty>
198198
struct _NO_SPECIALIZATIONS_OF_TYPE_TRAITS is_integral : bool_constant<is_integral_v<_Ty>> {};
199199

200+
template <class _Ty>
201+
constexpr bool _Is_nonbool_integral = is_integral_v<_Ty> && !is_same_v<remove_cv_t<_Ty>, bool>;
202+
200203
_EXPORT_STD template <class _Ty>
201204
_NO_SPECIALIZATIONS_OF_TYPE_TRAITS constexpr bool is_floating_point_v =
202205
_Is_any_of_v<remove_cv_t<_Ty>, float, double, long double>;

tests/std/test.lst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ tests\GH_005421_vector_algorithms_integer_class_type_iterator
270270
tests\GH_005472_do_not_overlap
271271
tests\GH_005546_containers_size_type_cast
272272
tests\GH_005553_regex_character_translation
273+
tests\GH_005768_pow_accuracy
273274
tests\LWG2381_num_get_floating_point
274275
tests\LWG2510_tag_classes
275276
tests\LWG2597_complex_branch_cut
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
RUNALL_INCLUDE ..\usual_matrix.lst
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
#include <algorithm>
5+
#include <cassert>
6+
#include <cmath>
7+
#include <cstddef>
8+
#include <cstdint>
9+
#include <cstdio>
10+
#include <functional>
11+
#include <random>
12+
#include <vector>
13+
14+
// INTENTIONALLY AVOIDED: using namespace std;
15+
16+
void initialize_randomness(std::mt19937_64& gen) {
17+
constexpr std::size_t n = std::mt19937_64::state_size;
18+
constexpr std::size_t w = std::mt19937_64::word_size;
19+
static_assert(w % 32 == 0, "w should be evenly divisible by 32");
20+
constexpr std::size_t k = w / 32;
21+
22+
std::vector<std::uint32_t> vec(n * k);
23+
24+
std::random_device rd;
25+
std::generate(vec.begin(), vec.end(), std::ref(rd));
26+
27+
std::printf("This is a randomized test.\n");
28+
std::printf("DO NOT IGNORE/RERUN ANY FAILURES.\n");
29+
std::printf("You must report them to the STL maintainers.\n\n");
30+
31+
std::seed_seq seq(vec.cbegin(), vec.cend());
32+
gen.seed(seq);
33+
}
34+
35+
void check_equal(const float val, const float actual, const float squared) {
36+
if (actual != squared) {
37+
std::printf("val: %.6a; actual: %.6a; squared: %.6a\n", val, actual, squared);
38+
}
39+
assert(actual == squared);
40+
}
41+
42+
void check_equal(const double val, const double actual, const double squared) {
43+
if (actual != squared) {
44+
std::printf("val: %a; actual: %a; squared: %a\n", val, actual, squared);
45+
}
46+
assert(actual == squared);
47+
}
48+
49+
void check_equal(const long double val, const long double actual, const long double squared) {
50+
if (actual != squared) {
51+
std::printf("val: %La; actual: %La; squared: %La\n", val, actual, squared);
52+
}
53+
assert(actual == squared);
54+
}
55+
56+
void test_square_flt(const float val, const float squared) {
57+
// float * float is float, N5014 [expr.arith.conv]/1.4.1.
58+
check_equal(val, val * val, squared);
59+
60+
// For std::pow(float, int), the arguments are effectively cast to double, N5014 [cmath.syn]/3.
61+
const double as_dbl = static_cast<double>(val);
62+
check_equal(as_dbl, std::pow(val, 2), as_dbl * as_dbl);
63+
}
64+
65+
void test_square_dbl(const double val, const double squared) {
66+
check_equal(val, val * val, squared);
67+
check_equal(val, std::pow(val, 2), squared);
68+
}
69+
70+
void test_square_ldbl(const long double val, const long double squared) {
71+
check_equal(val, val * val, squared);
72+
check_equal(val, std::pow(val, 2), squared);
73+
}
74+
75+
// GH-5768 <cmath>: Calling UCRT ::pow(x, 2) is less accurate than x * x
76+
void test_gh_5768_manually_verified() {
77+
// Manually verified to be correctly rounded:
78+
test_square_flt(0x1.bffff4p-1f, 0x1.87ffecp-1f);
79+
test_square_flt(0x1.bc0040p-2f, 0x1.810870p-3f);
80+
test_square_flt(0x1.2b7bc2p-1f, 0x1.5e5a52p-2f);
81+
82+
test_square_dbl(0x1.ec9a50154a6f9p-1, 0x1.d9f0c06b2463ep-1);
83+
test_square_dbl(0x1.12814d2dd432cp-1, 0x1.26590a84f9b12p-2);
84+
test_square_dbl(0x1.33994b0b751ccp-3, 0x1.719905c84494ap-6);
85+
86+
test_square_ldbl(0x1.ec9a50154a6f9p-1L, 0x1.d9f0c06b2463ep-1L);
87+
test_square_ldbl(0x1.12814d2dd432cp-1L, 0x1.26590a84f9b12p-2L);
88+
test_square_ldbl(0x1.33994b0b751ccp-3L, 0x1.719905c84494ap-6L);
89+
}
90+
91+
void test_gh_5768_randomized(std::mt19937_64& gen) {
92+
const int Trials = 1'000'000;
93+
94+
{
95+
std::uniform_real_distribution<float> dist{-10.0f, 10.0f};
96+
for (int i = 0; i < Trials; ++i) {
97+
const auto val = dist(gen);
98+
test_square_flt(val, val * val);
99+
}
100+
}
101+
102+
{
103+
std::uniform_real_distribution<double> dist{-10.0, 10.0};
104+
for (int i = 0; i < Trials; ++i) {
105+
const auto val = dist(gen);
106+
test_square_dbl(val, val * val);
107+
}
108+
}
109+
110+
{
111+
std::uniform_real_distribution<long double> dist{-10.0L, 10.0L};
112+
for (int i = 0; i < Trials; ++i) {
113+
const auto val = dist(gen);
114+
test_square_ldbl(val, val * val);
115+
}
116+
}
117+
}
118+
119+
int main() {
120+
test_gh_5768_manually_verified();
121+
122+
std::mt19937_64 gen;
123+
initialize_randomness(gen);
124+
test_gh_5768_randomized(gen);
125+
}

0 commit comments

Comments
 (0)