Wording for imaginary numbers

Author: Thorsten Ottosen
Contact: nesotto@cs.aau.dk
organizations:Dezide Aps and Aalborg University
Date: 2005-08-24
Number:WG21/N1869 and J16/05-0129
Working Group:Evolution

Abstract

This paper provides wording for n1612 which proposed to extend the complex number facility with imaginary numbers.

Table of Contents

1   Mode of description

2   Changes to general requirements

Modify 26.2 as shown:

1 The header <complex> defines a class template class templates, and numerous functions for representing and manipulating complex numbers.

2 The effect of instantiating the template complex or imaginary for any type other than float, double or long double is unspecified.

3   Changes to synopsis and specification

Modify 26.2.1 to:

namespace std {
    template<class T> class complex;
    template<> class complex<float>;
    template<> class complex<double>;
    template<> class complex<long double>;
    
    template<class T> class imaginary;
    template<> class imaginary<float>;
    template<> class imaginary<double>;
    template<> class imaginary<long double>;
    
    // 26.2.6 operators:
    
    template<class T> complex<T> operator+(const complex<T>&, const complex<T>&);
    template<class T> complex<T> operator+(const complex<T>&, const T&);
    template<class T> complex<T> operator+(const T&, const complex<T>&);
    template<class T> imaginary<T> operator+( imaginary<T>, imaginary<T>);
    template<class T> complex<T> operator+(T, imaginary<T>);
    template<class T> complex<T> operator+(imaginary<T>, T);
    template<class T> complex<T> operator+(const complex<T>&, imaginary<T>);
    template<class T> complex<T> operator+(imaginary<T>, const complex<T>&);

    template<class T> complex<T> operator-(const complex<T>&, const complex<T>&);
    template<class T> complex<T> operator-(const complex<T>&, const T&);
    template<class T> complex<T> operator-(const T&, const complex<T>&);
    template<class T> imaginary<T> operator-(imaginary<T>, imaginary<T>);
    template<class T> complex<T> operator-(T, imaginary<T>);
    template<class T> complex<T> operator-(imaginary<T>, T);
    template<class T> complex<T> operator-(const complex<T>&, imaginary<T>);
    template<class T> complex<T> operator-(imaginary<T>, const complex<T>&);

    template<class T> complex<T> operator*(const complex<T>&, const complex<T>&);
    template<class T> complex<T> operator*(const complex<T>&, const T&);
    template<class T> complex<T> operator*(const T&, const complex<T>&);
    template<class T> T operator*(imaginary<T>, imaginary<T>);
    template<class T> imaginary<T> operator*(T, imaginary<T>);
    template<class T> imaginary<T> operator*(imaginary<T>, T); 
    template<class T> complex<T> operator*(const complex<T>, imaginary<T>);
    template<class T> complex<T> operator*(imaginary<T>, complex<T>);
    
    template<class T> complex<T> operator/(const complex<T>&, const complex<T>&);
    template<class T> complex<T> operator/(const complex<T>&, const T&);
    template<class T> complex<T> operator/(const T&, const complex<T>&);
    template<class T> T operator/(imaginary<T>, imaginary<T>);
    template<class T> imaginary<T> operator/(T, imaginary<T>);
    template<class T> imaginary<T> operator/(imaginary<T>, T);
    template<class T> complex<T> operator/(const complex<T>&, imaginary<T>);
    template<class T> complex<T> operator/(imaginary<T>, const complex<T>&);

    template<class T> complex<T> operator+(const complex<T>&);
    template<class T> imaginary<T> operator+(imaginary<T>);
    
    template<class T> complex<T> operator-(const complex<T>&);
    template<class T> imaginary<T> operator-(imaginary<T>); 
    
    template<class T> bool operator==(const complex<T>&, const complex<T>&);
    template<class T> bool operator==(const complex<T>&, const T&);
    template<class T> bool operator==(const T&, const complex<T>&); 
    template<class T> bool operator==(imaginary<T>, imaginary<T>);
    template<class T> bool operator==(imaginary<T>, const complex<T>&);
    template<class T> bool operator==(const complex<T>&, imaginary<T>); 
    
    template<class T> bool operator!=(const complex<T>&, const complex<T>&);
    template<class T> bool operator!=(const complex<T>&, const T&);
    template<class T> bool operator!=(const T&, const complex<T>&);
    template<class T> bool operator!=(imaginary<T>, imaginary<T>);
    template<class T> bool operator!=(imaginary<T>, const complex<T>&);
    template<class T> bool operator!=(const complex<T>&, imaginary<T>); 
     
    template<class T> bool operator<(imaginary<T>, imaginary<T>);
    template<class T> bool operator<=(imaginary<T>, imaginary<T>);
    template<class T> bool operator>(imaginary<T>, imaginary<T>);
    template<class T> bool operator>=(imaginary<T>, imaginary<T>); 
    
    template<class T, class charT, class traits>
    basic_istream<charT, traits>&
    operator>>(basic_istream<charT, traits>&, complex<T>&);
    template<class T, class charT, class traits>
    basic_ostream<charT, traits>&
    operator<<(basic_ostream<charT, traits>&, const complex<T>&);

Modify 26.2.1 to:

namespace std {
    template<class T>
    class complex {
    public:
        typedef T value_type;
        complex(const T& re = T(), const T& im = T());
        template<class X> complex( imaginary<X> im );
        template<class X> complex( X re, imaginary<X> im );
        complex(const complex&);
        template<class X> complex(const complex<X>&);
        T real() const;
        T imag() const;
        complex<T>& operator= (const T&);
        complex<T>& operator+=(const T&);
        complex<T>& operator-=(const T&);
        complex<T>& operator*=(const T&);
        complex<T>& operator/=(const T&);
        template<class X> complex<T>& operator=(imaginary<X>);
        template<class X> complex<T>& operator+=(imaginary<X>);
        template<class X> complex<T>& operator-=(imaginary<X>);
        template<class X> complex<T>& operator*=(imaginary<X>);
        template<class X> complex<T>& operator/=(imaginary<X>);
        template<class X> complex<T>& operator=(const complex&);
        template<class X> complex<T>& operator= (const complex<X>&);
        template<class X> complex<T>& operator+=(const complex<X>&);
        template<class X> complex<T>& operator-=(const complex<X>&);
        template<class X> complex<T>& operator*=(const complex<X>&);
        template<class X> complex<T>& operator/=(const complex<X>&);
    };
}

Add a new section 26.2.3 [lib.imaginary]:

template<class T>
class imaginary
{
    T imag_; // exposition only
public:
    typedef T value_type;
    imaginary( T imag = T() );
    T& value();
    T  value() const;
    tempate<class X> imaginary<T>& operator+=( imaginary<X> r );
    tempate<class X> imaginary<T>& operator-=( imaginary<X> r );
    imaginary<T>& operator*=( T r );
    imaginary<T>& operator/=( T r );
};

1 The class imaginary describes an imaginary number, that is, a complex number with zero real part.

Modify 26.2.1 to:

template<> class complex<float> {
public:
    typedef float value_type;
    complex(float re = 0.0f, float im = 0.0f);
    template<class X> complex( imaginary<X> im );
    template<class X> complex( X re, imaginary<X> im );
    explicit complex(const complex<double>&);
    explicit complex(const complex<long double>&);
    float real() const;
    float imag() const;
    complex<float>& operator= (float);
    complex<float>& operator+=(float);
    complex<float>& operator-=(float);
    complex<float>& operator*=(float);
    complex<float>& operator/=(float);
    template<class X> complex<float>& operator=(imaginary<X>);
    template<class X> complex<float>& operator+=(imaginary<X>);
    template<class X> complex<float>& operator-=(imaginary<X>);
    template<class X> complex<float>& operator*=(imaginary<X>);
    template<class X> complex<float>& operator/=(imaginary<X>);        
    complex<float>& operator=(const complex<float>&);
    template<class X> complex<float>& operator= (const complex<X>&);
    template<class X> complex<float>& operator+=(const complex<X>&);
    template<class X> complex<float>& operator-=(const complex<X>&);
    template<class X> complex<float>& operator*=(const complex<X>&);
    template<class X> complex<float>& operator/=(const complex<X>&);
};

template<> class complex<double> {
public:
    typedef double value_type;
    complex(double re = 0.0, double im = 0.0);
    template<class X> complex( imaginary<X> im );
    template<class X> complex( X re, imaginary<X> im );
    complex(const complex<float>&);
    explicit complex(const complex<long double>&);
    double real() const;
    double imag() const;
    complex<double>& operator= (double);
    complex<double>& operator+=(double);
    complex<double>& operator-=(double);
    complex<double>& operator*=(double);
    complex<double>& operator/=(double);
    template<class X> complex<double>& operator=(imaginary<X>);
    template<class X> complex<double>& operator+=(imaginary<X>);
    template<class X> complex<double>& operator-=(imaginary<X>);
    template<class X> complex<double>& operator*=(imaginary<X>);
    template<class X> complex<double>& operator/=(imaginary<X>);        
    complex<double>& operator=(const complex<double>&);
    template<class X> complex<double>& operator= (const complex<X>&);
    template<class X> complex<double>& operator+=(const complex<X>&);
    template<class X> complex<double>& operator-=(const complex<X>&);
    template<class X> complex<double>& operator*=(const complex<X>&);
    template<class X> complex<double>& operator/=(const complex<X>&);
    };

template<> class complex<long double> {
public:
    typedef long double value_type;
    complex(long double re = 0.0L, long double im = 0.0L);
    template<class X> complex( imaginary<X> im );
    template<class X> complex( X re, imaginary<X> im );        
    complex(const complex<float>&);
    complex(const complex<double>&);
    long double real() const;
    long double imag() const;
    complex<long double>& operator=(const complex<long double>&);
    complex<long double>& operator= (long double);
    complex<long double>& operator+=(long double);
    complex<long double>& operator-=(long double);
    complex<long double>& operator*=(long double);
    complex<long double>& operator/=(long double);
    template<class X> complex<long double>& operator=(imaginary<X>);
    template<class X> complex<long double>& operator+=(imaginary<X>);
    template<class X> complex<long double>& operator-=(imaginary<X>);
    template<class X> complex<long double>& operator*=(imaginary<X>);
    template<class X> complex<long double>& operator/=(imaginary<X>);                
    template<class X> complex<long double>& operator= (const complex<X>&);
    template<class X> complex<long double>& operator+=(const complex<X>&);
    template<class X> complex<long double>& operator-=(const complex<X>&);
    template<class X> complex<long double>& operator*=(const complex<X>&);
    template<class X> complex<long double>& operator/=(const complex<X>&);
};

Add a new section 26.2.4 [lib.imaginary.special]:

template<> class imaginary<float> {
public:
    typedef float value_type;
    imaginary( float imag = 0.0f );
    float& value();
    float  value() const;
    tempate<class X> imaginary<float>& operator+=( imaginary<X> r );
    tempate<class X> imaginary<float>& operator-=( imaginary<X> r );
    imaginary<float>& operator*=( float r );
    imaginary<float>& operator/=( float r );        
};

template<> class imaginary<double> {
public:
    typedef double value_type;
    imaginary( double imag = 0.0 );
    double& value();
    double  value() const;
    tempate<class X> imaginary<double>& operator+=( imaginary<X> r );
    tempate<class X> imaginary<double>& operator-=( imaginary<X> r );
    imaginary<double>& operator*=( double r );
    imaginary<double>& operator/=( double r );
};

template<> class imaginary<long double> {
public:
    typedef long double value_type;
    imaginary( long double imag = 0.0L );
    long double& value();
    long double  value() const;
    tempate<class X> imaginary<long double>& operator+=( imaginary<X> r );
    tempate<class X> imaginary<long double>& operator-=( imaginary<X> r );
    imaginary<long double>& operator*=( long double r );
    imaginary<long double>& operator/=( long double r );
};

Add the following to 26.2.4:

template<class X> complex( imaginary<T> im );

3 Effects: Constructs an object of class complex.

4 Postcondition: real() == 0 && imag() == im.value().

template<class X> complex( X re, imaginary<T> im );

5 Effects: Constructs an object of class complex.

6 Postcondition: real() == re && imag() == im.value().

Add the following to 26.2.5:

template<class X> complex<T>& operator=(imaginary<X> im);

Postcondition: real() == 0 && imag() == im.value().

Returns: *this.

template<class X> complex<T>& operator+=(imaginary<X> im);

Effects: Adds im.value() to the imaginary part of *this.

Returns: *this.

template<class X> complex<T>& operator-=(imaginary<X> im);

Effects: Subtracts im.value() from the imaginary part of *this.

Returns: *this.

template<class X> complex<T>& operator*=(imaginary<X> im);

Effects: Multiplies the imaginary number im with *this and stores the product in *this.

Complexity: Faster than multiplying two complex numbers.

Returns: *this.

template<class X> complex<T>& operator/=(imaginary<X> im);

Effects: Divides *this with the imaginary number im and stores the product in *this.

Complexity: Faster than dividing two complex numbers.

Returns: *this.

Add a new section 26.2.6 [lib.imaginary.members]:

imaginary( T imag = T() );

1 Effects: Constructs an object of class imaginary.

2 Postcondition: value() == imag.

Add a new section 26.2.7 [lib.imaginary.members.ops]:

T& value();

3 Returns: imag_

T  value() const;

4 Returns: imag_

tempate<class X> imaginary<T>& operator+=( imaginary<X> r );

5 Effects: imag_ += r.value().

6 returns: *this.

tempate<class X> imaginary<T>& operator-=( imaginary<X> r );

7 Effects: imag_ -= r.value().

8 Returns: *this.

imaginary<T>& operator*=( T r );

9 Effects: imag_ *= r.

10 Returns: *this.

Modify 26.2.3 to:

template<class T> complex<T> operator+(const complex<T>& lhs, const complex<T>& rhs);
template<class T> complex<T> operator+(const complex<T>& lhs, const T& rhs);
template<class T> complex<T> operator+(const T& lhs, const complex<T>& rhs);
template<class T> complex<T> operator+( T lhs, imaginary<T> rhs );
template<class T> complex<T> operator+( imaginary<T> lhs, T rhs );
template<class T> complex<T> operator+( const complex<T>& lhs, imaginary<T> rhs );
template<class T> complex<T> operator+( imaginary<T> lhs, const complex<T>& rhs );
.
Returns: complex<T>(lhs) += rhs.

Modify 26.2.6 to:

template<class T> complex<T> operator-(const complex<T>& lhs, const complex<T>& rhs);
template<class T> complex<T> operator-(const complex<T>& lhs, const T& rhs);
template<class T> complex<T> operator-(const T& lhs, const complex<T>& rhs);
template<class T> complex<T> operator-( T lhs, imaginary<T> rhs );
template<class T> complex<T> operator-( imaginary<T> lhs, T rhs );
template<class T> complex<T> operator-( const complex<T>& lhs, imaginary<T> rhs );
template<class T> complex<T> operator-( imaginary<T> lhs, const complex<T>& rhs );
.
Returns: complex<T>(lhs) -= rhs.

Modify 26.2.7 to:

template<class T> complex<T> operator*(const complex<T>& lhs, const complex<T>& rhs);
template<class T> complex<T> operator*(const complex<T>& lhs, const T& rhs);
template<class T> complex<T> operator*(const T& lhs, const complex<T>& rhs);
template<class T> complex<T> operator*( const complex<T>& lhs, imaginary<T> rhs );
template<class T> complex<T> operator*( imaginary<T> lhs, const complex<T>& rhs );
.
Returns: complex<T>(lhs) *= rhs.

Modify 26.2.8 to:

template<class T> complex<T> operator/(const complex<T>& lhs, const complex<T>& rhs);
template<class T> complex<T> operator/(const complex<T>& lhs, const T& rhs);
template<class T> complex<T> operator/(const T& lhs, const complex<T>& rhs);
template<class T> complex<T> operator/( const complex<T>& lhs, imaginary<T> rhs );
template<class T> complex<T> operator/( imaginary<T> lhs, const complex<T>& rhs );
.
Returns: complex<T>(lhs) /= rhs.

Modify 26.2.9 to:

template<class T> bool operator==(const complex<T>& lhs, const complex<T>& rhs);
template<class T> bool operator==(const complex<T>& lhs, const T& rhs);
template<class T> bool operator==(const T& lhs, const complex<T>& rhs);
template<class T> complex<T> operator==( const complex<T>& lhs, imaginary<T> rhs );
template<class T> complex<T> operator==( imaginary<T> lhs, const complex<T>& rhs );
.

Returns: lhs.real() == rhs.real() && lhs.imag() == rhs.imag().

Notes: The imaginary part is assumed to be T(), or 0.0, for the T arguments. The real part is assumed to be 0.0 for imaginary<T> arguments.

Modify 26.2.10 to:

template<class T> bool operator!=(const complex<T>& lhs, const complex<T>& rhs);
template<class T> bool operator!=(const complex<T>& lhs, const T& rhs);
template<class T> bool operator!=(const T& lhs, const complex<T>& rhs);
template<class T> complex<T> operator!=( const complex<T>& lhs, imaginary<T> rhs );
template<class T> complex<T> operator!=( imaginary<T> lhs, const complex<T>& rhs );
.

Returns: lhs.real() != rhs.real() || lhs.imag() != rhs.imag().

Notes: The imaginary part is assumed to be T(), or 0.0, for the T arguments. The real part is assumed to be 0.0 for imaginary<T> arguments.

Add a new section 26.2.7 [lib.imaginary.ops]

template<class T> imaginary<T> operator+( imaginary<T> lhs, imaginary<T> rhs );

1 Returns: lhs += rhs.

template<class T> imaginary<T> operator-( imaginary<T> lhs, imaginary<T> rhs );

2 Returns: lhs -= rhs.

template<class T> T operator*( imaginary<T> lhs, imaginary<T> rhs );

3 Returns: - lhs.value() * rhs.value().

template<class T> imaginary<T> operator*( T lhs, imaginary<T> rhs );

4 Returns: rhs *= lhs.

template<class T> imaginary<T> operator*( imaginary<T> lhs, T rhs );

5 Returns: lhs *= rhs.

template<class T> T operator/(imaginary<T> lhs, imaginary<T> rhs);

6 Returns: lhs.value()/rhs.value().

template<class T> imaginary<T> operator/(T lhs, imaginary<T> rhs);

7 Returns: imaginary<T>( lhs/rhs.value() ).

template<class T> imaginary<T> operator/(imaginary<T> lhs, T rhs);

8 Returns: lhs /= rhs.

template<class T> imaginary<T> operator+( imaginary<T> rhs );

9 Notes: unary operator.

10 Returns: rhs;

template<class T> imaginary<T> operator-( imaginary<T> rhs );

11 Notes: unary operator.

12 Returns: rhs *= -1..

template<class T> bool operator==(imaginary<T> lhs, imaginary<T> rhs);

13 Returns: lhs.value() == rhs.value().

template<class T> bool operator!=(imaginary<T> lhs, imaginary<T> rhs);

14 Returns: lhs.value() != rhs.value().

template<class T> bool operator<(imaginary<T> lhs, imaginary<T> rhs);

15 Returns: lhs.value() < rhs.value().

template<class T> bool operator<=(imaginary<T> lhs, imaginary<T> rhs);

16 Returns: lhs.value() <= rhs.value().

template<class T> bool operator>(imaginary<T> lhs, imaginary<T> rhs);

17 Returns: lhs.value() > rhs.value().

template<class T> bool operator>=(imaginary<T> lhs, imaginary<T> rhs);

18 Returns: lhs.value() >= rhs.value().

3.1   Discussion

  1. should we change all const T& arguments to T? Or should we write const imaginary<T>& instead of imaginary<T>?.
  2. why is some member functions function templates and others not (eg. operator+=())?
  3. is it acceptable that all functions on complex numbers are function templates and so will not allow implicit conversion if passed imaginary numbers?
  4. why do the specialization of complex not directly define the member function templates (eg. why does complex<float>::operator=(complex<float>) exist)?.
  5. operator<<() is not provided for imaginary<T> since this should rather be done for the whole standard library at some point.
  6. operator+/-(T,imaginary<T>) could be given its own specification. It is here assumed that compilers will optimize the redundant double initialization of the imaginary number away.

4   Acknowledgements

The author would like to thank Bill Plauger.