Skip to content

Commit

Permalink
Fix complex's Sufficient Additional Overloads (#791)
Browse files Browse the repository at this point in the history
* Fixes GH-785, which reported that `arg(real)` was implemented as `return 0;`
but `arg(complex)` does actual work for complex numbers with
zero imaginary parts.

* `norm` should upgrade to `double` before squaring. The difference
is observable because `double`'s range is vast.

* `conj` and `proj` should return `complex`.

* `conj(real)` should behave exactly like `conj(complex)`, returning
an imaginary part of negative zero. The difference is technically
observable.

* `proj(real)` should behave exactly like `proj(complex)`, mapping
negative infinity to positive infinity.

* Test that float/double/long double aren't "upgraded".

* Tests pass for Clang, emit warnings for MSVC.
  • Loading branch information
StephanTLavavej authored May 5, 2020
1 parent 430b569 commit 65d98ff
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 12 deletions.
37 changes: 29 additions & 8 deletions stl/inc/complex
Original file line number Diff line number Diff line change
Expand Up @@ -1758,8 +1758,11 @@ template <class _Ty>
using _Upgrade_to_double = conditional_t<is_integral_v<_Ty>, double, _Ty>;

template <class _Ty, enable_if_t<is_arithmetic_v<_Ty>, int> = 0>
_NODISCARD _Upgrade_to_double<_Ty> arg(_Ty) {
return 0;
_NODISCARD _Upgrade_to_double<_Ty> arg(_Ty _Left) {
using _Upgraded = _Upgrade_to_double<_Ty>;
const auto _Val = static_cast<_Upgraded>(_Left);

return _Ctraits<_Upgraded>::atan2(0, _Val);
}

template <class _Ty, enable_if_t<is_arithmetic_v<_Ty>, int> = 0>
Expand All @@ -1769,22 +1772,40 @@ _NODISCARD _CONSTEXPR20 _Upgrade_to_double<_Ty> imag(_Ty) {

template <class _Ty, enable_if_t<is_arithmetic_v<_Ty>, int> = 0>
_NODISCARD _CONSTEXPR20 _Upgrade_to_double<_Ty> real(_Ty _Left) {
return static_cast<_Upgrade_to_double<_Ty>>(_Left);
using _Upgraded = _Upgrade_to_double<_Ty>;
const auto _Val = static_cast<_Upgraded>(_Left);

return _Val;
}

template <class _Ty, enable_if_t<is_arithmetic_v<_Ty>, int> = 0>
_NODISCARD _CONSTEXPR20 _Upgrade_to_double<_Ty> norm(_Ty _Left) {
return static_cast<_Upgrade_to_double<_Ty>>(_Left * _Left);
using _Upgraded = _Upgrade_to_double<_Ty>;
const auto _Val = static_cast<_Upgraded>(_Left);

return _Val * _Val;
}

template <class _Ty, enable_if_t<is_arithmetic_v<_Ty>, int> = 0>
_NODISCARD _CONSTEXPR20 _Upgrade_to_double<_Ty> conj(_Ty _Left) {
return static_cast<_Upgrade_to_double<_Ty>>(_Left);
_NODISCARD _CONSTEXPR20 complex<_Upgrade_to_double<_Ty>> conj(_Ty _Left) {
using _Upgraded = _Upgrade_to_double<_Ty>;
const auto _Val = static_cast<_Upgraded>(_Left);

return complex<_Upgraded>(_Val, -_Upgraded{0});
}

template <class _Ty, enable_if_t<is_arithmetic_v<_Ty>, int> = 0>
_NODISCARD _Upgrade_to_double<_Ty> proj(_Ty _Left) {
return static_cast<_Upgrade_to_double<_Ty>>(_Left);
_NODISCARD complex<_Upgrade_to_double<_Ty>> proj(_Ty _Left) {
using _Upgraded = _Upgrade_to_double<_Ty>;
const auto _Val = static_cast<_Upgraded>(_Left);

if (_Ctraits<_Upgraded>::_Isinf(_Val)) {
// C11 7.3.9.5/2: "z projects to z except that all complex infinities [...]
// project to positive infinity on the real axis."
return complex<_Upgraded>(_Ctraits<_Upgraded>::_Infv(), 0);
}

return complex<_Upgraded>(_Val, 0);
}

// FUNCTION TEMPLATE pow
Expand Down
6 changes: 4 additions & 2 deletions tests/libcxx/expected_results.txt
Original file line number Diff line number Diff line change
Expand Up @@ -591,9 +591,7 @@ std/numerics/complex.number/complex.special/float_long_double_implicit.compile.f
std/re/re.traits/transform.pass.cpp FAIL

# STL bug: Incorrect return types.
std/numerics/complex.number/cmplx.over/conj.pass.cpp FAIL
std/numerics/complex.number/cmplx.over/pow.pass.cpp FAIL
std/numerics/complex.number/cmplx.over/proj.pass.cpp FAIL

# STL bug: Missing <valarray> assignment operators.
std/numerics/numarray/template.mask.array/mask.array.assign/mask_array.pass.cpp FAIL
Expand Down Expand Up @@ -812,6 +810,10 @@ std/iterators/predef.iterators/insert.iterators/back.insert.iterator/types.pass.
std/iterators/predef.iterators/insert.iterators/front.insert.iterator/types.pass.cpp FAIL
std/iterators/predef.iterators/insert.iterators/insert.iterator/types.pass.cpp FAIL

# Tests emit warning C4244: 'argument': conversion from 'T' to 'const std::complex<double>::_Ty', possible loss of data
std/numerics/complex.number/cmplx.over/conj.pass.cpp:0 FAIL
std/numerics/complex.number/cmplx.over/proj.pass.cpp:0 FAIL


# *** LIKELY STL BUGS ***
# Not yet analyzed, likely STL bugs. Assertions and other runtime failures.
Expand Down
6 changes: 4 additions & 2 deletions tests/libcxx/skipped_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -591,9 +591,7 @@ numerics\complex.number\complex.special\float_long_double_implicit.compile.fail.
re\re.traits\transform.pass.cpp

# STL bug: Incorrect return types.
numerics\complex.number\cmplx.over\conj.pass.cpp
numerics\complex.number\cmplx.over\pow.pass.cpp
numerics\complex.number\cmplx.over\proj.pass.cpp

# STL bug: Missing <valarray> assignment operators.
numerics\numarray\template.mask.array\mask.array.assign\mask_array.pass.cpp
Expand Down Expand Up @@ -812,6 +810,10 @@ iterators\predef.iterators\insert.iterators\back.insert.iterator\types.pass.cpp
iterators\predef.iterators\insert.iterators\front.insert.iterator\types.pass.cpp
iterators\predef.iterators\insert.iterators\insert.iterator\types.pass.cpp

# Tests emit warning C4244: 'argument': conversion from 'T' to 'const std::complex<double>::_Ty', possible loss of data
numerics\complex.number\cmplx.over\conj.pass.cpp
numerics\complex.number\cmplx.over\proj.pass.cpp


# *** LIKELY STL BUGS ***
# Not yet analyzed, likely STL bugs. Assertions and other runtime failures.
Expand Down
58 changes: 58 additions & 0 deletions tests/std/tests/Dev10_555491_complex_linker_errors/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <assert.h>
#include <cmath>
#include <complex>
#include <limits>
#include <stdint.h>
#include <string.h>
#include <type_traits>

using namespace std;

#define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__)

template <class T, class U>
T pseudo_bit_cast(const U& source) {
static_assert(sizeof(T) == sizeof(U), "");
Expand Down Expand Up @@ -90,4 +95,57 @@ int main() {
assert(nearly_equal_partwise(asinh(-1e+307 + 2e+307i), -708.3914896859491 + 1.1071487177940904i));
assert(nearly_equal_partwise(asinh(-2e+37f + 4e+37if), -87.386663f + 1.1071488if));
assert(nearly_equal_partwise(asinh(-1e+307L + 2e+307il), -708.3914896859491L + 1.1071487177940904il));

// Also test GH-785 "<complex>: std::arg does not work for negative real values"

STATIC_ASSERT(is_same_v<decltype(arg(1729)), double>);
STATIC_ASSERT(is_same_v<decltype(imag(1729)), double>);
STATIC_ASSERT(is_same_v<decltype(real(1729)), double>);
STATIC_ASSERT(is_same_v<decltype(norm(1729)), double>);
STATIC_ASSERT(is_same_v<decltype(conj(1729)), complex<double>>);
STATIC_ASSERT(is_same_v<decltype(proj(1729)), complex<double>>);

STATIC_ASSERT(is_same_v<decltype(arg(1729.0f)), float>);
STATIC_ASSERT(is_same_v<decltype(imag(1729.0f)), float>);
STATIC_ASSERT(is_same_v<decltype(real(1729.0f)), float>);
STATIC_ASSERT(is_same_v<decltype(norm(1729.0f)), float>);
STATIC_ASSERT(is_same_v<decltype(conj(1729.0f)), complex<float>>);
STATIC_ASSERT(is_same_v<decltype(proj(1729.0f)), complex<float>>);

STATIC_ASSERT(is_same_v<decltype(arg(1729.0)), double>);
STATIC_ASSERT(is_same_v<decltype(imag(1729.0)), double>);
STATIC_ASSERT(is_same_v<decltype(real(1729.0)), double>);
STATIC_ASSERT(is_same_v<decltype(norm(1729.0)), double>);
STATIC_ASSERT(is_same_v<decltype(conj(1729.0)), complex<double>>);
STATIC_ASSERT(is_same_v<decltype(proj(1729.0)), complex<double>>);

STATIC_ASSERT(is_same_v<decltype(arg(1729.0L)), long double>);
STATIC_ASSERT(is_same_v<decltype(imag(1729.0L)), long double>);
STATIC_ASSERT(is_same_v<decltype(real(1729.0L)), long double>);
STATIC_ASSERT(is_same_v<decltype(norm(1729.0L)), long double>);
STATIC_ASSERT(is_same_v<decltype(conj(1729.0L)), complex<long double>>);
STATIC_ASSERT(is_same_v<decltype(proj(1729.0L)), complex<long double>>);

assert((arg(-1.0) == arg(complex<double>{-1.0, 0.0})));
assert((arg(-1) == arg(complex<double>{-1.0, 0.0})));

assert(imag(1729.0) == 0.0);
assert(imag(1729) == 0.0);

assert(real(1729.0) == 1729.0);
assert(real(1729) == 1729.0);

assert(norm(0x1p63) == 0x1p126);
assert(norm(0x8000'0000'0000'0000ULL) == 0x1p126);

assert((conj(1729.0) == complex<double>{1729.0, -0.0}));
assert((conj(1729) == complex<double>{1729.0, -0.0}));
assert(signbit(conj(1729.0).imag()));
assert(signbit(conj(1729).imag()));

assert((proj(1729.0) == complex<double>{1729.0, 0.0}));
assert((proj(1729) == complex<double>{1729.0, 0.0}));
constexpr double inf = numeric_limits<double>::infinity();
assert((proj(inf) == complex<double>{inf, 0.0}));
assert((proj(-inf) == complex<double>{inf, 0.0}));
}

0 comments on commit 65d98ff

Please sign in to comment.