/******************************************************************
 * DPUSER - The Next Generation
 *
 * File:     libfits/fits_ops.cpp
 * Purpose:  Fits class methods doing simple arithmetics
 * Author:   Thomas Ott
 *
 * Revision history
 * ================
 * 13.08.1999: file created
 * 23.09.2000: add handing of non-real fits structures
 * 04.02.2009: Parallelize using openmp
 ******************************************************************/

#include <gsl/gsl_math.h>
#include "fits.h"
#include "math_utils.h"
#include "platform.h"

#ifdef WIN
#pragma warning (disable: 4018) // disable warning for comparing signed and unsigned
#endif /* WIN */

/*!
Add a constant to the array. If the array is of integer type (i.e. membits = I1, I2, I4, I8),
this is very fast since only the bzero entry is modified.
*/

bool Fits::add(const double & a) {
    dpint64 i;
	float v = (float)a;

	if (a != 0.0) {
		switch (membits) {
			case I1:
			case I2:
			case I4:
            case I8:
                bzero += a;
				break;
			case R4:  
#pragma omp parallel for
				for (i = 0; i < n_elements; i++) r4data[i] += v; 
				break;
			case R8:
#pragma omp parallel for
				for (i = 0; i < n_elements; i++) r8data[i] += a;
				break;
			case C16:
#pragma omp parallel for
				for (i = 0; i < n_elements; i++) cdata[i].r += a;
				break;
			default:    return reportError(FITS_ERROR_BITPIX); break;
		}
	}
	return TRUE;
}

/*!
Subtract a constant from the array. If the array is of integer type (i.e. membits = I1, I2, I4, I8),
this is very fast since only the bzero entry is modified.
*/

bool Fits::sub(const double & a) {
    dpint64 i;
	float v = (float)a;

	if (a != 0.0) {
		switch (membits) {
			case I1:
			case I2:
			case I4:
            case I8:
				bzero -= a;
				break;
			case R4:
#pragma omp parallel for
				for (i = 0; i < n_elements; i++) r4data[i] -= v;
				break;
			case R8:
#pragma omp parallel for
				for (i = 0; i < n_elements; i++) r8data[i] -= a;
				break;
			case C16:
#pragma omp parallel for
				for (i = 0; i < n_elements; i++) cdata[i].r -= a;
				break;
			default:    return reportError(FITS_ERROR_BITPIX); break;
		}
	}
	return TRUE;
}

/*!
Multiply a constant to the array. If the array is of integer type (i.e. membits = I1, I2, I4, I8),
this is very fast since only the bzero and bscale entries are modified.
*/

bool Fits::mul(const double & a) {
    dpint64 i;
	float v = (float)a;

	if (a != 1.0) {
		switch (membits) {
			case I1:
			case I2:
			case I4:
            case I8:
				bscale *= a;
				bzero *= a;
				break;
			case R4:
#pragma omp parallel for
				for (i = 0; i < n_elements; i++) r4data[i] *= v;
				break;
			case R8:
#pragma omp parallel for
				for (i = 0; i < n_elements; i++) r8data[i] *= a;
				break;
			case C16:
#pragma omp parallel for
				for (i = 0; i < n_elements; i++) {
					cdata[i].r *= a;
					cdata[i].i *= a;
				}
			break;
			default:    return reportError(FITS_ERROR_BITPIX); break;
		}
	}
	return TRUE;
}

/*!
Divide the array by a constant. If the array is of integer type (i.e. membits = I1, I2, I4, I8),
this is very fast since only the bzero and bscale entries are modified.
*/

bool Fits::div(const double & a) {
    dpint64 i;
	float v = (float)a;

	if (a != 1.0) {
		switch (membits) {
			case I1:
			case I2:
			case I4:
            case I8:
				bscale /= a;
				bzero /= a;
				break;
			case R4:
#pragma omp parallel for
				for (i = 0; i < n_elements; i++) r4data[i] /= v;
				break;
			case R8:
#pragma omp parallel for
				for (i = 0; i < n_elements; i++) r8data[i] /= a;
				break;
			case C16:
#pragma omp parallel for
				for (i = 0; i < n_elements; i++) {
					cdata[i].r /= a;
					cdata[i].i /= a;
				}
			break;
			default:    return reportError(FITS_ERROR_BITPIX); break;
		}
	}
	return TRUE;
}

/*!
Raise the array to the power of a constant. If the array is of integer type (i.e. membits = I1, I2 or I4),
it will be changed to R4. If any resulting array value is complex, the array type will be changed
to C16.
*/

bool Fits::power(const double & a) {
    dpint64 i;
    bool will_be_complex = FALSE;

    /* if bad_exp is true, the exponent has a value that makes it possible, that the result will be complex */
    bool bad_exp = (((a < 0.0) && (a > -1.0)) || ((a > 0.0) && (a < 1.0))) && gsl_finite(a);

    if (membits > R4)
        if (!setType(R4))
            return FALSE;

    switch (membits) {
        case R4:
            for (i = 0; i < n_elements; i++) {
                /* if value is finite and smaller zero AND exponent is finite and smaller zero
                   then result will be complex! & */
                if ((r4data[i] < 0.0) && bad_exp && gsl_finite(r4data[i])) {
                    will_be_complex = TRUE;
                    break;
                }
                r4data[i] = pow(r4data[i], (float)a);
            }
            break;
        case R8:
            for (i = 0; i < n_elements; i++) {
                /* if value is finite and smaller zero AND exponent is finite and smaller zero
                   then result will be complex! & */
                if ((r8data[i] < 0.0) && bad_exp && gsl_finite(r8data[i])) {
                    will_be_complex = TRUE;
                    break;
                }
                r8data[i] = pow(r8data[i], a);
            }
            break;
        case C16:
            will_be_complex = TRUE;
            break;
        default:
            break;
    }

    if (will_be_complex) {
        dpComplex cr;
        if (!setType(C16))
            return FALSE;

        for (i = 0; i < n_elements; i++) {
            cr = complex_pow(dpComplex(cdata[i].r, cdata[i].i), a);
            cdata[i].r = cr.real();
            cdata[i].i = cr.imag();
        }
    }
    return TRUE;
}

/*!
Raise a constant to the power of the array. If the array is of integer type (i.e. membits = I1, I2 or I4),
it will be changed to R4. If any resulting array value is complex, the array type will be changed
to C16.
*/

bool Fits::ipower(const double & a) {
    dpint64 i;
	bool will_be_complex = FALSE;

	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4:  
			for (i = 0; i < n_elements; i++) {
				r4data[i] = pow((float)a, r4data[i]);
				if (!gsl_finite(r4data[i])) {
					will_be_complex = TRUE;
					break;
				}
			}
			break;
		case R8:  
			for (i = 0; i < n_elements; i++) {
				r8data[i] = pow(a, r8data[i]);
				if (!gsl_finite(r8data[i])) {
					will_be_complex = TRUE;
					break;
				}
			}
			break;
		case C16:

			will_be_complex = TRUE;
			break;
		default: break;
	}

	if (will_be_complex) {
		dpComplex cr;

		if (!setType(C16)) return FALSE;

		for (i = 0; i < n_elements; i++) {
			cr = complex_pow(a, dpComplex(cdata[i].r, cdata[i].i));
			cdata[i].r = cr.real();
			cdata[i].i = cr.imag();
		}
	}
	return TRUE;
}

/*!
Add a complex to the array. If the array is of non-complex type it will be converted
*/

bool Fits::add(const dpComplex & a) {
    dpint64 n = 0;

    setType(C16);

    for (n = 0; n < n_elements; n++) {
        cdata[n].r += a.real();
        cdata[n].i += a.imag();
    }

    return TRUE;
}

/*!
subtract a complex from the array. If the array is of non-complex type it will be converted
*/

bool Fits::sub(const dpComplex & a) {
    dpint64 n = 0;

    setType(C16);

    for (n = 0; n < n_elements; n++) {
        cdata[n].r -= a.real();
        cdata[n].i -= a.imag();
    }

    return TRUE;
}

/*!
multiply a complex with the array. If the array is of non-complex type it will be converted
*/

bool Fits::mul(const dpComplex & a) {
    dpint64 n = 0;
    double rr, ii;

    setType(C16);

    for (n = 0; n < n_elements; n++) {
        rr = cdata[n].r;
        ii = cdata[n].i;

        cdata[n].r = rr * a.real() - ii * a.imag();
        cdata[n].i = rr * a.imag() + ii * a.real();
    }

    return TRUE;
}

/*!
divide the array by a complex. If the array is of non-complex type it will be converted
*/

bool Fits::div(const dpComplex & a) {
    dpint64 n = 0;
    double rr, ii, div;

    setType(C16);

    for (n = 0; n < n_elements; n++) {
        rr = cdata[n].r;
        ii = cdata[n].i;
        div = pow(a.real(),2) + pow(a.imag(),2);

        cdata[n].r = (rr * a.real() + ii * a.imag()) / div;
        cdata[n].i = (ii * a.real() - rr * a.imag()) / div;
    }

    return TRUE;
}

/*!
Add an array to the array (pixel by pixel).
If membits of the current array is I1, I2, or I4, it will be changed to at least R4, or
to whatever a.membits is. This is in order to assure no loss in data.
*/
bool Fits::add(const Fits & a) {
	bool rv;

	rv = add1(a);
	if (!rv) rv = add2(a);
	if (!rv) rv = add3(a);
	
	return rv;
}

/*!
Add an array to the array (pixel by pixel).
This version deals with arrays which are of the same size.
*/
bool Fits::add1(const Fits & a) {
	long i;
	long n;

// Check if we can do anything at all, i.e. if the arrays are of the same size
	if (naxis[0] != a.naxis[0]) return FALSE;
	for (i = 1; i <= naxis[0]; i++) if (naxis[i] != a.naxis[i]) return FALSE;

// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}
	
// now do the real work
	switch (membits) {
		case R4: switch (a.membits) {
			case I1:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) r4data[n] += (float)a.i1data[n] * a.bscale + a.bzero;
				break;
			case I2:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) r4data[n] += (float)a.i2data[n] * a.bscale + a.bzero;
				break;
			case I4:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) r4data[n] += (float)a.i4data[n] * a.bscale + a.bzero;
				break;
            case I8:
#pragma omp parallel for
                for (n = 0; n < Nelements(); n++) r4data[n] += (float)a.i8data[n] * a.bscale + a.bzero;
                break;
            case R4:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) r4data[n] += a.r4data[n];
				break;
			default: break;
		}
			break;
		case R8: switch (a.membits) {
			case I1:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) r8data[n] += (double)a.i1data[n] * a.bscale + a.bzero;
				break;
			case I2:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) r8data[n] += (double)a.i2data[n] * a.bscale + a.bzero;
				break;
            case I4:
#pragma omp parallel for
                for (n = 0; n < Nelements(); n++) r8data[n] += (double)a.i4data[n] * a.bscale + a.bzero;
                break;
            case I8:
#pragma omp parallel for
                for (n = 0; n < Nelements(); n++) r8data[n] += (double)a.i8data[n] * a.bscale + a.bzero;
                break;
            case R4:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) r8data[n] += (double)a.r4data[n];
				break;
			case R8:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) r8data[n] += a.r8data[n];
				break;
			default: break;
		}
			break;
		case C16: switch (a.membits) {
			case I1:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) cdata[n].r += (double)a.i1data[n] * a.bscale + a.bzero;
				break;
			case I2:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) cdata[n].r += (double)a.i2data[n] * a.bscale + a.bzero;
				break;
			case I4:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) cdata[n].r += (double)a.i4data[n] * a.bscale + a.bzero;
				break;
            case I8:
#pragma omp parallel for
                for (n = 0; n < Nelements(); n++) cdata[n].r += (double)a.i8data[n] * a.bscale + a.bzero;
                break;
            case R4:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) cdata[n].r += (double)a.r4data[n];
				break;
			case R8:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) cdata[n].r += a.r8data[n];
				break;
			case C16:
#pragma omp parallel for
				for (n = 0; n < Nelements(); n++) {
					cdata[n].r += a.cdata[n].r;
					cdata[n].i += a.cdata[n].i;
				}
				break;
			default: break;
		}
			break;
		default: break;
	}
	return TRUE;
}

/*!
Add an array to the array (Pixel by pixel).
This version deals with the case where the number of axes in the current array
is exactly one greater than in a. We will loop over the current array.
*/

bool Fits::add2(const Fits & a) {
	long i;
	long n;
	bool loop = FALSE;
	
// Check if we can do anything at all
	if (naxis[0] != a.naxis[0] + 1) return FALSE;
	for (i = 1; i < naxis[0]; i++) if (naxis[i] != a.naxis[i]) return FALSE;

// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}

// Now do the real work
	float f;
	double d;
	long z;

	switch (membits) {
		case R4: switch (a.membits) {
			case I1:
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.Nelements(); n++) {
					f = (float)a.i1data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.Nelements()] += f;
				}
				break;
			case I2:
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.Nelements(); n++) {
					f = (float)a.i2data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.Nelements()] += f;
				}
				break;
			case I4:
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.Nelements(); n++) {
					f = (float)a.i4data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.Nelements()] += f;
				}
				break;
            case I8:
#pragma omp parallel for private(f,z)
                for (n = 0; n < a.Nelements(); n++) {
                    f = (float)a.i8data[n] * a.bscale + a.bzero;
                    for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.Nelements()] += f;
                }
                break;
            case R4:
#pragma omp parallel for private(z)
				for (n = 0; n < a.Nelements(); n++) {
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.Nelements()] += a.r4data[n];
				}
				break;
			default: break;
		}
			break;
		case R8: switch (a.membits) {
			case I1:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.Nelements(); n++) {
					d = (double)a.i1data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.Nelements()] += d;
				}
				break;
			case I2:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.Nelements(); n++) {
					d = (double)a.i2data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.Nelements()] += d;
				}
				break;
			case I4:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.Nelements(); n++) {
					d = (double)a.i4data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.Nelements()] += d;
				}
				break;
            case I8:
#pragma omp parallel for private(d,z)
                for (n = 0; n < a.Nelements(); n++) {
                    d = (double)a.i8data[n] * a.bscale + a.bzero;
                    for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.Nelements()] += d;
                }
                break;
            case R4:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.Nelements(); n++) {
					d = (double)a.r4data[n];
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.Nelements()] += d;
				}
				break;
			case R8:
#pragma omp parallel for private(z)
				for (n = 0; n < a.Nelements(); n++) {
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.Nelements()] += a.r8data[n];
				}
				break;
			default: break;
		}
			break;
		case C16: switch (a.membits) {
			case I1: for (n = 0; n < a.Nelements(); n++) {
				d = (double)a.i1data[n] * a.bscale + a.bzero;
				for (z = 0; z < naxis[naxis[0]]; z++) cdata[n + z * a.Nelements()].r += d;
			}
				break;
			case I2: for (n = 0; n < a.Nelements(); n++) {
				d = (double)a.i2data[n] * a.bscale + a.bzero;
				for (z = 0; z < naxis[naxis[0]]; z++) cdata[n + z * a.Nelements()].r += d;
			}
				break;
			case I4: for (n = 0; n < a.Nelements(); n++) {
				d = (double)a.i4data[n] * a.bscale + a.bzero;
				for (z = 0; z < naxis[naxis[0]]; z++) cdata[n + z * a.Nelements()].r += d;
			}
				break;
            case I8: for (n = 0; n < a.Nelements(); n++) {
                d = (double)a.i8data[n] * a.bscale + a.bzero;
                for (z = 0; z < naxis[naxis[0]]; z++) cdata[n + z * a.Nelements()].r += d;
            }
                break;
            case R4: for (n = 0; n < a.Nelements(); n++) {
				d = (double)a.r4data[n];
				for (z = 0; z < naxis[naxis[0]]; z++) cdata[n + z * a.Nelements()].r += d;
			}
				break;
			case R8: for (n = 0; n < a.Nelements(); n++) {
				for (z = 0; z < naxis[naxis[0]]; z++) cdata[n + z * a.Nelements()].r += a.r8data[n];
			}
				break;
			case C16: for (n = 0; n < a.Nelements(); n++) {
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.Nelements()].r += a.cdata[n].r;
					cdata[n + z * a.Nelements()].i += a.cdata[n].i;
				}
			}
				break;
			default: break;
		}
			break;
		default: break;
	}
	return TRUE;
}

/*!
Add an array to the array (pixel by pixel).
This version deals with arrays which are not of the same size,
but have the same number of axes. In this case, the array size
remains unchanged, the addition runs over the smallest size in
each dimension.
*/

bool Fits::add3(const Fits & a) {
    dpint64 x, y, z;
    dpint64 n[4] = {1, 1, 1, 1};
    dpint64 i;

// Check if we can do anything at all
	if (naxis[0] != a.naxis[0]) {
		return FALSE;
	}

// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}
	
// find smallest size
	for (i = 1; i <= Naxis(0); i++) {
		n[i] = Naxis(i);
		if (a.Naxis(i) < Naxis(i)) {
			n[i] = a.Naxis(i);
		}
		if (n[i] < 1) n[i] = 1;
	}
	
// Now do the real work
	switch (membits) {
		case R4: switch (a.membits) {
			case I1:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] += (float)a.i1data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I2:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] += (float)a.i2data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] += (float)a.i4data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
            case I8:
#pragma omp parallel for private(y,z)
                for (x = 0; x < n[1]; x++) {
                    for (y = 0; y < n[2]; y++) {
                        for (z = 0; z < n[3]; z++) {
                            r4data[C_I(x, y, z)] += (float)a.i8data[a.C_I(x, y, z)] * a.bscale + a.bzero;
                        }
                    }
                }
                break;
            case R4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] += a.r4data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			default: break;
		}
			break;
		case R8: switch (a.membits) {
			case I1:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] += (double)a.i1data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I2:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] += (double)a.i2data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] += (double)a.i4data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
            case I8:
#pragma omp parallel for private(y,z)
                for (x = 0; x < n[1]; x++) {
                    for (y = 0; y < n[2]; y++) {
                        for (z = 0; z < n[3]; z++) {
                            r8data[C_I(x, y, z)] += (double)a.i8data[a.C_I(x, y, z)] * a.bscale + a.bzero;
                        }
                    }
                }
                break;
            case R4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] += (double)a.r4data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			case R8:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] += a.r8data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			default: break;
			}
				break;
		case C16: switch (a.membits) {
			case I1:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)].r += (double)a.i1data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I2:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)].r += (double)a.i2data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)].r += (double)a.i4data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
            case I8:
#pragma omp parallel for private(y,z)
                for (x = 0; x < n[1]; x++) {
                    for (y = 0; y < n[2]; y++) {
                        for (z = 0; z < n[3]; z++) {
                            cdata[C_I(x, y, z)].r += (double)a.i8data[a.C_I(x, y, z)] * a.bscale + a.bzero;
                        }
                    }
                }
                break;
            case R4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)].r += (double)a.r4data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			case R8:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)].r += a.r8data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			case C16:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)].r += a.cdata[a.C_I(x, y, z)].r;
							cdata[C_I(x, y, z)].i += a.cdata[a.C_I(x, y, z)].i;
						}
					}
				}
				break;
			default: break;
		}
			break;
		default: break;
	}
	return TRUE;
}

/*!
Subtract an array from the array (pixel by pixel).
 If membits of the current array is I1, I2, or I4, it will be changed to at least R4, or
 to whatever a.membits is. This is in order to assure no loss in data.
 */
bool Fits::sub(const Fits & a) {
	bool rv;
	
	rv = sub1(a);
	if (!rv) rv = sub2(a);
	if (!rv) rv = sub3(a);
	
	return rv;
}


/*!
Subtract an array from the array (Pixel by pixel). If the number of axes in the current array
is exactly one greater than in a, we will loop over the current array.
If membits of the current array is I1, I2, or I4, it will be changed to at least R4, or
to whatever a.membits is. This is in order to assure no loss in data.
*/
bool Fits::sub1(const Fits & a) {
    dpint64 i;
    dpint64 n;
	
	// Check if we can do anything at all, i.e. if the arrays are of the same size
	if (naxis[0] != a.naxis[0]) return FALSE;
	for (i = 1; i <= naxis[0]; i++) if (naxis[i] != a.naxis[i]) return FALSE;
	
	// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}
	switch (membits) {
		case R4: switch (a.membits) {
			case I1:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r4data[n] -= (float)a.i1data[n] * a.bscale + a.bzero;
				break;
			case I2:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r4data[n] -= (float)a.i2data[n] * a.bscale + a.bzero;
				break;
			case I4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r4data[n] -= (float)a.i4data[n] * a.bscale + a.bzero;
				break;
            case I8:
#pragma omp parallel for
                for (n = 0; n < n_elements; n++) r4data[n] -= (float)a.i8data[n] * a.bscale + a.bzero;
                break;
            case R4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r4data[n] -= a.r4data[n];
				break;
			default: break;
		}
			break;
		case R8: switch (a.membits) {
			case I1:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] -= (double)a.i1data[n] * a.bscale + a.bzero;
				break;
			case I2:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] -= (double)a.i2data[n] * a.bscale + a.bzero;
				break;
			case I4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] -= (double)a.i4data[n] * a.bscale + a.bzero;
				break;
            case I8:
#pragma omp parallel for
                for (n = 0; n < n_elements; n++) r8data[n] -= (double)a.i8data[n] * a.bscale + a.bzero;
                break;
            case R4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] -= (double)a.r4data[n];
				break;
			case R8:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] -= a.r8data[n];
				break;
			default: break;
		}
			break;
		case C16: switch (a.membits) {
			case I1:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) cdata[n].r -= (double)a.i1data[n] * a.bscale + a.bzero;
				break;
			case I2:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) cdata[n].r -= (double)a.i2data[n] * a.bscale + a.bzero;
				break;
			case I4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) cdata[n].r -= (double)a.i4data[n] * a.bscale + a.bzero;
				break;
            case I8:
#pragma omp parallel for
                for (n = 0; n < n_elements; n++) cdata[n].r -= (double)a.i8data[n] * a.bscale + a.bzero;
                break;
            case R4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) cdata[n].r -= (double)a.r4data[n];
				break;
			case R8:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) cdata[n].r -= a.r8data[n];
				break;
			case C16:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) {
					cdata[n].r -= a.cdata[n].r;
					cdata[n].i -= a.cdata[n].i;
				}
				break;
			default: break;
		}
			break;
		default: break;
	}
	return TRUE;
}

bool Fits::sub2(const Fits & a) {
    dpint64 i;
    dpint64 n;
	bool loop = FALSE;
	

// Check if we can do anything at all
	if (naxis[0] != a.naxis[0] + 1) return FALSE;
	for (i = 1; i < naxis[0]; i++) if (naxis[i] != a.naxis[i]) return FALSE;
	
// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}
	
	// Now do the real work
	float f;
	double d;
    dpint64 z;

	switch (membits) {
		case R4: switch (a.membits) {
			case I1:
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.n_elements; n++) {
					f = (float)a.i1data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] -= f;
				}
				break;
			case I2:
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.n_elements; n++) {
					f = (float)a.i2data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] -= f;
				}
				break;
			case I4:
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.n_elements; n++) {
					f = (float)a.i4data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] -= f;
				}
				break;
            case I8:
#pragma omp parallel for private(f,z)
                for (n = 0; n < a.n_elements; n++) {
                    f = (float)a.i8data[n] * a.bscale + a.bzero;
                    for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] -= f;
                }
                break;
            case R4:
#pragma omp parallel for private(z)
				for (n = 0; n < a.n_elements; n++) {
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] -= a.r4data[n];
				}
				break;
			default: break;
		}
			break;
		case R8: switch (a.membits) {
			case I1:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i1data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] -= d;
				}
				break;
			case I2:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i2data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] -= d;
				}
				break;
			case I4:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i4data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] -= d;
				}
				break;
            case I8:
#pragma omp parallel for private(d,z)
                for (n = 0; n < a.n_elements; n++) {
                    d = (double)a.i8data[n] * a.bscale + a.bzero;
                    for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] -= d;
                }
                break;
            case R4:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = (double)a.r4data[n];
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] -= d;
				}
				break;
			case R8:
#pragma omp parallel for private(z)
				for (n = 0; n < a.n_elements; n++) {
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] -= a.r8data[n];
				}
				break;
			default: break;
		}
			break;
		case C16: switch (a.membits) {
			case I1: for (n = 0; n < a.n_elements; n++) {
				d = (double)a.i1data[n] * a.bscale + a.bzero;
				for (z = 0; z < naxis[naxis[0]]; z++) cdata[n + z * a.n_elements].r -= d;
			}
				break;
			case I2: for (n = 0; n < a.n_elements; n++) {
				d = (double)a.i2data[n] * a.bscale + a.bzero;
				for (z = 0; z < naxis[naxis[0]]; z++) cdata[n + z * a.n_elements].r -= d;
			}
				break;
			case I4: for (n = 0; n < a.n_elements; n++) {
				d = (double)a.i4data[n] * a.bscale + a.bzero;
				for (z = 0; z < naxis[naxis[0]]; z++) cdata[n + z * a.n_elements].r -= d;
			}
				break;
            case I8: for (n = 0; n < a.n_elements; n++) {
                d = (double)a.i8data[n] * a.bscale + a.bzero;
                for (z = 0; z < naxis[naxis[0]]; z++) cdata[n + z * a.n_elements].r -= d;
            }
                break;
            case R4: for (n = 0; n < a.n_elements; n++) {
				d = (double)a.r4data[n];
				for (z = 0; z < naxis[naxis[0]]; z++) cdata[n + z * a.n_elements].r -= d;
			}
				break;
			case R8: for (n = 0; n < a.n_elements; n++) {
				for (z = 0; z < naxis[naxis[0]]; z++) cdata[n + z * a.n_elements].r -= a.r8data[n];
			}
				break;
			case C16: for (n = 0; n < a.n_elements; n++) {
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.n_elements].r -= a.cdata[n].r;
					cdata[n + z * a.n_elements].i -= a.cdata[n].i;
				}
			}
				break;
			default: break;
		}
			break;
		default: break;
	}
	return TRUE;
}

bool Fits::sub3(const Fits &a) {
    dpint64 x, y, z;
    dpint64 n[4] = {1, 1, 1, 1};
    dpint64 i;
	
	// Check if we can do anything at all
	if (naxis[0] != a.naxis[0]) {
		return FALSE;
	}
	
	// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}
	
	// find smallest size
	for (i = 1; i <= Naxis(0); i++) {
		n[i] = Naxis(i);
		if (a.Naxis(i) < Naxis(i)) {
			n[i] = a.Naxis(i);
		}
		if (n[i] < 1) n[i] = 1;
	}
	
	// Now do the real work
	switch (membits) {
		case R4: switch (a.membits) {
			case I1:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] -= (float)a.i1data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I2:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] -= (float)a.i2data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] -= (float)a.i4data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
            case I8:
#pragma omp parallel for private(y,z)
                for (x = 0; x < n[1]; x++) {
                    for (y = 0; y < n[2]; y++) {
                        for (z = 0; z < n[3]; z++) {
                            r4data[C_I(x, y, z)] -= (float)a.i8data[a.C_I(x, y, z)] * a.bscale + a.bzero;
                        }
                    }
                }
                break;
            case R4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] -= a.r4data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			default: break;
		}
			break;
		case R8: switch (a.membits) {
			case I1:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] -= (double)a.i1data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I2:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] -= (double)a.i2data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] -= (double)a.i4data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
            case I8:
#pragma omp parallel for private(y,z)
                for (x = 0; x < n[1]; x++) {
                    for (y = 0; y < n[2]; y++) {
                        for (z = 0; z < n[3]; z++) {
                            r8data[C_I(x, y, z)] -= (double)a.i8data[a.C_I(x, y, z)] * a.bscale + a.bzero;
                        }
                    }
                }
                break;
            case R4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] -= (double)a.r4data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			case R8:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] -= a.r8data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			default: break;
		}
			break;
		case C16: switch (a.membits) {
			case I1:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)].r -= (double)a.i1data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I2:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)].r -= (double)a.i2data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I4:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)].r -= (double)a.i4data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
            case I8:
                for (x = 0; x < n[1]; x++) {
                    for (y = 0; y < n[2]; y++) {
                        for (z = 0; z < n[3]; z++) {
                            cdata[C_I(x, y, z)].r -= (double)a.i8data[a.C_I(x, y, z)] * a.bscale + a.bzero;
                        }
                    }
                }
                break;
            case R4:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)].r -= (double)a.r4data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			case R8:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)].r -= a.r8data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			case C16:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)].r -= a.cdata[a.C_I(x, y, z)].r;
							cdata[C_I(x, y, z)].i -= a.cdata[a.C_I(x, y, z)].i;
						}
					}
				}
				break;
			default: break;
		}
			break;
		default: break;
	}
	return TRUE;
}

/*!
Multiply an array to the array (Pixel by pixel). If the number of axes in the current array
is exactly one greater than in a, we will loop over the current array.
If membits of the current array is I1, I2, or I4, it will be changed to at least R4, or
to whatever a.membits is. This is in order to assure no loss in data.
*/

bool Fits::mul(const Fits & a) {
	bool rv;
	
	rv = mul1(a);
	if (!rv) rv = mul2(a);
	if (!rv) rv = mul3(a);
	
	return rv;
}

/*!
Multiply an array to the array (pixel by pixel).
 This version deals with arrays which are of the same size.
 */
bool Fits::mul1(const Fits & a) {
    dpint64 i;
    dpint64 n;
	
	// Check if we can do anything at all, i.e. if the arrays are of the same size
	if (naxis[0] != a.naxis[0]) return FALSE;
	for (i = 1; i <= naxis[0]; i++) if (naxis[i] != a.naxis[i]) return FALSE;
	
	// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}
	
	// now do the real work
	double d;
	
	switch (membits) {
		case R4: switch (a.membits) {
			case I1:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r4data[n] *= (float)a.i1data[n] * a.bscale + a.bzero;
				break;
			case I2:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r4data[n] *= (float)a.i2data[n] * a.bscale + a.bzero;
				break;
			case I4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r4data[n] *= (float)a.i4data[n] * a.bscale + a.bzero;
				break;
            case I8:
#pragma omp parallel for
                for (n = 0; n < n_elements; n++) r4data[n] *= (float)a.i8data[n] * a.bscale + a.bzero;
                break;
            case R4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r4data[n] *= a.r4data[n];
				break;
			default: break;
		}
			break;
		case R8: switch (a.membits) {
			case I1:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] *= (double)a.i1data[n] * a.bscale + a.bzero;
				break;
			case I2:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] *= (double)a.i2data[n] * a.bscale + a.bzero;
				break;
			case I4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] *= (double)a.i4data[n] * a.bscale + a.bzero;
				break;
            case I8:
#pragma omp parallel for
                for (n = 0; n < n_elements; n++) r8data[n] *= (double)a.i8data[n] * a.bscale + a.bzero;
                break;
            case R4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] *= (double)a.r4data[n];
				break;
			case R8:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] *= a.r8data[n];
				break;
			default: break;
		}
			break;
		case C16: switch (a.membits) {
			case I1: for (n = 0; n < n_elements; n++) {
				d = (double)a.i1data[n] * a.bscale + a.bzero;
				cdata[n].r *= d;
				cdata[n].i *= d;
			}
				break;
			case I2: for (n = 0; n < n_elements; n++) {
				d = (double)a.i2data[n] * a.bscale + a.bzero;
				cdata[n].r *= d;
				cdata[n].i *= d;
			}
				break;
			case I4: for (n = 0; n < n_elements; n++) {
				d = (double)a.i4data[n] * a.bscale + a.bzero;
				cdata[n].r *= d;
				cdata[n].i *= d;
			}
				break;
            case I8: for (n = 0; n < n_elements; n++) {
                d = (double)a.i8data[n] * a.bscale + a.bzero;
                cdata[n].r *= d;
                cdata[n].i *= d;
            }
                break;
            case R4: for (n = 0; n < n_elements; n++) {
				d = (double)a.r4data[n];
				cdata[n].r *= d;
				cdata[n].i *= d;
			}
				break;
			case R8: for (n = 0; n < n_elements; n++) {
				d = a.r8data[n];
				cdata[n].r *= d;
				cdata[n].i *= d;
			}
				break;
			case C16: for (n = 0; n < n_elements; n++) {
				d = cdata[n].r * a.cdata[n].r - cdata[n].i * a.cdata[n].i;
				cdata[n].i = cdata[n].i * a.cdata[n].r + cdata[n].r * a.cdata[n].i;
				cdata[n].r = d;
			}
				break;
			default: break;
		}
			break;
		default: break;
	}
	return TRUE;
}

/*!
Multiply an array to the array (Pixel by pixel).
 This version deals with the case where the number of axes in the current array
 is exactly one greater than in a. We will loop over the current array.
 */

bool Fits::mul2(const Fits & a) {
    dpint64 i;
    dpint64 n;
	bool loop = FALSE;
	
	// Check if we can do anything at all
	if (naxis[0] != a.naxis[0] + 1) return FALSE;
	for (i = 1; i < naxis[0]; i++) if (naxis[i] != a.naxis[i]) return FALSE;
	
	// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}
	
	// Now do the real work
	float f;
	double d;
    dpint64 z;
	
	switch (membits) {
		case R4: switch (a.membits) {
			case I1:
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.n_elements; n++) {
					f = (float)a.i1data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] *= f;
				}
				break;
			case I2:
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.n_elements; n++) {
					f = (float)a.i2data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] *= f;
				}
				break;
			case I4:
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.n_elements; n++) {
					f = (float)a.i4data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] *= f;
				}
				break;
            case I8:
#pragma omp parallel for private(f,z)
                for (n = 0; n < a.n_elements; n++) {
                    f = (float)a.i8data[n] * a.bscale + a.bzero;
                    for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] *= f;
                }
                break;
            case R4:
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.n_elements; n++) {
					f = a.r4data[n];
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] *= f;
				}
				break;
			default: break;
		}
			break;
		case R8: switch (a.membits) {
			case I1:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i1data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] *= d;
				}
				break;
			case I2:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i2data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] *= d;
				}
				break;
			case I4:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i4data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] *= d;
				}
				break;
            case I8:
#pragma omp parallel for private(d,z)
                for (n = 0; n < a.n_elements; n++) {
                    d = (double)a.i8data[n] * a.bscale + a.bzero;
                    for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] *= d;
                }
                break;
            case R4:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = (double)a.r4data[n];
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] *= d;
				}
				break;
			case R8:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = a.r8data[n];
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] *= d;
				}
				break;
			default: break;
		}
			break;
		case C16: switch (a.membits) {
			case I1: for (n = 0; n < a.n_elements; n++) {
				d = (double)a.i1data[n] * a.bscale + a.bzero;
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.n_elements].r *= d;
					cdata[n + z * a.n_elements].i *= d;
				}
			}
				break;
			case I2: for (n = 0; n < a.n_elements; n++) {
				d = (double)a.i2data[n] * a.bscale + a.bzero;
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.n_elements].r *= d;
					cdata[n + z * a.n_elements].i *= d;
				}
			}
				break;
			case I4: for (n = 0; n < a.n_elements; n++) {
				d = (double)a.i4data[n] * a.bscale + a.bzero;
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.n_elements].r *= d;
					cdata[n + z * a.n_elements].i *= d;
				}
			}
				break;
            case I8: for (n = 0; n < a.n_elements; n++) {
                d = (double)a.i8data[n] * a.bscale + a.bzero;
                for (z = 0; z < naxis[naxis[0]]; z++) {
                    cdata[n + z * a.n_elements].r *= d;
                    cdata[n + z * a.n_elements].i *= d;
                }
            }
                break;
            case R4: for (n = 0; n < a.n_elements; n++) {
				d = (double)a.r4data[n];
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.n_elements].r *= d;
					cdata[n + z * a.n_elements].i *= d;
				}
			}
				break;
			case R8: for (n = 0; n < a.n_elements; n++) {
				d = a.r8data[n];
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.n_elements].r *= d;
					cdata[n + z * a.n_elements].i *= d;
				}
			}
				break;
			case C16: for (n = 0; n < a.n_elements; n++) {
				for (z = 0; z < naxis[naxis[0]]; z++) {
					d = cdata[n + z * a.n_elements].r * a.cdata[n].r - cdata[n + z * a.n_elements].i * a.cdata[n].i;
					cdata[n + z * a.n_elements].i = cdata[n + z * a.n_elements].i * a.cdata[n].r + cdata[n + z * a.n_elements].r * a.cdata[n].i;
					cdata[n + z * a.n_elements].r = d;
				}
			}
				break;
			default: break;
		}
			break;
		default: break;
	}
	return TRUE;
}

bool Fits::mul3(const Fits &a) {
    dpint64 x, y, z;
    dpint64 n[4] = {1, 1, 1, 1};
    dpint64 i;
	
	// Check if we can do anything at all
	if (naxis[0] != a.naxis[0]) {
		return FALSE;
	}
	
	// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}
	
	// find smallest size
	for (i = 1; i <= Naxis(0); i++) {
		n[i] = Naxis(i);
		if (a.Naxis(i) < Naxis(i)) {
			n[i] = a.Naxis(i);
		}
		if (n[i] < 1) n[i] = 1;
	}
	
	double d;
	
	// Now do the real work
	switch (membits) {
		case R4: switch (a.membits) {
			case I1:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] *= (float)a.i1data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I2:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] *= (float)a.i2data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] *= (float)a.i4data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
            case I8:
#pragma omp parallel for private(y,z)
                for (x = 0; x < n[1]; x++) {
                    for (y = 0; y < n[2]; y++) {
                        for (z = 0; z < n[3]; z++) {
                            r4data[C_I(x, y, z)] *= (float)a.i8data[a.C_I(x, y, z)] * a.bscale + a.bzero;
                        }
                    }
                }
                break;
            case R4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] *= a.r4data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			default: break;
		}
			break;
		case R8: switch (a.membits) {
			case I1:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] *= (double)a.i1data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I2:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] *= (double)a.i2data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] *= (double)a.i4data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
            case I8:
#pragma omp parallel for private(y,z)
                for (x = 0; x < n[1]; x++) {
                    for (y = 0; y < n[2]; y++) {
                        for (z = 0; z < n[3]; z++) {
                            r8data[C_I(x, y, z)] *= (double)a.i8data[a.C_I(x, y, z)] * a.bscale + a.bzero;
                        }
                    }
                }
                break;
            case R4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] *= (double)a.r4data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			case R8:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] *= a.r8data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			default: break;
		}
			break;
		case C16: switch (a.membits) {
			case I1:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							d = (double)a.i1data[a.C_I(x, y, z)] * a.bscale + a.bzero;
							cdata[C_I(x, y, z)].r *= d;
							cdata[C_I(x, y, z)].i *= d;
						}
					}
				}
				break;
			case I2:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							d = (double)a.i2data[a.C_I(x, y, z)] * a.bscale + a.bzero;
							cdata[C_I(x, y, z)].r *= d;
							cdata[C_I(x, y, z)].i *= d;
						}
					}
				}
				break;
			case I4:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							d = (double)a.i4data[a.C_I(x, y, z)] * a.bscale + a.bzero;
							cdata[C_I(x, y, z)].r *= d;
							cdata[C_I(x, y, z)].i *= d;
						}
					}
				}
				break;
            case I8:
                for (x = 0; x < n[1]; x++) {
                    for (y = 0; y < n[2]; y++) {
                        for (z = 0; z < n[3]; z++) {
                            d = (double)a.i8data[a.C_I(x, y, z)] * a.bscale + a.bzero;
                            cdata[C_I(x, y, z)].r *= d;
                            cdata[C_I(x, y, z)].i *= d;
                        }
                    }
                }
                break;
            case R4:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							d = (double)a.r4data[a.C_I(x, y, z)];
							cdata[C_I(x, y, z)].r *= d;
							cdata[C_I(x, y, z)].i *= d;
						}
					}
				}
				break;
			case R8:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							d = a.r8data[a.C_I(x, y, z)];
							cdata[C_I(x, y, z)].r *= d;
							cdata[C_I(x, y, z)].i *= d;
						}
					}
				}
				break;
			case C16:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							d = cdata[C_I(x, y, z)].r * a.cdata[a.C_I(x, y, z)].r - cdata[C_I(x, y, z)].i * a.cdata[a.C_I(x, y, z)].i;
							cdata[C_I(x, y, z)].i = cdata[C_I(x, y, z)].i * a.cdata[a.C_I(x, y, z)].r + cdata[C_I(x, y, z)].r * a.cdata[a.C_I(x, y, z)].i;
							cdata[C_I(x, y, z)].r = d;
						}
					}
				}
				break;
			default: break;
		}
			break;
		default: break;
	}
	return TRUE;
}

/*!
Divide one array by another (Pixel by pixel). If the number of axes in the current array
is exactly one greater than in a, we will loop over the current array.
If membits of the current array is I1, I2, or I4, it will be changed to at least R4, or
to whatever a.membits is. This is in order to assure no loss in data.
*/
bool Fits::div(const Fits & a) {
	bool rv;
	
	rv = div1(a);
	if (!rv) rv = div2(a);
	if (!rv) rv = div3(a);
	
	return rv;
}

/*!
Divide one array by another array (pixel by pixel).
This version deals with arrays which are of the same size.
*/
bool Fits::div1(const Fits & a) {
    dpint64 i;
    dpint64 n;
	
	// Check if we can do anything at all, i.e. if the arrays are of the same size
	if (naxis[0] != a.naxis[0]) return FALSE;
	for (i = 1; i <= naxis[0]; i++) if (naxis[i] != a.naxis[i]) return FALSE;
	
	// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}
	
	// now do the real work
	double d;
	
	switch (membits) {
		case R4: switch (a.membits) {
			case I1:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r4data[n] /= (float)a.i1data[n] * a.bscale + a.bzero;
				break;
			case I2:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r4data[n] /= (float)a.i2data[n] * a.bscale + a.bzero;
				break;
			case I4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r4data[n] /= (float)a.i4data[n] * a.bscale + a.bzero;
				break;
            case I8:
#pragma omp parallel for
                for (n = 0; n < n_elements; n++) r4data[n] /= (float)a.i8data[n] * a.bscale + a.bzero;
                break;
            case R4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r4data[n] /= a.r4data[n];
				break;
			default: break;
		}
			break;
		case R8: switch (a.membits) {
			case I1:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] /= (double)a.i1data[n] * a.bscale + a.bzero;
				break;
			case I2:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] /= (double)a.i2data[n] * a.bscale + a.bzero;
				break;
			case I4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] /= (double)a.i4data[n] * a.bscale + a.bzero;
				break;
            case I8:
#pragma omp parallel for
                for (n = 0; n < n_elements; n++) r8data[n] /= (double)a.i8data[n] * a.bscale + a.bzero;
                break;
            case R4:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] /= (double)a.r4data[n];
				break;
			case R8:
#pragma omp parallel for
				for (n = 0; n < n_elements; n++) r8data[n] /= a.r8data[n];
				break;
			default: break;
		}
			break;
		case C16: switch (a.membits) {
			case I1: for (n = 0; n < n_elements; n++) {
				d = (double)a.i1data[n] * a.bscale + a.bzero;
				cdata[n].r /= d;
				cdata[n].i /= d;
			}
				break;
			case I2: for (n = 0; n < n_elements; n++) {
				d = (double)a.i2data[n] * a.bscale + a.bzero;
				cdata[n].r /= d;
				cdata[n].i /= d;
			}
				break;
			case I4: for (n = 0; n < n_elements; n++) {
				d = (double)a.i4data[n] * a.bscale + a.bzero;
				cdata[n].r /= d;
				cdata[n].i /= d;
			}
				break;
            case I8: for (n = 0; n < n_elements; n++) {
                d = (double)a.i8data[n] * a.bscale + a.bzero;
                cdata[n].r /= d;
                cdata[n].i /= d;
            }
                break;
            case R4: for (n = 0; n < n_elements; n++) {
				d = (double)a.r4data[n];
				cdata[n].r /= d;
				cdata[n].i /= d;
			}
				break;
			case R8: for (n = 0; n < n_elements; n++) {
				cdata[n].r /= a.r8data[n];
				cdata[n].i /= a.r8data[n];
			}
				break;
			case C16: for (n = 0; n < n_elements; n++) {
				cdata[n] = Cdiv(cdata[n], a.cdata[n]);
			}
				break;
			default: break;
		}
			break;
		default: break;
	}
	return TRUE;
}

/*!
Divide an array by another array (Pixel by pixel).
This version deals with the case where the number of axes in the current array
is exactly one greater than in a. We will loop over the current array.
*/
bool Fits::div2(const Fits & a) {
    dpint64 i;
    dpint64 n;
	bool loop = FALSE;
	
	// Check if we can do anything at all
	if (naxis[0] != a.naxis[0] + 1) return FALSE;
	for (i = 1; i < naxis[0]; i++) if (naxis[i] != a.naxis[i]) return FALSE;
	
	// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}
	
	// Now do the real work
	float f;
	double d;
    dpint64 z;
	
	switch (membits) {
		case R4: switch (a.membits) {
			case I1: 
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.n_elements; n++) {
					f = (float)a.i1data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] /= f;
				}
				break;
			case I2:
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.n_elements; n++) {
					f = (float)a.i2data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] /= f;
				}
				break;
			case I4:
#pragma omp parallel for private(f,z)
				for (n = 0; n < a.n_elements; n++) {
					f = (float)a.i4data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] /= f;
				}
				break;
            case I8:
#pragma omp parallel for private(f,z)
                for (n = 0; n < a.n_elements; n++) {
                    f = (float)a.i8data[n] * a.bscale + a.bzero;
                    for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] /= f;
                }
                break;
            case R4:
#pragma omp parallel for private(z)
				for (n = 0; n < a.n_elements; n++) {
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] /= a.r4data[n];
				}
				break;
			default: break;
		}
			break;
		case R8: switch (a.membits) {
			case I1:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i1data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] /= d;
				}
				break;
			case I2:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i2data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] /= d;
				}
				break;
			case I4:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i4data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] /= d;
				}
				break;
            case I8:
#pragma omp parallel for private(d,z)
                for (n = 0; n < a.n_elements; n++) {
                    d = (double)a.i8data[n] * a.bscale + a.bzero;
                    for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] /= d;
                }
                break;
            case R4:
#pragma omp parallel for private(d,z)
				for (n = 0; n < a.n_elements; n++) {
					d = (double)a.r4data[n];
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] /= d;
				}
				break;
			case R8:
#pragma omp parallel for private(z)
				for (n = 0; n < a.n_elements; n++) {
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] /= a.r8data[n];
				}
				break;
			default: break;
		}
			break;
		case C16: switch (a.membits) {
			case I1: for (n = 0; n < a.n_elements; n++) {
				d = (double)a.i1data[n] * a.bscale + a.bzero;
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.n_elements].r /= d;
					cdata[n + z * a.n_elements].i /= d;
				}
			}
				break;
			case I2: for (n = 0; n < a.n_elements; n++) {
				d = (double)a.i2data[n] * a.bscale + a.bzero;
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.n_elements].r /= d;
					cdata[n + z * a.n_elements].i /= d;
				}
			}
				break;
			case I4: for (n = 0; n < a.n_elements; n++) {
				d = (double)a.i4data[n] * a.bscale + a.bzero;
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.n_elements].r /= d;
					cdata[n + z * a.n_elements].i /= d;
				}
			}
				break;
            case I8: for (n = 0; n < a.n_elements; n++) {
                d = (double)a.i8data[n] * a.bscale + a.bzero;
                for (z = 0; z < naxis[naxis[0]]; z++) {
                    cdata[n + z * a.n_elements].r /= d;
                    cdata[n + z * a.n_elements].i /= d;
                }
            }
                break;
            case R4: for (n = 0; n < a.n_elements; n++) {
				d = (double)a.r4data[n];
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.n_elements].r /= d;
					cdata[n + z * a.n_elements].i /= d;
				}
			}
				break;
			case R8: for (n = 0; n < a.n_elements; n++) {
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.n_elements].r /= a.r8data[n];
					cdata[n + z * a.n_elements].i /= a.r8data[n];
				}
			}
				break;
			case C16: for (n = 0; n < a.n_elements; n++) {
				for (z = 0; z < naxis[naxis[0]]; z++) {
					cdata[n + z * a.n_elements] = Cdiv(cdata[n + z * a.n_elements], a.cdata[n]);
				}
			}
				break;
			default: break;
		}
			break;
		default: break;
	}
	return TRUE;
}

bool Fits::div3(const Fits &a) {
    dpint64 x, y, z;
    dpint64 n[4] = {1, 1, 1, 1};
    dpint64 i;
	
	// Check if we can do anything at all
	if (naxis[0] != a.naxis[0]) {
		return FALSE;
	}
	
	// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}
	
	// find smallest size
	for (i = 1; i <= Naxis(0); i++) {
		n[i] = Naxis(i);
		if (a.Naxis(i) < Naxis(i)) {
			n[i] = a.Naxis(i);
		}
		if (n[i] < 1) n[i] = 1;
	}
	
	double d;
	
	// Now do the real work
	switch (membits) {
		case R4: switch (a.membits) {
			case I1:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] /= (float)a.i1data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I2:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] /= (float)a.i2data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] /= (float)a.i4data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
            case I8:
#pragma omp parallel for private(y,z)
                for (x = 0; x < n[1]; x++) {
                    for (y = 0; y < n[2]; y++) {
                        for (z = 0; z < n[3]; z++) {
                            r4data[C_I(x, y, z)] /= (float)a.i8data[a.C_I(x, y, z)] * a.bscale + a.bzero;
                        }
                    }
                }
                break;
            case R4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r4data[C_I(x, y, z)] /= a.r4data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			default: break;
		}
			break;
		case R8: switch (a.membits) {
			case I1:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] /= (double)a.i1data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I2:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] /= (double)a.i2data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
			case I4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] /= (double)a.i4data[a.C_I(x, y, z)] * a.bscale + a.bzero;
						}
					}
				}
				break;
            case I8:
#pragma omp parallel for private(y,z)
                for (x = 0; x < n[1]; x++) {
                    for (y = 0; y < n[2]; y++) {
                        for (z = 0; z < n[3]; z++) {
                            r8data[C_I(x, y, z)] /= (double)a.i8data[a.C_I(x, y, z)] * a.bscale + a.bzero;
                        }
                    }
                }
                break;
            case R4:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] /= (double)a.r4data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			case R8:
#pragma omp parallel for private(y,z)
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							r8data[C_I(x, y, z)] /= a.r8data[a.C_I(x, y, z)];
						}
					}
				}
				break;
			default: break;
		}
			break;
		case C16: switch (a.membits) {
			case I1:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							d = (double)a.i1data[a.C_I(x, y, z)] * a.bscale + a.bzero;
							cdata[C_I(x, y, z)].r /= d;
							cdata[C_I(x, y, z)].i /= d;
						}
					}
				}
				break;
			case I2:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							d = (double)a.i2data[a.C_I(x, y, z)] * a.bscale + a.bzero;
							cdata[C_I(x, y, z)].r /= d;
							cdata[C_I(x, y, z)].i /= d;
						}
					}
				}
				break;
			case I4:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							d = (double)a.i4data[a.C_I(x, y, z)] * a.bscale + a.bzero;
							cdata[C_I(x, y, z)].r /= d;
							cdata[C_I(x, y, z)].i /= d;
						}
					}
				}
				break;
            case I8:
                for (x = 0; x < n[1]; x++) {
                    for (y = 0; y < n[2]; y++) {
                        for (z = 0; z < n[3]; z++) {
                            d = (double)a.i8data[a.C_I(x, y, z)] * a.bscale + a.bzero;
                            cdata[C_I(x, y, z)].r /= d;
                            cdata[C_I(x, y, z)].i /= d;
                        }
                    }
                }
                break;
            case R4:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							d = (double)a.r4data[a.C_I(x, y, z)];
							cdata[C_I(x, y, z)].r /= d;
							cdata[C_I(x, y, z)].i /= d;
						}
					}
				}
				break;
			case R8:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							d = a.r8data[a.C_I(x, y, z)];
							cdata[C_I(x, y, z)].r /= d;
							cdata[C_I(x, y, z)].i /= d;
						}
					}
				}
				break;
			case C16:
				for (x = 0; x < n[1]; x++) {
					for (y = 0; y < n[2]; y++) {
						for (z = 0; z < n[3]; z++) {
							cdata[C_I(x, y, z)] = Cdiv(cdata[C_I(x, y, z)], a.cdata[a.C_I(x, y, z)]);
						}
					}
				}
				break;
			default: break;
		}
			break;
		default: break;
	}
	return TRUE;
}


/*!
Raise one array to the power of another (Pixel by pixel). If the number of axes in the current array
is exactly one greater than in a, we will loop over the current array.
If membits of the current array is I1, I2, or I4, it will be changed to at least R4, or
to whatever a.membits is. This is in order to assure no loss in data.
*/
/*
bool Fits::power(const Fits & a) {
	long i;
	ULONG n;
	bool loop = FALSE;
	
// Check if we can do anything at all
	if (naxis[0] != a.naxis[0]) {
		if (naxis[0] != a.naxis[0] + 1) return reportError(FITS_ERROR_SIZE);
		for (i = 1; i < naxis[0]; i++) if (naxis[i] != a.naxis[i]) return reportError(FITS_ERROR_SIZE);
		loop = TRUE;
	} else {
		for (i = 1; i < naxis[0]; i++) if (naxis[i] != a.naxis[i]) return reportError(FITS_ERROR_SIZE);
	}

// The arrays are ok, next check membits
	if (membits > R4) {
		if (a.membits <= R4) {
			if (!setType(a.membits)) return FALSE;
		} else if (!setType(R4)) return FALSE;
	}
	if (membits > a.membits) {
		if (!setType(a.membits)) return FALSE;
	}

// Now do the real work
	if (loop) {
		float f;
		double d;
		dpComplex cr;
		long z;

		switch (membits) {
			case R4: switch (a.membits) {
				case I1: for (n = 0; n < a.n_elements; n++) {
					f = (float)a.i1data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] = pow(r4data[n + z * a.n_elements], f);
				}
					break;
				case I2: for (n = 0; n < a.n_elements; n++) {
					f = (float)a.i2data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] = pow(r4data[n + z * a.n_elements], f);
				}
					break;
				case I4: for (n = 0; n < a.n_elements; n++) {
					f = (float)a.i4data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] = pow(r4data[n + z * a.n_elements], f);
				}
					break;
				case R4: for (n = 0; n < a.n_elements; n++) {
					for (z = 0; z < naxis[naxis[0]]; z++) r4data[n + z * a.n_elements] = pow(r4data[n + z * a.n_elements], a.r4data[n]);
				}
					break;
				default: break;
			}
				break;
			case R8: switch (a.membits) {
				case I1: for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i1data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] = pow(r8data[n + z * a.n_elements], d);
				}
					break;
				case I2: for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i2data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] = pow(r8data[n + z * a.n_elements], d);
				}
					break;
				case I4: for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i4data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] = pow(r8data[n + z * a.n_elements], d);
				}
					break;
				case R4: for (n = 0; n < a.n_elements; n++) {
					d = (double)a.r4data[n];
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] = pow(r8data[n + z * a.n_elements], d);
				}
					break;
				case R8: for (n = 0; n < a.n_elements; n++) {
					for (z = 0; z < naxis[naxis[0]]; z++) r8data[n + z * a.n_elements] = pow(r8data[n + z * a.n_elements], a.r8data[n]);
				}
					break;
				default: break;
			}
				break;
			case C16: switch (a.membits) {
				case I1: for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i1data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) {
						cr = complex_pow(dpComplex(cdata[n + z * a.n_elements].r, cdata[n + z * a.n_elements].i), d);
						cdata[n + z * a.n_elements].r = cr.real();
						cdata[n + z * a.n_elements].i = cr.imag();
					}
				}
					break;
				case I2: for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i2data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) {
						cr = complex_pow(dpComplex(cdata[n + z * a.n_elements].r, cdata[n + z * a.n_elements].i), d);
						cdata[n + z * a.n_elements].r = cr.real();
						cdata[n + z * a.n_elements].i = cr.imag();
					}
				}
					break;
				case I4: for (n = 0; n < a.n_elements; n++) {
					d = (double)a.i4data[n] * a.bscale + a.bzero;
					for (z = 0; z < naxis[naxis[0]]; z++) {
						cr = complex_pow(dpComplex(cdata[n + z * a.n_elements].r, cdata[n + z * a.n_elements].i), d);
						cdata[n + z * a.n_elements].r = cr.real();
						cdata[n + z * a.n_elements].i = cr.imag();
					}
				}
					break;
				case R4: for (n = 0; n < a.n_elements; n++) {
					d = (double)a.r4data[n];
					for (z = 0; z < naxis[naxis[0]]; z++) {
						cr = complex_pow(dpComplex(cdata[n + z * a.n_elements].r, cdata[n + z * a.n_elements].i), d);
						cdata[n + z * a.n_elements].r = cr.real();
						cdata[n + z * a.n_elements].i = cr.imag();
					}
				}
					break;
				case R8: for (n = 0; n < a.n_elements; n++) {
					for (z = 0; z < naxis[naxis[0]]; z++) {
						cr = complex_pow(dpComplex(cdata[n + z * a.n_elements].r, cdata[n + z * a.n_elements].i), a.r8data[n]);
						cdata[n + z * a.n_elements].r = cr.real();
						cdata[n + z * a.n_elements].i = cr.imag();
					}
				}
					break;
				case C16: for (n = 0; n < a.n_elements; n++) {
					for (z = 0; z < naxis[naxis[0]]; z++) {
						cr = complex_pow(dpComplex(cdata[n + z * a.n_elements].r, cdata[n + z * a.n_elements].i), dpComplex(a.cdata[n].r, a.cdata[n].i));
						cdata[n + z * a.n_elements].r = cr.real();
						cdata[n + z * a.n_elements].i = cr.imag();
					}
				}
					break;
				default: break;
			}
				break;
			default: break;
		}
	} else {
		double d;
		dpComplex cr;

		switch (membits) {
			case R4: switch (a.membits) {
				case I1: 
					for (n = 0; n < n_elements; n++) 
						r4data[n] = pow(r4data[n], (float)a.i1data[n] * a.bscale + a.bzero);
					break;
				case I2: 
					for (n = 0; n < n_elements; n++) 
						r4data[n] = pow(r4data[n], (float)a.i2data[n] * a.bscale + a.bzero);
					break;
				case I4:
					for (n = 0; n < n_elements; n++) 
						r4data[n] = pow(r4data[n], (float)a.i4data[n] * a.bscale + a.bzero);
					break;
				case R4: 
					for (n = 0; n < n_elements; n++) 
						r4data[n] = pow(r4data[n], a.r4data[n]);
					break;
				default: break;
			}
				break;
			case R8: switch (a.membits) {
				case I1: 
					for (n = 0; n < n_elements; n++) 
						r8data[n] = pow(r8data[n], (double)a.i1data[n] * a.bscale + a.bzero);
					break;
				case I2:
					for (n = 0; n < n_elements; n++)
						r8data[n] = pow(r8data[n], (double)a.i2data[n] * a.bscale + a.bzero);
					break;
				case I4:
					for (n = 0; n < n_elements; n++)
						r8data[n] = pow(r8data[n], (double)a.i4data[n] * a.bscale + a.bzero);
					break;
				case R4:
					for (n = 0; n < n_elements; n++)
						r8data[n] = pow(r8data[n], (double)a.r4data[n]);
					break;
				case R8:
					for (n = 0; n < n_elements; n++)
						r8data[n] = pow(r8data[n], a.r8data[n]);
					break;
				default: break;
			}
				break;
			case C16: switch (a.membits) {
				case I1: for (n = 0; n < n_elements; n++) {
					d = (double)a.i1data[n] * a.bscale + a.bzero;
					cr = complex_pow(dpComplex(cdata[n].r, cdata[n].i), d);
					cdata[n].r = cr.real();
					cdata[n].i = cr.imag();
				}
					break;
				case I2: for (n = 0; n < n_elements; n++) {
					d = (double)a.i2data[n] * a.bscale + a.bzero;
					cr = complex_pow(dpComplex(cdata[n].r, cdata[n].i), d);
					cdata[n].r = cr.real();
					cdata[n].i = cr.imag();
				}
					break;
				case I4: for (n = 0; n < n_elements; n++) {
					d = (double)a.i4data[n] * a.bscale + a.bzero;
					cr = complex_pow(dpComplex(cdata[n].r, cdata[n].i), d);
					cdata[n].r = cr.real();
					cdata[n].i = cr.imag();
				}
					break;
				case R4: for (n = 0; n < n_elements; n++) {
					d = (double)a.r4data[n];
					cr = complex_pow(dpComplex(cdata[n].r, cdata[n].i), d);
					cdata[n].r = cr.real();
					cdata[n].i = cr.imag();
				}
					break;
				case R8: for (n = 0; n < n_elements; n++) {
					cr = complex_pow(dpComplex(cdata[n].r, cdata[n].i), a.r8data[n]);
					cdata[n].r = cr.real();
					cdata[n].i = cr.imag();
				}
					break;
				case C16: for (n = 0; n < n_elements; n++) {
					cr = complex_pow(dpComplex(cdata[n].r, cdata[n].i), dpComplex(a.cdata[n].r, a.cdata[n].i));
					cdata[n].r = cr.real();
					cdata[n].i = cr.imag();
				}
					break;
				default: break;
			}
				break;
			default: break;
		}
	}
	return TRUE;
}
*/
bool Fits::convol(const Fits & kernel) {
    dpint64 m, n, l, i, j, t, u;
	Fits a, k;

	if ((naxis[0] != 2) || (kernel.naxis[0] != 2)) {
		dp_output("Fits::convol: Both image and kernel must be 2-dimensional\n");
		return FALSE;
	}
	if (kernel.naxis[1] != kernel.naxis[2]) {
		dp_output("Fits::convol: Kernel must be square\n");
		return FALSE;
	}
	m = naxis[1];
	n = naxis[2];
	l = kernel.naxis[1];

	if ((l >= m) || (l >= n)) {
		dp_output("Fits::convol: kernel must be of smaller size than image\n");
		return FALSE;
	}

	setType(R4);
	a.copy(*this);
	k.copy(kernel);
	k.setType(R4);
	*this = 0.0;

	for (t = l/2; t < naxis[1] - l/2; t++) {
		for (u = l/2; u < naxis[2] - l/2; u++) {
			for (i = 0; i < l; i++) {
				for (j = 0; j < l; j++) {
					r4data[C_I(t, u)] += a.r4data[a.C_I(t+i-l/2, u+j-l/2)]*k.r4data[k.C_I(i,j)];
				}
			}
		}
	}

	return TRUE;
}

bool Fits::convolve(const Fits & a) {
	if ((a.naxis[1] < naxis[1]) || (a.naxis[2] != naxis[2])) return convol(a);

    dpint64 i;
    dpint64 n;

// Check if the two arrays match in size
	if (Nelements() != a.Nelements()) {
		dp_output("Fits::convolve: The two images must be of exactly the same size.\n");
		return FALSE;
	}
	if (naxis[0] != a.naxis[0]) {
		dp_output("Fits::convolve: The two images must be of exactly the same size.\n");
		return FALSE;
	}
	for (i = 1; i <= naxis[0]; i++) {
		if (naxis[i] != a.naxis[i]) {
			dp_output("Fits::convolve: The two images must be of exactly the same size.\n");
			return FALSE;
		}
	}

	Fits aa;
	if (!aa.copy(a)) return FALSE;
	if (!aa.fft()) return FALSE;
	if (!fft()) return FALSE;
	for (n = 0; n < n_elements; n++) cdata[n] = Cmul(cdata[n], aa.cdata[n]);
	if (!fft()) return FALSE;
	if (!reass()) return FALSE;
	for (n = 0; n < n_elements; n++) r4data[n] /= (float)n_elements;
	return TRUE;
}

Fits Fits::operator&(const Fits & a)
{
    dpint64 i;
	Fits aa, bb, c;
	
	aa.copy(a);
	bb.copy(*this);
	c.create(a.naxis[1], a.naxis[2]);
	aa.fft();
	bb.fft();
	for (i = 0; i < c.n_elements; i++) c.cdata[i] = Cmul(aa.cdata[i], bb.cdata[i]);
	c.fft();
	c.reass();
	for (i = 0; i < c.n_elements; i++) c.r4data[i] /= (float)(c.n_elements);
	
	return c;
}

/*!
Raise all array elements to the power of r. Array elements whose value is < 0 will be set to 0.
*/

Fits &Fits::operator^=(const float & r)
{
    power(r);

    return *this;
}

Fits &Fits::operator^=(const dpComplex & r)
{
    dpint64 i = 0;
    dpComplex cr;

    setType(C16);

    for (i = 0; i < n_elements; i++) {
        cr = complex_pow(dpComplex(cdata[i].r, cdata[i].i), r);
        cdata[i].r = cr.real();
        cdata[i].i = cr.imag();
    }

    return *this;
}

Fits &Fits::operator&=(const Fits & a)
{
    dpint64 i;
	Fits aa;
	
	aa.copy(a);
	aa.fft();
	fft();
	for (i = 0; i < n_elements; i++) cdata[i] = Cmul(aa.cdata[i], cdata[i]);
	fft();
	reass();
	for (i = 0; i < n_elements; i++) r4data[i] /= (float)n_elements;
	
	return *this;
}

bool Fits::matrix_mul(const Fits & a) {
//	if ((naxis[0] != 2) || (a.naxis[0] != 2)) {
//		printf("Fits::matrix_mul: must be 2-dimensional\n");
//		return FALSE;
//	}
	if (naxis[2] != a.naxis[1]) {
		dp_output("Fits::matrix_mul: The 2 matrices do not match\n");
		return FALSE;
	}

	Fits result;
	int i, j, k;
	long index;

	result.create(naxis[1], a.naxis[2], R8);
	for (i = 1; i <= naxis[1]; i++) {
		for (j = 1; j <= a.naxis[2]; j++) {
			index = result.F_I(i, j);
			for (k = 1; k <= naxis[2]; k++) {
				result.r8data[index] += ValueAt(F_I(i, k)) * a.ValueAt(a.F_I(k, j));
			}
		}
	}
	copy(result);

	return TRUE;
}
