Skip to content
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5073b38
Add better cmake support
mborland May 6, 2024
1f98503
Add test set
mborland May 6, 2024
8e82bbb
Add class basis
mborland May 6, 2024
2a688a5
Restrict to boost.mp types
mborland May 6, 2024
564ce77
Add unary operators
mborland May 6, 2024
2df271f
Specialize class
mborland May 6, 2024
f61a9e1
Have std::complex inherit from multiprecision number class
mborland May 7, 2024
b714bb2
Add equality
mborland May 7, 2024
dbbc941
Add the ostream operator
mborland May 7, 2024
0b5bd14
Add the istream operator
mborland May 7, 2024
f50f134
Add overloads for non-member real and imag
mborland May 7, 2024
612f4fe
Replace polar impl with the one from default ops
mborland May 7, 2024
21d9b25
Add abs overload
mborland May 7, 2024
d76f19b
Add constexpr annotations
mborland May 7, 2024
e46424d
Add arg
mborland May 7, 2024
20faba1
Add norm
mborland May 7, 2024
8ee77ce
Add conj overload
mborland May 7, 2024
a5faa8d
Add proj overload and fix const correctness
mborland May 7, 2024
618bdf3
Fix test for std::sqrt promoting float result to double
mborland May 7, 2024
861c6bd
Add exp overload
mborland May 8, 2024
1c95d3a
Add overload for log
mborland May 8, 2024
4abb271
Add complex and scalar basic operations
mborland May 8, 2024
4a35b0d
Add log10 overload
mborland May 8, 2024
aa01997
Make basic operations non-member functions
mborland May 8, 2024
2a5d7bb
Add pow overload
mborland May 8, 2024
c560957
Add sqrt overload
mborland May 8, 2024
f1b88ea
Add sinh overload
mborland May 8, 2024
caf7058
Add cosh overload
mborland May 8, 2024
17b815d
Add tanh overload
mborland May 8, 2024
f9b9071
Add MSVC workaround for test failure
mborland May 8, 2024
9b3575b
Bootstrap from existing complex class
mborland May 9, 2024
214a2d2
Add remaining special functions
mborland May 9, 2024
7ec2760
Add tentative polar() support and tests.
jzmaddock May 14, 2024
411d9ea
Remove MSVC 14.0 for boost 1.86
mborland May 15, 2024
4b0d1e5
Fix usage of if constexpr
mborland May 15, 2024
b437d60
Use SFINAE to fix C++14 build errors
mborland May 16, 2024
b885279
Delete cruft
mborland May 17, 2024
ded45c0
Add nick's test set
mborland May 17, 2024
903d23c
Eigen matrix doesn't support iterators
mborland May 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ else()

endif()

if(BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt")

add_subdirectory(test)

endif()

# Only enable tests when we're the root project
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)

Expand Down
373 changes: 373 additions & 0 deletions include/boost/multiprecision/complex.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,373 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright 2024 Matt Borland. Distributed under the Boost
// Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_MP_COMPLEX_HPP
#define BOOST_MP_COMPLEX_HPP

#include <boost/multiprecision/detail/assert.hpp>
#include <boost/multiprecision/traits/is_backend.hpp>
#include <boost/multiprecision/cpp_bin_float.hpp>
#include <boost/multiprecision/fwd.hpp>
#include <ostream>
#include <istream>
#include <complex>
#include <cmath>

namespace boost {
namespace multiprecision {

template <typename T>
class complex
{
private:
T real_;
T imag_;

public:
BOOST_MP_CXX14_CONSTEXPR complex() noexcept = default;
BOOST_MP_CXX14_CONSTEXPR complex(T real) noexcept : real_ {real}, imag_ {T{0}} {}
BOOST_MP_CXX14_CONSTEXPR complex(T real, T imag) noexcept : real_ {real}, imag_ {imag} {}
BOOST_MP_CXX14_CONSTEXPR complex& operator=(const complex& rhs)
{
real_ = rhs.real_;
imag_ = rhs.imag_;
return *this;
}

BOOST_MP_CXX14_CONSTEXPR T real() const noexcept { return real_; }
BOOST_MP_CXX14_CONSTEXPR T imag() const noexcept { return imag_; }

BOOST_MP_CXX14_CONSTEXPR complex operator+() const { return *this; }
BOOST_MP_CXX14_CONSTEXPR complex operator-() const { return {-real_, -imag_}; }

friend BOOST_MP_CXX14_CONSTEXPR complex operator+(const complex& lhs, const complex& rhs) noexcept
{
return {lhs.real_ + rhs.real_, lhs.imag_ + rhs.imag_};
}

friend BOOST_MP_CXX14_CONSTEXPR complex operator+(const complex& lhs, const T& rhs) noexcept
{
return {lhs.real_ + rhs, lhs.imag_};
}

friend BOOST_MP_CXX14_CONSTEXPR complex operator+(const T& lhs, const complex& rhs) noexcept
{
return {lhs + rhs.real_, rhs.imag_};
}

friend BOOST_MP_CXX14_CONSTEXPR complex operator-(const complex& lhs, const complex& rhs) noexcept
{
return {lhs.real_ - rhs.real_, lhs.imag_ - rhs.imag_};
}

friend BOOST_MP_CXX14_CONSTEXPR complex operator-(const complex& lhs, const T& rhs) noexcept
{
return {lhs.real_ - rhs, lhs.imag_};
}

friend BOOST_MP_CXX14_CONSTEXPR complex operator-(const T& lhs, const complex& rhs) noexcept
{
return {lhs - rhs.real_, -rhs.imag_};
}

friend BOOST_MP_CXX14_CONSTEXPR complex operator*(const complex& lhs, const complex& rhs) noexcept
{
return {lhs.real_ * rhs.real_ - lhs.imag_ * rhs.imag_, lhs.imag_ * rhs.real_ + lhs.real_ * rhs.imag_};
}

friend BOOST_MP_CXX14_CONSTEXPR complex operator*(const complex& lhs, const T& rhs) noexcept
{
return {lhs.real_ * rhs, lhs.imag_ * rhs};
}

friend BOOST_MP_CXX14_CONSTEXPR complex operator*(const T& lhs, const complex& rhs) noexcept
{
return {lhs * rhs.real_, lhs * rhs.imag_};
}

friend BOOST_MP_CXX14_CONSTEXPR complex operator/(const complex& lhs, const complex& rhs) noexcept
{
const T divisor = rhs.real_ * rhs.real_ + rhs.imag_ * rhs.imag_;
const T real_part = (lhs.real_ * rhs.real_ + lhs.imag_ * rhs.imag_) / divisor;
const T imag_part = (lhs.imag_ * rhs.real_ - lhs.real_ * rhs.imag_) / divisor;
return {real_part, imag_part};
}

friend BOOST_MP_CXX14_CONSTEXPR complex operator/(const complex& lhs, const T& rhs) noexcept
{
const T divisor = rhs * rhs;
const T real_part = (lhs.real_ * rhs) / divisor;
const T imag_part = (lhs.imag_ * rhs) / divisor;
return {real_part, imag_part};
}

friend BOOST_MP_CXX14_CONSTEXPR complex operator/(const T& lhs, const complex& rhs) noexcept
{
const T divisor = rhs.real_ * rhs.real_ + rhs.imag_ * rhs.imag_;
const T real_part = (lhs * rhs.real_) / divisor;
const T imag_part = -(lhs.real_ * rhs.imag_) / divisor;
return {real_part, imag_part};
}

BOOST_MP_CXX14_CONSTEXPR complex& operator+=(const complex& rhs) noexcept
{
*this = *this + rhs;
return *this;
}

BOOST_MP_CXX14_CONSTEXPR complex& operator+=(const T& rhs) noexcept
{
*this = *this + rhs;
return *this;
}

BOOST_MP_CXX14_CONSTEXPR complex& operator-=(const complex& rhs) noexcept
{
*this = *this - rhs;
return *this;
}

BOOST_MP_CXX14_CONSTEXPR complex& operator-=(const T& rhs) noexcept
{
*this = *this - rhs;
return *this;
}

BOOST_MP_CXX14_CONSTEXPR complex& operator*=(const complex& rhs) noexcept
{
*this = *this * rhs;
return *this;
}

BOOST_MP_CXX14_CONSTEXPR complex& operator*=(const T& rhs) noexcept
{
*this = *this * rhs;
return *this;
}

BOOST_MP_CXX14_CONSTEXPR complex& operator/=(const complex& rhs) noexcept
{
*this = *this / rhs;
return *this;
}

BOOST_MP_CXX14_CONSTEXPR complex& operator/=(const T& rhs) noexcept
{
*this = *this / rhs;
return *this;
}

BOOST_MP_CXX14_CONSTEXPR bool operator==(const complex& rhs) const noexcept
{
return real_ == rhs.real_ && imag_ == rhs.imag_;
}

BOOST_MP_CXX14_CONSTEXPR bool operator!=(const complex& rhs) const noexcept
{
return !(*this == rhs);
}

BOOST_MP_CXX14_CONSTEXPR bool operator==(const T& rhs) const noexcept
{
return real_ == rhs && imag_ == T{0};
}

BOOST_MP_CXX14_CONSTEXPR bool operator!=(const T& rhs) const noexcept
{
return !(*this == rhs);
}

template <typename CharT, typename Traits>
friend std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const complex& z)
{
std::basic_ostringstream<CharT, Traits> s;
s.flags(os.flags());
s.imbue(os.getloc());
s.precision(os.precision());
s << '(' << z.real_ << ',' << z.imag_ << ')';

return os << s.str();
}

// Supported formats:
// 1) real
// 2) (real)
// 3) (real, imag)
template <typename CharT, typename Traits>
friend std::basic_istream<CharT, Traits>& operator>>(std::basic_istream<CharT, Traits>& is, complex& z)
{
CharT ch {};
T real = T{0};
T imag = T{0};

is >> std::ws;
is.get(ch);

if (ch == '(')
{
// Expecting a format like 2 or 3
is >> std::ws >> real;
is.get(ch);
if (ch == ',')
{
// A comma indicates it's 3
is >> std::ws >> imag;
is.get(ch); // Should be ')'
}
else if (ch != ')')
{
// Syntax error: unexpected character
is.setstate(std::ios_base::failbit);
return is;
}
}
else
{
// No parentheses, just a real number from format 1
is.putback(ch);
is >> real;
}

if (!is.fail())
{
z.real_ = real;
z.imag_ = imag;
}

return is;
}
};

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> polar(const boost::multiprecision::number<T, ET>& rho,
const boost::multiprecision::number<T, ET>& theta) noexcept
{
BOOST_MP_ASSERT_MSG(rho >= 0, "Rho must be positive");
return {rho * cos(theta), rho * sin(theta)};
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR boost::multiprecision::number<T, ET> real(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
return z.real();
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR boost::multiprecision::number<T, ET> imag(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
return z.imag();
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR boost::multiprecision::number<T, ET> abs(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
return hypot(z.real(), z.imag());
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR boost::multiprecision::number<T, ET> arg(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
return atan2(z.imag(), z.real());
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR boost::multiprecision::number<T, ET> norm(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
return z.real() * z.real() + z.imag() * z.imag();
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> conj(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
return {z.real(), -z.imag()};
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> proj(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
if (isinf(z.real()) || isinf(z.imag()))
{
return {std::numeric_limits<boost::multiprecision::number<T, ET>>::infinity(), copysign(boost::multiprecision::number<T, ET>{0}, z.imag())};
}

return z;
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> exp(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mborland you're re-inventing the wheel here? All these complex number functions are already implemented, it's just that number<whatever> is already a complex number type with the same interface as std::complex for a suitably defined whatever. See https://www.boost.org/doc/libs/1_85_0/libs/multiprecision/doc/html/boost_multiprecision/tut/complex.html

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In @NAThompson's original issue he was using cpp_bin_float_100 as the number type. The templates could be enable_if for the already complex types, but if we want to allow a wrapper of any floating-point multiprecision type I don't see any other way around this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess my point is that adding a boost::multiprecision::complex class duplicates what is already there and adds confusion (and maintenance), what if you had (caution untested code ahead!!):

template <typename T, boost::multiprecision::expression_template_option ET>
class complex<boost::multiprecision::number<T, ET>>
{
  using self_type = complex<boost::multiprecision::number<T, ET>>;
  using complex_type = decltype(polar(std::declval<boost::multiprecision::number<T, ET>>(), std::declval<boost::multiprecision::number<T, ET>>()));
  complex_type m_data;

  complex_type& data(){ return m_data; }
  const complex_type& data()const{ return m_data; }
public:
  // public members here..

  // and then:
  friend inline self_type operator+(const self_type& a, const self_type& b)
  { return a.data() + b.data(); }
  // etc
  friend inline self_type exp(const self_type& val) 
  { return exp(val.data()); }
  // etc
};

Now there's no duplication, so bugs fixed in one place are fixed everywhere. Plus if the user has included mpc.hpp for example then mpc will be used for mpfr complex number types rather than synthesising our own type.

It's still a bind writing out all the forwarding functions, but at least they're trivial and improvements in one place propagate throughout.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can give that a go tomorrow. Forwarding functions will be monotonous which is fine. What is non-obvious to me is how to overload say self_type + complex type like is here: https://github.com/boostorg/multiprecision/pull/620/files#diff-db4d91a1fc843b60d38c832c49b4b5ec9b8c22f772d5c5d0a56eea563ae59a5dR50. I'll play with it

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you have the form of the function already there?

BTW those functions are definitely NOT noexcept in the multiprecision case: memory allocations usually are involved!

{
return polar(exp(z.real()), z.imag());
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> log(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
return {log(abs(z)), arg(z)};
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> log10(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
return log(z) / log(boost::multiprecision::number<T, ET>{10});
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> pow(const complex<boost::multiprecision::number<T, ET>>& x,
const complex<boost::multiprecision::number<T, ET>>& y) noexcept
{
return exp(y * log(x));
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> pow(const boost::multiprecision::number<T, ET>& x,
const complex<boost::multiprecision::number<T, ET>>& y) noexcept
{
const complex<boost::multiprecision::number<T, ET>> new_x {x};
return exp(y * log(new_x));
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> pow(const complex<boost::multiprecision::number<T, ET>>& x,
const boost::multiprecision::number<T, ET>& y) noexcept
{
const complex<boost::multiprecision::number<T, ET>> new_y {y};
return exp(new_y * log(x));
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> sqrt(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
return polar(sqrt(abs(z)), arg(z) / 2);
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> sinh(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
return {sinh(z.real()) * cos(z.imag()), cosh(z.real()) * sin(z.imag())};
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> cosh(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
return {cosh(z.real()) * cos(z.imag()), sinh(z.real()) * sin(z.imag())};
}

template <typename T, expression_template_option ET>
inline BOOST_MP_CXX14_CONSTEXPR complex<boost::multiprecision::number<T, ET>> tanh(const complex<boost::multiprecision::number<T, ET>>& z) noexcept
{
return {sinh(z) / cosh(z)};
}

} // Namespace multiprecision
} // Namespace boost

namespace std {

template <typename T, boost::multiprecision::expression_template_option ET>
class complex<boost::multiprecision::number<T, ET>> : public boost::multiprecision::complex<boost::multiprecision::number<T, ET>> {};

} // namespace std

#endif //BOOST_MP_COMPLEX_HPP
9 changes: 9 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright 2018, 2019 Peter Dimov
# Copyright 2024 Matt Borland
# Distributed under the Boost Software License, Version 1.0.
# See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt

file(GLOB SOURCES "*.cpp")
add_library(boost_mp_tests STATIC ${SOURCES})
target_compile_features(boost_mp_tests PRIVATE cxx_std_17)
target_link_libraries(boost_mp_tests PUBLIC Boost::multiprecision Boost::assert Boost::config Boost::core Boost::integer Boost::lexical_cast Boost::math Boost::random)
Loading