/* @(#) money.h          Copymiddle 1991 Adolfo Di Mare  */
/*                                                       */
/*            Yet Another Money C++ Class                */
/*                                                       */
/*   Use freely but acknowledge author and publication.  */
/*    DO NOT SELL IT. The author reserves all rigths!    */

/*                               BITNET: adimare@UCRVM2  */

/* Compiler:                           Borland C++ v 2.0 */
/*                          [should work with Turbo C++] */


#ifndef _money_h
#define _money_h

extern "C" {     // avoid type clashes with the C library
    #include <math.h>    /* floor() */
    #include <float.h>   /* DBL_DIG */
}

#ifndef MONEY_DECIMALS       /* number of decimals in */
#define MONEY_DECIMALS  2    /*   any money quantity  */
#endif                       /* don't use parentesis! */

#define __VAL(n)  n               /* 1 level indirection */
#define TENPOW(n) __VAL(1.0e##n)  /* Trick to yield 10^n */


#define MONEY_DIG   DBL_DIG


class money {
public:

    static int    decimals() { return MONEY_DECIMALS;     }
    static int    digits()   { return MONEY_DIG; }
    static double SCALE()
        { return TENPOW(MONEY_DECIMALS); }

    money();              // do nothing constructor
    money(double);        // constructor from double
    money(const money&);  // copy constructor

    money& operator= (const money&);  // copy operator
    money& operator= (double);        // copy from double
    operator double() const;          // convert to double

    int  OK()   const;  // check money's invariant
    void FIX();         // get rid of unwanted decimals


    friend money  operator + (const money&, const money&);
    friend money  operator + (double,       const money&);
    friend money  operator + (const money&, double);
    friend money  operator - (const money&, const money&);
    friend money  operator - (double,       const money&);
    friend money  operator - (const money&, double);

    friend money  operator*  (const money&, double);
    friend money  operator*  (double,       const money&);
    friend double operator/  (const money&, const money&);
    friend money  operator/  (const money&, double);
    friend money  operator%  (const money&, const money&);

    // money  * money  is NOT valid
    // double / money  is INVALID

    friend int operator == (const money&, const money&);
    friend int operator != (const money&, const money&);
    friend int operator <  (const money&, const money&);
    friend int operator >  (const money&, const money&);
    friend int operator <= (const money&, const money&);
    friend int operator >= (const money&, const money&);

    money& operator += (const money&);
    money& operator += (double);
    money& operator -= (const money&);
    money& operator -= (double);

    money& operator *= (double);
    money& operator /= (double);

    friend money  operator+ (const money&);
    friend money  operator- (const money&);
    money& operator++();
    money& operator--();
    friend int    operator! (const money&);

    friend money abs(const money&);
    friend money flatten(
         const money& m,
         double cents=0.25, int rounding = 1 /* TRUE */);

protected:         // let users change the class behaviour
    double m_money;
};

// Constructors && assignment
inline money::money() {
// do nothing constructor, for efficiency
}
inline money::money(double d) {
// construct from double
    m_money = d*SCALE();
    FIX();
}
inline money::money(const money& m) {
// copy constructor
    m_money = m.m_money;
}

inline money& money::operator= (const money& m) {
// copy operator
    m_money = m.m_money;
    return *this;
}
inline money& money::operator= (double d) {
// assign from double
    m_money = d*SCALE();
    FIX();
    return *this;
}
inline money::operator double() const {
// convert to double
    return m_money / SCALE();
}

inline int money::OK() const {
// Returns TRUE (1) when the quantity stored
// in *this really corresponds to a money
// quantity.

    money temp;
    temp.m_money = m_money;
    temp.FIX();
    return (
        ( temp.m_money == m_money )
          &&
        ( fabs(m_money) < (TENPOW(DBL_DIG) / SCALE()) )
    );
}

inline void money::FIX() {
// Deletes all decimals digits beyond
// the MONEY_DECIMALS decimal place.
// - If the value is out of range, FIX
//   won't fix it.
    m_money =
        (m_money > 0.0
            ?
                floor(
                    m_money
                     #ifdef MONEY_ROUNDING
                        + 0.5 // 0.49 is also an option...
                     #endif
                 )
            :
                ceil(
                    m_money
                     #ifdef MONEY_ROUNDING
                        - 0.5
                     #endif
                 )
        );
}

// add
inline money operator+ (const money& m, const money& mm) {
    money temp;    // don't mult*SCALE()
    temp.m_money = m.m_money + mm.m_money;
    return temp;
}
inline money operator+ (double d, const money& m) {
    return (money(d)+m);
}
inline money operator+ (const money& m, double d) {
    return (m+money(d));
}

// substract
inline money operator- (const money& m, const money& mm) {
    money temp;
    temp.m_money = m.m_money - mm.m_money;
    return temp;
}
inline money operator- (double d, const money& m) {
    return (money(d)-m);
}
inline money operator- (const money& m, double d) {
    return (m-money(d));
}

// multiply
inline money operator* (const money& m, double d) {
    money temp;
    temp.m_money = m.m_money * d; // don't mult by SCALE()
    temp.FIX();    // this could be delayed...
    return temp;
}
inline money operator* (double d, const money& m) {
    return (m*d);
}

// divide
inline double operator/ (const money& m, const money& mm) {
    return m.m_money / mm.m_money;
}
inline money  operator/ (const money& m, double d) {
    money temp;
    temp.m_money = m.m_money / d;
    temp.FIX();    // this could be delayed...
    return temp;
}
inline money operator%  (const money& m, const money& mm) {
    money temp;
    temp.m_money = fmod(m.m_money, mm.m_money);
    temp.FIX();    // this could be delayed...
    return temp;
}

// compare
inline int operator  == (const money& m, const money& mm) {
    return m.m_money ==  mm.m_money;
}
inline int operator  != (const money& m, const money& mm) {
    return m.m_money !=  mm.m_money;
}
inline int operator  <  (const money& m, const money& mm) {
    return m.m_money <   mm.m_money;
}
inline int operator  >  (const money& m, const money& mm) {
    return m.m_money >   mm.m_money;
}
inline int operator  <= (const money& m, const money& mm) {
    return m.m_money <=  mm.m_money;
}
inline int operator  >= (const money& m, const money& mm) {
    return m.m_money >=  mm.m_money;
}

inline money& money::operator += (const money& m) {
    m_money += m.m_money;
    return *this;
}
inline money& money::operator += (double d) {
    m_money += d*SCALE();
    FIX();
    return *this;
}
inline money& money::operator -= (const money& m) {
    m_money -= m.m_money;
    return *this;
}
inline money& money::operator -= (double d) {
    m_money -= d*SCALE();
    FIX();
    return *this;
}
inline money& money::operator *= (double d) {
    m_money *= d;
    FIX();
    return *this;
}
inline money& money::operator /= (double d) {
    m_money /= d;
    FIX();
    return *this;
}

// unary op's
inline money operator+(const money& m) {
    return m;
}
inline money operator-(const money& m) {
    money temp;
    temp.m_money = -m.m_money;
    return temp;
}
inline money& money::operator++() {
    m_money += SCALE();
    #if (MONEY_DECIMALS<0)
        FIX(); // avoid problems because of
    #endif     // the representation of 10^-n
    return *this;
}
inline money& money::operator--() {
    m_money -= SCALE();
    #if (MONEY_DECIMALS<0)
        FIX();
    #endif
    return *this;
}
inline int operator!(const money& m) {
    return m.m_money == 0.0;
}

inline money abs(const money& m) {
    money temp;
    temp.m_money = fabs(m.m_money);
    return temp;
}

money flatten(const money& m, double cents, int rounding) {
// Returns a money data item where the cents are
// rounded modulo "cents". In this way cents can
// be stripped of money items when the currency
// does not have all the coins required to pay
// every posible quantity.
    money temp;
    double c = floor(fabs(cents*money::SCALE())); // cents
    double r = fmod(m.m_money, c);            // remainder
    temp.m_money =
        (!rounding || (2.0* r <= c)
            ? m.m_money - r
            : m.m_money - r + c
        );
    return temp;
}

/* Avoid name space overcrowding */
#undef __VAL
#undef TENPOW /* jic: Just In Case! */

#endif  /* _money_h */
