/* (c) Hundredsoft Corporation 2010-2012 All rights reserved.

    Ldouble.h -  VC 拡張倍精度クラス

*/

#ifndef LDOUBLE_INCLUDED
#define LDOUBLE_INCLUDED

#include <memory.h>

typedef struct {
    unsigned char d[16];
} LONG_DOUBLE_STR, *LPLONG_DOUBLE_STR;

extern "C" void Ldouble_init(void);
extern "C" double LdoubleToDouble(LONG_DOUBLE_STR* m);

extern "C" void Ldouble_int64(LONG_DOUBLE_STR* m, __int64* v);
extern "C" void Ldouble_int(LONG_DOUBLE_STR* m, int* v);
extern "C" void Ldouble_double(LONG_DOUBLE_STR* m, double* v);
extern "C" void Ldouble_plus_eq(LONG_DOUBLE_STR* m, LONG_DOUBLE_STR* v);
extern "C" void Ldouble_minus_eq(LONG_DOUBLE_STR* m, LONG_DOUBLE_STR* v);
extern "C" void Ldouble_mul_eq(LONG_DOUBLE_STR* m, LONG_DOUBLE_STR* v);
extern "C" void Ldouble_div_eq(LONG_DOUBLE_STR* m, LONG_DOUBLE_STR* v);
extern "C" void Ldouble_rem_eq(LONG_DOUBLE_STR* m, LONG_DOUBLE_STR* v);
extern "C" void Ldouble_comp(LONG_DOUBLE_STR* m, LONG_DOUBLE_STR* v, int* sts);
extern "C" void Ldouble_frndint(LONG_DOUBLE_STR* m);
extern "C" void Ldouble_frnddec(LONG_DOUBLE_STR* m);
extern "C" void Ldouble_sin(LONG_DOUBLE_STR* m);
extern "C" void Ldouble_cos(LONG_DOUBLE_STR* m);
extern "C" void Ldouble_sincos(LONG_DOUBLE_STR* m, LONG_DOUBLE_STR* v);
extern "C" void Ldouble_tan(LONG_DOUBLE_STR* m);
extern "C" void Ldouble_atan(LONG_DOUBLE_STR* m);
extern "C" void Ldouble_atan2(LONG_DOUBLE_STR* m, LONG_DOUBLE_STR* v);
extern "C" void Ldouble_sqrt(LONG_DOUBLE_STR* m);
extern "C" void Ldouble_pi(LONG_DOUBLE_STR* m);
extern "C" void Ldouble_log(LONG_DOUBLE_STR* m);
extern "C" void Ldouble_log10(LONG_DOUBLE_STR* m);
extern "C" void Ldouble_exp(LONG_DOUBLE_STR* m, int* sign);
extern "C" void Ldouble_pow(LONG_DOUBLE_STR* m, LONG_DOUBLE_STR* v, int* sign);


class Ldouble
{
public:
    Ldouble() {
        memset(&m_data, 0, sizeof(m_data));
    }
    virtual ~Ldouble(){}

    Ldouble(const Ldouble& v) {
        if (this != &v){
            memcpy(&m_data, &v.m_data, sizeof(m_data));
        }
    }
    Ldouble(const __int64& v) {
        Ldouble_int64(&m_data, (__int64*)&v);
    }
    Ldouble(const unsigned __int64& v) {
        __int64 t = 0x8000000000000000;
        int i;
        for (i=63; i>=0; i--, t>>=1){
            if (v & t) break;
        }
        if (i < 0){
            memset(&m_data, 0, sizeof(m_data));
        }else{
            t = v;
            t <<= 63-i; // 仮数部
            int ex = i + 16383; // 指数部
            memcpy(&m_data, &t, sizeof(__int64));
            *(int*)&m_data.d[8] = ex;
        }
    }
    Ldouble(const int& v) {
        __int64 i = v;
        Ldouble_int64(&m_data, (__int64*)&i);
    }
    Ldouble(const unsigned int& v) {
        __int64 i = v;
        Ldouble_int64(&m_data, (__int64*)&i);
    }
    Ldouble(const short& v) {
        __int64 i = v;
        Ldouble_int64(&m_data, (__int64*)&i);
    }
    Ldouble(const unsigned short& v) {
        __int64 i = v;
        Ldouble_int64(&m_data, (__int64*)&i);
    }
    Ldouble(const char& v) {
        __int64 i = v;
        Ldouble_int64(&m_data, (__int64*)&i);
    }
    Ldouble(const unsigned char& v) {
        __int64 i = v;
        Ldouble_int64(&m_data, (__int64*)&i);
    }
    Ldouble(const double& v) {
        Ldouble_double(&m_data, (double*)&v);
    }
    Ldouble& operator=(const Ldouble& v){
        if (this != &v){
            memcpy(&m_data, &v.m_data, sizeof(m_data));
        }
        return *this;
    }

    // Unary -                                
    Ldouble operator-(){
        Ldouble v = *this;
        v.m_data.d[9] ^= 0x80;
        return v;
    }

    // Binary operator +=, -=, *=, /=
    Ldouble& operator+=(const Ldouble& v){
        Ldouble_plus_eq(&m_data, (LPLONG_DOUBLE_STR)&v.m_data);
        return *this;
    }
    Ldouble& operator-=(const Ldouble& v){
        Ldouble_minus_eq(&m_data, (LPLONG_DOUBLE_STR)&v.m_data);
        return *this;
    }
    Ldouble& operator*=(const Ldouble& v){
        Ldouble_mul_eq(&m_data, (LPLONG_DOUBLE_STR)&v.m_data);
        return *this;
    }
    Ldouble& operator/=(const Ldouble& v){
        Ldouble_div_eq(&m_data, (LPLONG_DOUBLE_STR)&v.m_data);
        return *this;
    }
    Ldouble& operator%=(const Ldouble& v){
        Ldouble_rem_eq(&m_data, (LPLONG_DOUBLE_STR)&v.m_data);
        return *this;
    }

    // compare
    // this <  v: -
    // this == v: 0
    // this >  v: +
    int comp(const Ldouble& v) const{
        int sts;
        Ldouble_comp((LPLONG_DOUBLE_STR)&m_data, (LPLONG_DOUBLE_STR)&v.m_data, &sts);
        return ((sts & 0x4100) - 1) ^ 0x3fff;
    }
    bool operator==(const Ldouble& v) const{
        return (comp(v) == 0);
    }
    bool operator!=(const Ldouble& v) const{
        return (comp(v) != 0);
    }
    bool operator<(const Ldouble& v) const{
        return (comp(v) < 0);
    }
    bool operator<=(const Ldouble& v) const{
        return (comp(v) <= 0);
    }
    bool operator>(const Ldouble& v) const{
        return (comp(v) > 0);
    }
    bool operator>=(const Ldouble& v) const{
        return (comp(v) >= 0);
    }

    bool operator==(const double& v) const{
        return (comp(v) == 0);
    }
    bool operator!=(const double& v) const{
        return (comp(v) != 0);
    }
    bool operator<(const double& v) const{
        return (comp(v) < 0);
    }
    bool operator<=(const double& v) const{
        return (comp(v) <= 0);
    }
    bool operator>(const double& v) const{
        return (comp(v) > 0);
    }
    bool operator>=(const double& v) const{
        return (comp(v) >= 0);
    }

    // cast
    operator double() const{
        return LdoubleToDouble((LPLONG_DOUBLE_STR)&m_data);
    }

    //friend Binary operator     +,-,*,/
    friend  const Ldouble operator+(const  Ldouble&a, const Ldouble&b){
        Ldouble p(a);
        p += b;
        return p;
    }
    friend  const Ldouble operator+(const  Ldouble&a, const double&b){
        Ldouble p(a);
        p += (Ldouble)b;
        return p;
    }
    friend  const Ldouble operator+(const  double&a, const Ldouble&b){
        Ldouble p(a);
        p += b;
        return p;
    }
    friend  const Ldouble operator-(const  Ldouble&a, const Ldouble&b){
        Ldouble p(a);
        p -= b;
        return p;
    }
    friend  const Ldouble operator-(const  Ldouble&a, const double&b){
        Ldouble p(a);
        p -= (Ldouble)b;
        return p;
    }
    friend  const Ldouble operator-(const  double&a, const Ldouble&b){
        Ldouble p(a);
        p -= b;
        return p;
    }
    friend  const Ldouble operator*(const  Ldouble&a, const Ldouble&b){
        Ldouble p(a);
        p *= b;
        return p;
    }
    friend  const Ldouble operator*(const  Ldouble&a, const double&b){
        Ldouble p(a);
        p *= (Ldouble)b;
        return p;
    }
    friend  const Ldouble operator*(const  double&a, const Ldouble&b){
        Ldouble p(a);
        p *= b;
        return p;
    }
    friend  const Ldouble operator/(const  Ldouble&a, const Ldouble&b){
        Ldouble p(a);
        p /= b;
        return p;
    }
    friend  const Ldouble operator/(const  Ldouble&a, const double&b){
        Ldouble p(a);
        p /= (Ldouble)b;
        return p;
    }
    friend  const Ldouble operator/(const  double&a, const Ldouble&b){
        Ldouble p(a);
        p /= b;
        return p;
    }
    friend  const Ldouble operator%(const  Ldouble&a, const Ldouble&b){
        Ldouble p(a);
        p %= b;
        return p;
    }
    friend  const Ldouble operator%(const  Ldouble&a, const double&b){
        Ldouble p(a);
        p %= (Ldouble)b;
        return p;
    }
    friend  const Ldouble operator%(const  double&a, const Ldouble&b){
        Ldouble p(a);
        p %= b;
        return p;
    }


    LONG_DOUBLE_STR    m_data;
};

namespace LD {
    Ldouble abs(const Ldouble& s){
        Ldouble v(s);
        v.m_data.d[9] &= 0x7f;
        return v;
    }

    Ldouble rndint(const Ldouble& s){
        Ldouble v(s);
        Ldouble_frndint(&v.m_data);
        return v;
    }

    Ldouble rnddec(const Ldouble& s){
        Ldouble v(s);
        Ldouble_frnddec(&v.m_data);
        return v;
    }

    Ldouble sin(const Ldouble& s){
        Ldouble v(s);
        Ldouble_sin(&v.m_data);
        return v;
    }

    Ldouble cos(const Ldouble& s){
        Ldouble v(s);
        Ldouble_cos(&v.m_data);
        return v;
    }

    void sincos(const Ldouble& s, Ldouble& sin, Ldouble& cos){
        sin = s;
        Ldouble_sincos(&sin.m_data, &cos.m_data);
    }

    Ldouble tan(const Ldouble& s){
        Ldouble v(s);
        Ldouble_tan(&v.m_data);
        return v;
    }

    Ldouble atan(const Ldouble& s){
        Ldouble v(s);
        Ldouble_atan(&v.m_data);
        return v;
    }

    Ldouble atan2(const Ldouble& x, const Ldouble& y){
        Ldouble v(y);
        Ldouble_atan2((LPLONG_DOUBLE_STR)&x.m_data, (LPLONG_DOUBLE_STR)&v.m_data);
        return v;
    }

    Ldouble sqrt(const Ldouble& s){
        Ldouble v(s);
        Ldouble_sqrt(&v.m_data);
        return v;
    }

    Ldouble pi(){
        Ldouble v;
        Ldouble_pi(&v.m_data);
        return v;
    }

    Ldouble log(const Ldouble& s){
        Ldouble v(s);
        Ldouble_log(&v.m_data);
        return v;
    }

    Ldouble log10(const Ldouble& s){
        Ldouble v(s);
        Ldouble_log10(&v.m_data);
        return v;
    }

    Ldouble exp(const Ldouble& s){
        Ldouble v(s);
        LPLONG_DOUBLE_STR m = &v.m_data;
        int sign = ((m->d[9] & 0x80) != 0) ? 1 : 0;
        m->d[9] &= 0x7f;

        Ldouble_exp(&v.m_data, &sign);
        return v;
    }

    Ldouble pow(const Ldouble& x, const Ldouble& y){
        Ldouble u(x),v(y);
        LPLONG_DOUBLE_STR mx = &u.m_data;
        LPLONG_DOUBLE_STR m = &v.m_data;
        int xsign = ((mx->d[9] & 0x80) != 0) ? 1 : 0;
        int ysign = ((m->d[9] & 0x80) != 0) ? 1 : 0;
        if (xsign){
            int ny = (int)v;
            Ldouble t = ny;
            if (t != y){
                throw "Invalid value.";
            }
            xsign = (ny & 1) ? 1 : 0;
        }
        mx->d[9] &= 0x7f;
        m->d[9] &= 0x7f;

        Ldouble_pow(mx, m, &ysign);

        if (xsign){
            m->d[9] |= 0x80;
        }
        return v;
    }
} // end of namespace

#endif