Topic: quaternions


Author: Valentin Bonnard <bonnardv@pratique.fr>
Date: 1999/01/08
Raw View
Christopher Eltschka wrote:
>
> Haim Kreitman wrote:
> >
> > The question is about quaternions , which can be just typedef as:

> >
> > 2. After that I changed
> > template<class T>
> > operator*(const complex& a , const complex& b)   {
> > // usualy implemented as :
> > // return   complex<T> ( a.re*b.re - a.im*b.im , a.re*b.im + a.im*b.re )
> >
> >    return complex<T> (a.re*b.re - conj(b.im)*a.im)
> >                                , b.im*a.re + a.im*conj(b.re) );
> > // the order of multiplication is NOT important for quaternions,
> > // because complex numbers are commutative field
> > // but for Caley numbers i.e. complex < complex < complex < T > > >
> > // it IS important.
> > }
>
> Don't do this (it's not legal C++ anyway - you may not re-define the
> product of the std library).

It's legal. You are just adding another unrelated function.

> However, you can do the following:
>
> template<class T>
>  complex<complex<T> > operator*(complex<complex<T> > const& a,
>                                 complex<complex<T> > const& b)

No: such a function doesn't overload the standard function, it's
in the wrong namespace.

--

Valentin Bonnard                mailto:bonnardv@pratique.fr
info about C++/a propos du C++: http://pages.pratique.fr/~bonnardv/
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Haim Kreitman <haim_kreitman@scitex.com>
Date: 1999/01/04
Raw View
The question is about quaternions , which can be just typedef as:

template <class T>
struct numeric_t {
   typedef complex<complex<T> >   quaternion;
};

I use Visual C++ 5.0 of Microsoft.

1.  I had to add the following global inline functions:

inline float          conj(float x)             { return x;}
inline double       conj(double x)          { return x;}
inline long double conj(long double x)   { return x;}

inline float          real(float x)          { return x;}
inline double       real(double x)        { return x;}
inline long double real(long double x)   { return x;}

inline float          imag(float x)               { return 0;}
inline double       imag(double x)           { return 0;}
inline long double imag(long double x)   { return 0;}

2. After that I changed
template<class T>
operator*(const complex& a , const complex& b)   {
// usualy implemented as :
// return   complex<T> ( a.re*b.re - a.im*b.im , a.re*b.im + a.im*b.re )

   return complex<T> (a.re*b.re - conj(b.im)*a.im)
                               , b.im*a.re + a.im*conj(b.re) );
// the order of multiplication is NOT important for quaternions,
// because complex numbers are commutative field
// but for Caley numbers i.e. complex < complex < complex < T > > >
// it IS important.
}

3. In the similar way I changed operator /


4. In the similar way I changed global functions T conj(const
complex<T>&)
   and also abs.

5. Most of numeric package can work with quaternions (which are VERY
important for 3d rotations)
   except of :
   a) Global function arg(z) , which is actualy is  -log(z/abs(z) )*i
       should have another return value (pure imaginary quaternion)
   b) Global function polar(), which is actualy exp(i*angle)*lenght. 1st
argument must be
       of type pure-imaginary-quaternion.

6. Starting from now quaternions represent usual hypercomplex numbers
    q = a + i*b + j*c + k*d    with    conj(q)=a - i*b - j*c - k*d
    and usual muliplication rules:
    i*i = j*j = k*k = -1    i*j = -j*i = k    j*k = -k*j = i   k*i =
-i*k = j

   Maybe desirable to change opeartor << ()
   Now I print : q = ( (x,y) , (z,u))

                     q = (x,y,z,u)   looks better.

7. The question :

   a) Why global function conj() was NOT defined for real numbers.
   b) Why the standard doesn't define some implementation details
       of such important things as operator* and global function abs()
       Implementators can be non-familiar with possible usage of
       complex package for hypercomplex systems. The 1st of them
       (quaternions) has very many applications in 3d transformations.

8. It is useful to represent complex numbers as x + i*y
   From here derived name imag()  (or vice versa)
   Also useful to represent quaternions as x + i*y + j*z + k*u
   where i,j,k are imaginary ones.
   I propose to call appropriate global functions as jmag(), kmag()
   as continuation of imag()

H.Kreitman
haim_kreitman@scitex.com







[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1999/01/07
Raw View
Haim Kreitman wrote:
>
> The question is about quaternions , which can be just typedef as:
>
> template <class T>
> struct numeric_t {
>    typedef complex<complex<T> >   quaternion;
> };

You say, you want that they can just be typedefed as...

>
> I use Visual C++ 5.0 of Microsoft.
>
> 1.  I had to add the following global inline functions:
>
> inline float          conj(float x)             { return x;}
> inline double       conj(double x)          { return x;}
> inline long double conj(long double x)   { return x;}
>
> inline float          real(float x)          { return x;}
> inline double       real(double x)        { return x;}
> inline long double real(long double x)   { return x;}
>
> inline float          imag(float x)               { return 0;}
> inline double       imag(double x)           { return 0;}
> inline long double imag(long double x)   { return 0;}

Why did you have to add them?

>
> 2. After that I changed
> template<class T>
> operator*(const complex& a , const complex& b)   {
> // usualy implemented as :
> // return   complex<T> ( a.re*b.re - a.im*b.im , a.re*b.im + a.im*b.re )
>
>    return complex<T> (a.re*b.re - conj(b.im)*a.im)
>                                , b.im*a.re + a.im*conj(b.re) );
> // the order of multiplication is NOT important for quaternions,
> // because complex numbers are commutative field
> // but for Caley numbers i.e. complex < complex < complex < T > > >
> // it IS important.
> }

Don't do this (it's not legal C++ anyway - you may not re-define the
product of the std library). However, you can do the following:

template<class T>
 complex<complex<T> > operator*(complex<complex<T> > const& a,
                                complex<complex<T> > const& b)
{
  // same code here as above
}

This way you also can omit step 1: Your function is only
used for complex<complex<T> > types, not for complex<X>
where T is not itself a complex<Y>.

>
> 3. In the similar way I changed operator /
>
> 4. In the similar way I changed global functions T conj(const
> complex<T>&)
>    and also abs.

Modify those definition in the same way.

>
> 5. Most of numeric package can work with quaternions (which are VERY
> important for 3d rotations)
>    except of :
>    a) Global function arg(z) , which is actualy is  -log(z/abs(z) )*i
>        should have another return value (pure imaginary quaternion)

One could also argue that arg(z) should return more angles for
quaternions. arg() is defined as the angle of the complex number,
the fact that it can be expressed with the log expression is just
an interesting fact. Indeed, no implementation will calculate
it this way anyway (indeed, the log definition will probably
call the arg function).

>    b) Global function polar(), which is actualy exp(i*angle)*lenght. 1st
> argument must be
>        of type pure-imaginary-quaternion.

For quaternions, it obviously should have more than two arguments,
which are all real: "polar" tells you to use polar coordinates,
which are well defined in any dimension. However, your first
parameter doesn't fit this interpretation.

In both cases, you might want different functions which do what you
want. For ordinary complex numbers, they would be equivalent to arg
and polar with adapted argument types (and maybe different efficiency
and rounding errors).

arg and polar should then not be used on quaternions at all,
since they don't make sense there (in the way they are defined).

>
> 6. Starting from now quaternions represent usual hypercomplex numbers
>     q = a + i*b + j*c + k*d    with    conj(q)=a - i*b - j*c - k*d
>     and usual muliplication rules:
>     i*i = j*j = k*k = -1    i*j = -j*i = k    j*k = -k*j = i   k*i =
> -i*k = j
>
>    Maybe desirable to change opeartor << ()
>    Now I print : q = ( (x,y) , (z,u))
>
>                      q = (x,y,z,u)   looks better.

In that case, it would be better to add a manipulator for that.
Say

cout << q << endl; // default: ((x, y), (z, w))
cout << flat_complex << q << endl; // flatten: (x, y, z, w)

>
> 7. The question :
>
>    a) Why global function conj() was NOT defined for real numbers.

Because it doesn't really make sense. You won't use them if you
don't want complex numbers - and for that, the complex<T> version
is enough.

>    b) Why the standard doesn't define some implementation details
>        of such important things as operator* and global function abs()
>        Implementators can be non-familiar with possible usage of
>        complex package for hypercomplex systems. The 1st of them
>        (quaternions) has very many applications in 3d transformations.

Definitive answer can only come from commitee members, of course.
However, I can imagine a couple of possible reasons:

- Maybe the commitee members were not aware of that possibility

- Maybe the resulting increased complexity of the standard was not
  desired

- Maybe the standard would have been further delayed due to that
  addition

- Maybe quaternions just were not considered important enough,
  especially since you always can program them yourself anyway.

>
> 8. It is useful to represent complex numbers as x + i*y
>    From here derived name imag()  (or vice versa)
>    Also useful to represent quaternions as x + i*y + j*z + k*u
>    where i,j,k are imaginary ones.
>    I propose to call appropriate global functions as jmag(), kmag()
>    as continuation of imag()

This would get in conflict with the current definition of imag -
currently, imag(q) would give j+ik, if q is complex<complex<T> >.

Moreover, if the goal is to get general hypercomplex numbers, it
would probably be better to add an operator[] which enumerates
the fields, and to have an enumeration in an extra namespace:

namespace std
{
  namespace complex_units
  {
    enum { re=0, im=1, jm=2, km=3 }
  }
}

Then you'd get to the parts of a complex number or quaternion with

complex<double> z;
complex<complex<double> > q;

using namespace std::complex_units; // for simplicity

z[re]; // same as real(z)
z[im]; // same as imag(z)
q[re]; // real part of quaternion
q[im]; // "i-part" of quaternion
q[jm]; // "j-part" of quaternion
q[km]; // "k-part" of quaternion
q[2]; // same as q[jm] - good for index loops

complex<complex<complex<double> > > x;

x[0]; // or x[re]
x[1]; // or x[im]
x[2]; // or x[jm]
x[3]; // or x[km]
x[4]; // no symbolic name
x[5];
x[6];
x[7];


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]