//------------------------------------------------------------------------------
/*
    Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================

SETUP_LOG (STAmount)

uint64  STAmount::uRateOne  = STAmount::getRate (STAmount (1), STAmount (1));

bool STAmount::issuerFromString (uint160& uDstIssuer, const std::string& sIssuer)
{
    bool    bSuccess    = true;

    if (sIssuer.size () == (160 / 4))
    {
        uDstIssuer.SetHex (sIssuer);
    }
    else
    {
        RippleAddress raIssuer;

        if (raIssuer.setAccountID (sIssuer))
        {
            uDstIssuer  = raIssuer.getAccountID ();
        }
        else
        {
            bSuccess    = false;
        }
    }

    return bSuccess;
}

// --> sCurrency: "", "XRP", or three letter ISO code.
bool STAmount::currencyFromString (uint160& uDstCurrency, const std::string& sCurrency)
{
    bool    bSuccess    = true;

    if (sCurrency.empty () || !sCurrency.compare (SYSTEM_CURRENCY_CODE))
    {
        uDstCurrency.zero ();
    }
    else if (3 == sCurrency.size ())
    {
        Blob    vucIso (3);

        std::transform (sCurrency.begin (), sCurrency.end (), vucIso.begin (), ::toupper);

        // std::string  sIso;
        // sIso.assign(vucIso.begin(), vucIso.end());
        // Log::out() << "currency: " << sIso;

        Serializer  s;

        s.addZeros (96 / 8);
        s.addRaw (vucIso);
        s.addZeros (16 / 8);
        s.addZeros (24 / 8);

        s.get160 (uDstCurrency, 0);
    }
    else
    {
        bSuccess    = false;
    }

    return bSuccess;
}

// XXX Broken for custom currencies?
std::string STAmount::getHumanCurrency () const
{
    return createHumanCurrency (mCurrency);
}

bool STAmount::bSetJson (const Json::Value& jvSource)
{
    try
    {
        STAmount    saParsed (sfGeneric, jvSource);

        *this   = saParsed;

        return true;
    }
    catch (const std::exception& e)
    {
        WriteLog (lsINFO, STAmount)
                << boost::str (boost::format ("bSetJson(): caught: %s")
                               % e.what ());

        return false;
    }
}

STAmount::STAmount (SField::ref n, const Json::Value& v)
    : SerializedType (n), mValue (0), mOffset (0), mIsNegative (false)
{
    Json::Value value, currency, issuer;

    if (v.isObject ())
    {
        WriteLog (lsTRACE, STAmount)
                << boost::str (boost::format ("value='%s', currency='%s', issuer='%s'")
                               % v["value"].asString ()
                               % v["currency"].asString ()
                               % v["issuer"].asString ());

        value       = v["value"];
        currency    = v["currency"];
        issuer      = v["issuer"];
    }
    else if (v.isArray ())
    {
        value = v.get (Json::UInt (0), 0);
        currency = v.get (Json::UInt (1), Json::nullValue);
        issuer = v.get (Json::UInt (2), Json::nullValue);
    }
    else if (v.isString ())
    {
        std::string val = v.asString ();
        std::vector<std::string> elements;
        boost::split (elements, val, boost::is_any_of ("\t\n\r ,/"));

        if (elements.size () > 3)
            throw std::runtime_error ("invalid amount string");

        value = elements[0];

        if (elements.size () > 1)
            currency = elements[1];

        if (elements.size () > 2)
            issuer = elements[2];
    }
    else
        value = v;

    mIsNative = !currency.isString () || currency.asString ().empty () || (currency.asString () == SYSTEM_CURRENCY_CODE);

    if (mIsNative)
    {
        if (v.isObject ())
            throw std::runtime_error ("XRP may not be specified as an object");
    }
    else
    {
        // non-XRP
        if (!currencyFromString (mCurrency, currency.asString ()))
            throw std::runtime_error ("invalid currency");

        if (!issuer.isString ()
                || !issuerFromString (mIssuer, issuer.asString ()))
            throw std::runtime_error ("invalid issuer");

        if (mIssuer.isZero ())
            throw std::runtime_error ("invalid issuer");
    }

    if (value.isInt ())
    {
        if (value.asInt () >= 0)
            mValue = value.asInt ();
        else
        {
            mValue = -value.asInt ();
            mIsNegative = true;
        }

        canonicalize ();
    }
    else if (value.isUInt ())
    {
        mValue = v.asUInt ();

        canonicalize ();
    }
    else if (value.isString ())
    {
        if (mIsNative)
        {
            int64 val = lexicalCastThrow <int64> (value.asString ());

            if (val >= 0)
                mValue = val;
            else
            {
                mValue = -val;
                mIsNegative = true;
            }

            canonicalize ();
        }
        else
        {
            setValue (value.asString ());
        }
    }
    else
        throw std::runtime_error ("invalid amount type");
}

std::string STAmount::createHumanCurrency (const uint160& uCurrency)
{
    std::string sCurrency;

    if (uCurrency.isZero ())
    {
        return SYSTEM_CURRENCY_CODE;
    }
    else if (CURRENCY_ONE == uCurrency)
    {
        return "1";
    }
    else if (CURRENCY_BAD == uCurrency)
    {
        return uCurrency.ToString ();
    }
    else
    {
        Serializer  s (160 / 8);

        s.add160 (uCurrency);

        SerializerIterator  sit (s);

        Blob    vucZeros    = sit.getRaw (96 / 8);
        Blob    vucIso      = sit.getRaw (24 / 8);
        Blob    vucVersion  = sit.getRaw (16 / 8);
        Blob    vucReserved = sit.getRaw (24 / 8);

        bool    bIso    =    isZeroFilled (vucZeros.begin (), vucZeros.size ())            // Leading zeros
                          && isZeroFilled (vucVersion.begin (), vucVersion.size ())   // Zero version
                          && isZeroFilled (vucReserved.begin (), vucReserved.size ()); // Reserved is zero.

        if (bIso)
        {
            sCurrency.assign (vucIso.begin (), vucIso.end ());
        }
        else
        {
            sCurrency   = uCurrency.ToString ();
        }
    }

    return sCurrency;
}

bool STAmount::setValue (const std::string& sAmount)
{
    // Note: mIsNative and mCurrency must be set already!

    static boost::regex reNumber ("\\`([+-]?)(\\d*)(\\.(\\d*))?([eE]([+-]?)(\\d+))?\\'");
    boost::smatch smMatch;

    if (!boost::regex_match (sAmount, smMatch, reNumber))
    {
        WriteLog (lsWARNING, STAmount) << "Number not valid: \"" << sAmount << "\"";
        return false;
    }

    // Match fields: 0 = whole input, 1 = sign, 2 = integer portion, 3 = whole fraction (with '.')
    // 4 = fraction (without '.'), 5 = whole exponent (with 'e'), 6 = exponent sign, 7 = exponent number

    try
    {
        if ((smMatch[2].length () + smMatch[4].length ()) > 32)
        {
            WriteLog (lsWARNING, STAmount) << "Overlong number: " << sAmount;
            return false;
        }

        mIsNegative = (smMatch[1].matched && (smMatch[1] == "-"));

        if (!smMatch[4].matched) // integer only
        {
            mValue = lexicalCast <uint64> (std::string (smMatch[2]));
            mOffset = 0;
        }
        else
        {
            // integer and fraction
            mValue = lexicalCast <uint64> (smMatch[2] + smMatch[4]);
            mOffset = - (smMatch[4].length ());
        }

        if (smMatch[5].matched)
        {
            // we have an exponent
            if (smMatch[6].matched && (smMatch[6] == "-"))
                mOffset -= lexicalCast <int> (std::string (smMatch[7]));
            else
                mOffset += lexicalCast <int> (std::string (smMatch[7]));
        }
    }
    catch (...)
    {
        WriteLog (lsWARNING, STAmount) << "Number not parsed: \"" << sAmount << "\"";
        return false;
    }

    WriteLog (lsTRACE, STAmount) << "Float \"" << sAmount << "\" parsed to " << mValue << " : " << mOffset;

    if (mIsNative)
    {
        if (smMatch[3].matched)
            mOffset -= SYSTEM_CURRENCY_PRECISION;

        while (mOffset > 0)
        {
            mValue  *= 10;
            --mOffset;
        }

        while (mOffset < 0)
        {
            mValue  /= 10;
            ++mOffset;
        }
    }
    else
        canonicalize ();

    return true;
}

// Not meant to be the ultimate parser.  For use by RPC which is supposed to be sane and trusted.
// Native has special handling:
// - Integer values are in base units.
// - Float values are in float units.
// - To avoid a mistake float value for native are specified with a "^" in place of a "."
// <-- bValid: true = valid
bool STAmount::setFullValue (const std::string& sAmount, const std::string& sCurrency, const std::string& sIssuer)
{
    //
    // Figure out the currency.
    //
    if (!currencyFromString (mCurrency, sCurrency))
    {
        WriteLog (lsINFO, STAmount) << "Currency malformed: " << sCurrency;

        return false;
    }

    mIsNative   = !mCurrency;

    //
    // Figure out the issuer.
    //
    RippleAddress   naIssuerID;

    // Issuer must be "" or a valid account string.
    if (!naIssuerID.setAccountID (sIssuer))
    {
        WriteLog (lsINFO, STAmount) << "Issuer malformed: " << sIssuer;

        return false;
    }

    mIssuer = naIssuerID.getAccountID ();

    // Stamps not must have an issuer.
    if (mIsNative && !mIssuer.isZero ())
    {
        WriteLog (lsINFO, STAmount) << "Issuer specified for XRP: " << sIssuer;

        return false;
    }

    return setValue (sAmount);
}

// amount = value * [10 ^ offset]
// representation range is 10^80 - 10^(-80)
// on the wire, high 8 bits are (offset+142), low 56 bits are value
// value is zero if amount is zero, otherwise value is 10^15 to (10^16 - 1) inclusive

void STAmount::canonicalize ()
{
    if (mCurrency.isZero ())
    {
        // native currency amounts should always have an offset of zero
        mIsNative = true;

        if (mValue == 0)
        {
            mOffset = 0;
            mIsNegative = false;
            return;
        }

        while (mOffset < 0)
        {
            mValue /= 10;
            ++mOffset;
        }

        while (mOffset > 0)
        {
            mValue *= 10;
            --mOffset;
        }

        if (mValue > cMaxNative)
        {
            assert (false);
            throw std::runtime_error ("Native currency amount out of range");
        }

        return;
    }

    mIsNative = false;

    if (mValue == 0)
    {
        mOffset = -100;
        mIsNegative = false;
        return;
    }

    while ((mValue < cMinValue) && (mOffset > cMinOffset))
    {
        mValue *= 10;
        --mOffset;
    }

    while (mValue > cMaxValue)
    {
        if (mOffset >= cMaxOffset)
            throw std::runtime_error ("value overflow");

        mValue /= 10;
        ++mOffset;
    }

    if ((mOffset < cMinOffset) || (mValue < cMinValue))
    {
        mValue = 0;
        mOffset = 0;
        mIsNegative = false;
    }

    if (mOffset > cMaxOffset)
        throw std::runtime_error ("value overflow");

    assert ((mValue == 0) || ((mValue >= cMinValue) && (mValue <= cMaxValue)));
    assert ((mValue == 0) || ((mOffset >= cMinOffset) && (mOffset <= cMaxOffset)));
    assert ((mValue != 0) || (mOffset != -100));
}

void STAmount::add (Serializer& s) const
{
    if (mIsNative)
    {
        assert (mOffset == 0);

        if (!mIsNegative)
            s.add64 (mValue | cPosNative);
        else
            s.add64 (mValue);
    }
    else
    {
        if (isZero ())
            s.add64 (cNotNative);
        else if (mIsNegative) // 512 = not native
            s.add64 (mValue | (static_cast<uint64> (mOffset + 512 + 97) << (64 - 10)));
        else // 256 = positive
            s.add64 (mValue | (static_cast<uint64> (mOffset + 512 + 256 + 97) << (64 - 10)));

        s.add160 (mCurrency);
        s.add160 (mIssuer);
    }
}

STAmount STAmount::createFromInt64 (SField::ref name, int64 value)
{
    return value >= 0
           ? STAmount (name, static_cast<uint64> (value), false)
           : STAmount (name, static_cast<uint64> (-value), true);
}

void STAmount::setValue (const STAmount& a)
{
    mCurrency   = a.mCurrency;
    mIssuer     = a.mIssuer;
    mValue      = a.mValue;
    mOffset     = a.mOffset;
    mIsNative   = a.mIsNative;
    mIsNegative = a.mIsNegative;
}

int STAmount::compare (const STAmount& a) const
{
    // Compares the value of a to the value of this STAmount, amounts must be comparable
    if (mIsNegative != a.mIsNegative) return mIsNegative ? -1 : 1;

    if (!mValue)
    {
        if (a.mIsNegative) return 1;

        return a.mValue ? -1 : 0;
    }

    if (!a.mValue) return 1;

    if (mOffset > a.mOffset) return mIsNegative ? -1 : 1;

    if (mOffset < a.mOffset) return mIsNegative ? 1 : -1;

    if (mValue > a.mValue) return mIsNegative ? -1 : 1;

    if (mValue < a.mValue) return mIsNegative ? 1 : -1;

    return 0;
}

STAmount* STAmount::construct (SerializerIterator& sit, SField::ref name)
{
    uint64 value = sit.get64 ();

    if ((value & cNotNative) == 0)
    {
        // native
        if ((value & cPosNative) != 0)
            return new STAmount (name, value & ~cPosNative, false); // positive
        else if (value == 0)
            throw std::runtime_error ("negative zero is not canonical");

        return new STAmount (name, value, true); // negative
    }

    uint160 uCurrencyID = sit.get160 ();

    if (!uCurrencyID)
        throw std::runtime_error ("invalid non-native currency");

    uint160 uIssuerID = sit.get160 ();

    int offset = static_cast<int> (value >> (64 - 10)); // 10 bits for the offset, sign and "not native" flag
    value &= ~ (1023ull << (64 - 10));

    if (value)
    {
        bool isNegative = (offset & 256) == 0;
        offset = (offset & 255) - 97; // center the range

        if ((value < cMinValue) || (value > cMaxValue) || (offset < cMinOffset) || (offset > cMaxOffset))
            throw std::runtime_error ("invalid currency value");

        return new STAmount (name, uCurrencyID, uIssuerID, value, offset, isNegative);
    }

    if (offset != 512)
        throw std::runtime_error ("invalid currency value");

    return new STAmount (name, uCurrencyID, uIssuerID);
}

int64 STAmount::getSNValue () const
{
    // signed native value
    if (!mIsNative) throw std::runtime_error ("not native");

    if (mIsNegative) return - static_cast<int64> (mValue);

    return static_cast<int64> (mValue);
}

void STAmount::setSNValue (int64 v)
{
    if (!mIsNative) throw std::runtime_error ("not native");

    if (v > 0)
    {
        mIsNegative = false;
        mValue = static_cast<uint64> (v);
    }
    else
    {
        mIsNegative = true;
        mValue = static_cast<uint64> (-v);
    }
}

std::string STAmount::getRaw () const
{
    // show raw internal form
    if (mValue == 0) return "0";

    if (mIsNative)
    {
        if (mIsNegative) return std::string ("-") + lexicalCast <std::string> (mValue);
        else return lexicalCast <std::string> (mValue);
    }

    if (mIsNegative)
        return mCurrency.GetHex () + ": -" +
               lexicalCast <std::string> (mValue) + "e" + lexicalCast <std::string> (mOffset);
    else return mCurrency.GetHex () + ": " +
                    lexicalCast <std::string> (mValue) + "e" + lexicalCast <std::string> (mOffset);
}

std::string STAmount::getText () const
{
    // keep full internal accuracy, but make more human friendly if posible
    if (isZero ()) return "0";

    if (mIsNative)
    {
        if (mIsNegative)
            return std::string ("-") +  lexicalCast <std::string> (mValue);
        else return lexicalCast <std::string> (mValue);
    }

    if ((mOffset != 0) && ((mOffset < -25) || (mOffset > -5)))
    {
        if (mIsNegative)
            return std::string ("-") + lexicalCast <std::string> (mValue) +
                   "e" + lexicalCast <std::string> (mOffset);
        else
            return lexicalCast <std::string> (mValue) + "e" + lexicalCast <std::string> (mOffset);
    }

    std::string val = "000000000000000000000000000";
    val += lexicalCast <std::string> (mValue);
    val += "00000000000000000000000";

    std::string pre = val.substr (0, mOffset + 43);
    std::string post = val.substr (mOffset + 43);

    size_t s_pre = pre.find_first_not_of ('0');

    if (s_pre == std::string::npos)
        pre = "0";
    else
        pre = pre.substr (s_pre);

    size_t s_post = post.find_last_not_of ('0');

    if (mIsNegative) pre = std::string ("-") + pre;

    if (s_post == std::string::npos)
        return pre;
    else
        return pre + "." + post.substr (0, s_post + 1);
}

bool STAmount::isComparable (const STAmount& t) const
{
    // are these two STAmount instances in the same currency
    if (mIsNative) return t.mIsNative;

    if (t.mIsNative) return false;

    return mCurrency == t.mCurrency;
}

bool STAmount::isEquivalent (const SerializedType& t) const
{
    const STAmount* v = dynamic_cast<const STAmount*> (&t);

    if (!v) return false;

    return isComparable (*v) && (mIsNegative == v->mIsNegative) && (mValue == v->mValue) && (mOffset == v->mOffset);
}

void STAmount::throwComparable (const STAmount& t) const
{
    // throw an exception if these two STAmount instances are incomparable
    if (!isComparable (t))
        throw std::runtime_error ("amounts are not comparable");
}

bool STAmount::operator== (const STAmount& a) const
{
    return isComparable (a) && (mIsNegative == a.mIsNegative) && (mOffset == a.mOffset) && (mValue == a.mValue);
}

bool STAmount::operator!= (const STAmount& a) const
{
    return (mOffset != a.mOffset) || (mValue != a.mValue) || (mIsNegative != a.mIsNegative) || !isComparable (a);
}

bool STAmount::operator< (const STAmount& a) const
{
    throwComparable (a);
    return compare (a) < 0;
}

bool STAmount::operator> (const STAmount& a) const
{
    throwComparable (a);
    return compare (a) > 0;
}

bool STAmount::operator<= (const STAmount& a) const
{
    throwComparable (a);
    return compare (a) <= 0;
}

bool STAmount::operator>= (const STAmount& a) const
{
    throwComparable (a);
    return compare (a) >= 0;
}

STAmount& STAmount::operator+= (const STAmount& a)
{
    *this = *this + a;
    return *this;
}

STAmount& STAmount::operator-= (const STAmount& a)
{
    *this = *this - a;
    return *this;
}

STAmount STAmount::operator- (void) const
{
    if (mValue == 0) return *this;

    return STAmount (getFName (), mCurrency, mIssuer, mValue, mOffset, mIsNative, !mIsNegative);
}

STAmount& STAmount::operator= (uint64 v)
{
    // does not copy name, does not change currency type
    mOffset = 0;
    mValue = v;
    mIsNegative = false;

    if (!mIsNative) canonicalize ();

    return *this;
}

STAmount& STAmount::operator+= (uint64 v)
{
    if (mIsNative)
        setSNValue (getSNValue () + static_cast<int64> (v));
    else *this += STAmount (mCurrency, v);

    return *this;
}

STAmount& STAmount::operator-= (uint64 v)
{
    if (mIsNative)
        setSNValue (getSNValue () - static_cast<int64> (v));
    else *this -= STAmount (mCurrency, v);

    return *this;
}

bool STAmount::operator< (uint64 v) const
{
    return getSNValue () < static_cast<int64> (v);
}

bool STAmount::operator> (uint64 v) const
{
    return getSNValue () > static_cast<int64> (v);
}

bool STAmount::operator<= (uint64 v) const
{
    return getSNValue () <= static_cast<int64> (v);
}

bool STAmount::operator>= (uint64 v) const
{
    return getSNValue () >= static_cast<int64> (v);
}

STAmount STAmount::operator+ (uint64 v) const
{
    return STAmount (getFName (), getSNValue () + static_cast<int64> (v));
}

STAmount STAmount::operator- (uint64 v) const
{
    return STAmount (getFName (), getSNValue () - static_cast<int64> (v));
}

STAmount::operator double () const
{
    // Does not keep the precise value. Not recommended
    if (!mValue)
        return 0.0;

    if (mIsNegative) return -1.0 * static_cast<double> (mValue) * pow (10.0, mOffset);

    return static_cast<double> (mValue) * pow (10.0, mOffset);
}

STAmount operator+ (const STAmount& v1, const STAmount& v2)
{
    v1.throwComparable (v2);

    if (v2.isZero ()) return v1;

    if (v1.isZero ())
    {
        // Result must be in terms of v1 currency and issuer.
        return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, v2.mValue, v2.mOffset, v2.mIsNegative);
    }

    if (v1.mIsNative)
        return STAmount (v1.getFName (), v1.getSNValue () + v2.getSNValue ());

    int ov1 = v1.mOffset, ov2 = v2.mOffset;
    int64 vv1 = static_cast<int64> (v1.mValue), vv2 = static_cast<int64> (v2.mValue);

    if (v1.mIsNegative) vv1 = -vv1;

    if (v2.mIsNegative) vv2 = -vv2;

    while (ov1 < ov2)
    {
        vv1 /= 10;
        ++ov1;
    }

    while (ov2 < ov1)
    {
        vv2 /= 10;
        ++ov2;
    }

    // this addition cannot overflow an int64, it can overflow an STAmount and the constructor will throw

    int64 fv = vv1 + vv2;

    if ((fv >= -10) && (fv <= 10))
        return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer);
    else if (fv >= 0)
        return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, fv, ov1, false);
    else
        return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, -fv, ov1, true);
}

STAmount operator- (const STAmount& v1, const STAmount& v2)
{
    v1.throwComparable (v2);

    if (v2.isZero ()) return v1;

    if (v2.mIsNative)
    {
        // XXX This could be better, check for overflow and that maximum range is covered.
        return STAmount::createFromInt64 (v1.getFName (), v1.getSNValue () - v2.getSNValue ());
    }

    int ov1 = v1.mOffset, ov2 = v2.mOffset;
    int64 vv1 = static_cast<int64> (v1.mValue), vv2 = static_cast<int64> (v2.mValue);

    if (v1.mIsNegative) vv1 = -vv1;

    if (v2.mIsNegative) vv2 = -vv2;

    while (ov1 < ov2)
    {
        vv1 /= 10;
        ++ov1;
    }

    while (ov2 < ov1)
    {
        vv2 /= 10;
        ++ov2;
    }

    // this subtraction cannot overflow an int64, it can overflow an STAmount and the constructor will throw

    int64 fv = vv1 - vv2;

    if ((fv >= -10) && (fv <= 10))
        return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer);
    else if (fv >= 0)
        return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, fv, ov1, false);
    else
        return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, -fv, ov1, true);
}

STAmount STAmount::divide (const STAmount& num, const STAmount& den, const uint160& uCurrencyID, const uint160& uIssuerID)
{
    if (den.isZero ())
        throw std::runtime_error ("division by zero");

    if (num.isZero ())
        return STAmount (uCurrencyID, uIssuerID);

    uint64 numVal = num.mValue, denVal = den.mValue;
    int numOffset = num.mOffset, denOffset = den.mOffset;

    if (num.mIsNative)
        while (numVal < STAmount::cMinValue)
        {
            // Need to bring into range
            numVal *= 10;
            --numOffset;
        }

    if (den.mIsNative)
        while (denVal < STAmount::cMinValue)
        {
            denVal *= 10;
            --denOffset;
        }

    // Compute (numerator * 10^17) / denominator
    CBigNum v;

    if ((BN_add_word64 (&v, numVal) != 1) ||
            (BN_mul_word64 (&v, tenTo17) != 1) ||
            (BN_div_word64 (&v, denVal) == ((uint64) - 1)))
    {
        throw std::runtime_error ("internal bn error");
    }

    // 10^16 <= quotient <= 10^18
    assert (BN_num_bytes (&v) <= 64);

    return STAmount (uCurrencyID, uIssuerID, v.getuint64 () + 5,
                     numOffset - denOffset - 17, num.mIsNegative != den.mIsNegative);
}

STAmount STAmount::multiply (const STAmount& v1, const STAmount& v2, const uint160& uCurrencyID, const uint160& uIssuerID)
{
    if (v1.isZero () || v2.isZero ())
        return STAmount (uCurrencyID, uIssuerID);

    if (v1.mIsNative && v2.mIsNative && uCurrencyID.isZero ())
    {
        uint64 minV = (v1.getSNValue () < v2.getSNValue ()) ? v1.getSNValue () : v2.getSNValue ();
        uint64 maxV = (v1.getSNValue () < v2.getSNValue ()) ? v2.getSNValue () : v1.getSNValue ();

        if (minV > 3000000000ull) // sqrt(cMaxNative)
            throw std::runtime_error ("Native value overflow");

        if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32
            throw std::runtime_error ("Native value overflow");

        return STAmount (v1.getFName (), minV * maxV);
    }

    uint64 value1 = v1.mValue, value2 = v2.mValue;
    int offset1 = v1.mOffset, offset2 = v2.mOffset;

    if (v1.mIsNative)
    {
        while (value1 < STAmount::cMinValue)
        {
            value1 *= 10;
            --offset1;
        }
    }

    if (v2.mIsNative)
    {
        while (value2 < STAmount::cMinValue)
        {
            value2 *= 10;
            --offset2;
        }
    }

    // Compute (numerator * denominator) / 10^14 with rounding
    // 10^16 <= result <= 10^18
    CBigNum v;

    if ((BN_add_word64 (&v, value1) != 1) ||
            (BN_mul_word64 (&v, value2) != 1) ||
            (BN_div_word64 (&v, tenTo14) == ((uint64) - 1)))
    {
        throw std::runtime_error ("internal bn error");
    }

    // 10^16 <= product <= 10^18
    assert (BN_num_bytes (&v) <= 64);

    return STAmount (uCurrencyID, uIssuerID, v.getuint64 () + 7, offset1 + offset2 + 14,
                     v1.mIsNegative != v2.mIsNegative);
}

// Convert an offer into an index amount so they sort by rate.
// A taker will take the best, lowest, rate first.
// (e.g. a taker will prefer pay 1 get 3 over pay 1 get 2.
// --> offerOut: takerGets: How much the offerer is selling to the taker.
// -->  offerIn: takerPays: How much the offerer is receiving from the taker.
// <--    uRate: normalize(offerIn/offerOut)
//             A lower rate is better for the person taking the order.
//             The taker gets more for less with a lower rate.
// Zero is returned if the offer is worthless.
uint64 STAmount::getRate (const STAmount& offerOut, const STAmount& offerIn)
{
    if (offerOut.isZero ())
        return 0;

    try
    {
        STAmount r = divide (offerIn, offerOut, CURRENCY_ONE, ACCOUNT_ONE);

        if (r.isZero ()) // offer is too good
            return 0;

        assert ((r.getExponent () >= -100) && (r.getExponent () <= 155));

        uint64 ret = r.getExponent () + 100;

        return (ret << (64 - 8)) | r.getMantissa ();
    }
    catch (...)
    {
        // overflow -- very bad offer
        return 0;
    }
}

STAmount STAmount::setRate (uint64 rate)
{
    if (rate == 0)
        return STAmount (CURRENCY_ONE, ACCOUNT_ONE);

    uint64 mantissa = rate & ~ (255ull << (64 - 8));
    int exponent = static_cast<int> (rate >> (64 - 8)) - 100;

    return STAmount (CURRENCY_ONE, ACCOUNT_ONE, mantissa, exponent);
}

// Existing offer is on the books.
// Price is offer owner's, which might be better for taker.
// Taker pays what they can.
// Taker gets all taker can pay for with saTakerFunds/uTakerPaysRate, limited by saOfferPays and saOfferFunds/uOfferPaysRate.
// If taker is an offer, taker is spending at same or better rate than they wanted.
// Taker should consider themselves as wanting to buy X amount.
// Taker is willing to pay at most the rate of Y/X each.
// Buy semantics:
// - After having some part of their offer fulfilled at a better rate their offer should be reduced accordingly.
//
// There are no quality costs for offer vs offer taking.
//
// -->            bSell: True for sell semantics.
// -->   uTakerPaysRate: >= QUALITY_ONE | TransferRate for third party IOUs paid by taker.
// -->   uOfferPaysRate: >= QUALITY_ONE | TransferRate for third party IOUs paid by offer owner.
// -->      saOfferRate: Original saOfferGets/saOfferPays, when offer was made.
// -->     saOfferFunds: Limit for saOfferPays : How much can pay including fees.
// -->     saTakerFunds: Limit for saOfferGets : How much can pay including fees.
// -->      saOfferPays: Request : this should be reduced as the offer is fullfilled.
// -->      saOfferGets: Request : this should be reduced as the offer is fullfilled.
// -->      saTakerPays: Limit for taker to pay.
// -->      saTakerGets: Limit for taker to get.
// <--      saTakerPaid: Actual
// <--       saTakerGot: Actual
// <-- saTakerIssuerFee: Actual
// <-- saOfferIssuerFee: Actual
bool STAmount::applyOffer (
    const bool bSell,
    const uint32 uTakerPaysRate, const uint32 uOfferPaysRate,
    const STAmount& saOfferRate,
    const STAmount& saOfferFunds, const STAmount& saTakerFunds,
    const STAmount& saOfferPays, const STAmount& saOfferGets,
    const STAmount& saTakerPays, const STAmount& saTakerGets,
    STAmount& saTakerPaid, STAmount& saTakerGot,
    STAmount& saTakerIssuerFee, STAmount& saOfferIssuerFee)
{
    saOfferGets.throwComparable (saTakerFunds);

    assert (saOfferFunds.isPositive () && saTakerFunds.isPositive ()); // Both must have funds.
    assert (saOfferGets.isPositive () && saOfferPays.isPositive ()); // Must not be a null offer.

    // Available = limited by funds.
    // Limit offerer funds available, by transfer fees.
    STAmount    saOfferFundsAvailable   = QUALITY_ONE == uOfferPaysRate
                                          ? saOfferFunds          // As is.
                                          : STAmount::divide (saOfferFunds, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uOfferPaysRate, -9)); // Reduce by offer fees.

    WriteLog (lsINFO, STAmount) << "applyOffer: uOfferPaysRate=" << uOfferPaysRate;
    WriteLog (lsINFO, STAmount) << "applyOffer: saOfferFundsAvailable=" << saOfferFundsAvailable.getFullText ();

    // Limit taker funds available, by transfer fees.
    STAmount    saTakerFundsAvailable   = QUALITY_ONE == uTakerPaysRate
                                          ? saTakerFunds          // As is.
                                          : STAmount::divide (saTakerFunds, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9)); // Reduce by taker fees.

    WriteLog (lsINFO, STAmount) << "applyOffer: TAKER_FEES=" << STAmount (CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9).getFullText ();
    WriteLog (lsINFO, STAmount) << "applyOffer: uTakerPaysRate=" << uTakerPaysRate;
    WriteLog (lsINFO, STAmount) << "applyOffer: saTakerFundsAvailable=" << saTakerFundsAvailable.getFullText ();

    STAmount    saOfferPaysAvailable;   // Amount offer can pay out, limited by offer and offerer funds.
    STAmount    saOfferGetsAvailable;   // Amount offer would get, limited by offer funds.

    if (saOfferFundsAvailable >= saOfferPays)
    {
        // Offer was fully funded, avoid math shenanigans.

        saOfferPaysAvailable    = saOfferPays;
        saOfferGetsAvailable    = saOfferGets;
    }
    else
    {
        // Offer has limited funding, limit offer gets and pays by funds available.

        saOfferPaysAvailable    = saOfferFundsAvailable;
        saOfferGetsAvailable    = std::min (saOfferGets, mulRound (saOfferPaysAvailable, saOfferRate, saOfferGets, true));
    }

    WriteLog (lsINFO, STAmount) << "applyOffer: saOfferPaysAvailable=" << saOfferPaysAvailable.getFullText ();
    WriteLog (lsINFO, STAmount) << "applyOffer: saOfferGetsAvailable=" << saOfferGetsAvailable.getFullText ();

    STAmount    saTakerPaysAvailable    = std::min (saTakerPays, saTakerFundsAvailable);
    WriteLog (lsINFO, STAmount) << "applyOffer: saTakerPaysAvailable=" << saTakerPaysAvailable.getFullText ();

    // Limited = limited by other sides raw numbers.
    // Taker can't pay more to offer than offer can get.
    STAmount    saTakerPaysLimited      = std::min (saTakerPaysAvailable, saOfferGetsAvailable);
    WriteLog (lsINFO, STAmount) << "applyOffer: saTakerPaysLimited=" << saTakerPaysLimited.getFullText ();

    // Align saTakerGetsLimited with saTakerPaysLimited.
    STAmount    saTakerGetsLimited      = saTakerPaysLimited >= saOfferGetsAvailable              // Cannot actually be greater
                                          ? saOfferPaysAvailable                                  // Potentially take entire offer. Avoid math shenanigans.
                                          : std::min (saOfferPaysAvailable, divRound (saTakerPaysLimited, saOfferRate, saTakerGets, true)); // Take a portion of offer.

    WriteLog (lsINFO, STAmount) << "applyOffer: saOfferRate=" << saOfferRate.getFullText ();
    WriteLog (lsINFO, STAmount) << "applyOffer: saTakerGetsLimited=" << saTakerGetsLimited.getFullText ();

    // Got & Paid = Calculated by price and transfered without fees.
    // Compute from got as when !bSell, we want got to be exact to finish off offer if possible.

    saTakerGot  = bSell
                  ? saTakerGetsLimited                            // Get all available that are paid for.
                  : std::min (saTakerGets, saTakerGetsLimited);   // Limit by wanted.
    saTakerPaid = saTakerGot >= saTakerGetsLimited
                  ? saTakerPaysLimited
                  : std::min (saTakerPaysLimited, mulRound (saTakerGot, saOfferRate, saTakerFunds, true));

    WriteLog (lsINFO, STAmount) << "applyOffer: saTakerGot=" << saTakerGot.getFullText ();
    WriteLog (lsINFO, STAmount) << "applyOffer: saTakerPaid=" << saTakerPaid.getFullText ();

    if (uTakerPaysRate == QUALITY_ONE)
    {
        saTakerIssuerFee    = STAmount (saTakerPaid.getCurrency (), saTakerPaid.getIssuer ());
    }
    else
    {
        // Compute fees in a rounding safe way.

        STAmount    saTransferRate  = STAmount (CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9);
        WriteLog (lsINFO, STAmount) << "applyOffer: saTransferRate=" << saTransferRate.getFullText ();

        // TakerCost includes transfer fees.
        STAmount    saTakerCost     = STAmount::mulRound (saTakerPaid, saTransferRate, true);

        WriteLog (lsINFO, STAmount) << "applyOffer: saTakerCost=" << saTakerCost.getFullText ();
        WriteLog (lsINFO, STAmount) << "applyOffer: saTakerFunds=" << saTakerFunds.getFullText ();
        saTakerIssuerFee    = saTakerCost > saTakerFunds
                              ? saTakerFunds - saTakerPaid // Not enough funds to cover fee, stiff issuer the rounding error.
                              : saTakerCost - saTakerPaid;
        WriteLog (lsINFO, STAmount) << "applyOffer: saTakerIssuerFee=" << saTakerIssuerFee.getFullText ();
        assert (!saTakerIssuerFee.isNegative ());
    }

    if (uOfferPaysRate == QUALITY_ONE)
    {
        saOfferIssuerFee    = STAmount (saTakerGot.getCurrency (), saTakerGot.getIssuer ());
    }
    else
    {
        // Compute fees in a rounding safe way.
        STAmount    saOfferCost = STAmount::mulRound (saTakerGot, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uOfferPaysRate, -9), true);

        saOfferIssuerFee    = saOfferCost > saOfferFunds
                              ? saOfferFunds - saTakerGot // Not enough funds to cover fee, stiff issuer the rounding error.
                              : saOfferCost - saTakerGot;
    }

    WriteLog (lsINFO, STAmount) << "applyOffer: saTakerGot=" << saTakerGot.getFullText ();

    return saTakerGot >= saOfferPaysAvailable;              // True, if consumed offer.
}

STAmount STAmount::getPay (const STAmount& offerOut, const STAmount& offerIn, const STAmount& needed)
{
    // Someone wants to get (needed) out of the offer, how much should they pay in?
    if (offerOut.isZero ())
        return STAmount (offerIn.getCurrency (), offerIn.getIssuer ());

    if (needed >= offerOut)
    {
        // They need more than offered, pay full amount.
        return needed;
    }

    STAmount ret = divide (multiply (needed, offerIn, CURRENCY_ONE, ACCOUNT_ONE), offerOut, offerIn.getCurrency (), offerIn.getIssuer ());

    return (ret > offerIn) ? offerIn : ret;
}

STAmount STAmount::deserialize (SerializerIterator& it)
{
    UPTR_T<STAmount> s (dynamic_cast<STAmount*> (construct (it, sfGeneric)));
    STAmount ret (*s);
    return ret;
}

std::string STAmount::getFullText () const
{
    static const boost::format nativeFormat ("%s/" SYSTEM_CURRENCY_CODE);
    static const boost::format noIssuer ("%s/%s/0");
    static const boost::format issuerOne ("%s/%s/1");
    static const boost::format normal ("%s/%s/%s");

    if (mIsNative)
    {
        return str (boost::format (nativeFormat) % getText ());
    }
    else if (!mIssuer)
    {
        return str (boost::format (noIssuer) % getText () % getHumanCurrency ());
    }
    else if (mIssuer == ACCOUNT_ONE)
    {
        return str (boost::format (issuerOne) % getText () % getHumanCurrency ());
    }
    else
    {
        return str (boost::format (normal)
                    % getText ()
                    % getHumanCurrency ()
                    % RippleAddress::createHumanAccountID (mIssuer));
    }
}

STAmount STAmount::getRound () const
{
    if (mIsNative)
        return *this;

    uint64 valueDigits = mValue % 1000000000ull;

    if (valueDigits == 1)
        return STAmount (mCurrency, mIssuer, mValue - 1, mOffset, mIsNegative);
    else if (valueDigits == 999999999ull)
        return STAmount (mCurrency, mIssuer, mValue + 1, mOffset, mIsNegative);

    return *this;
}

void STAmount::roundSelf ()
{
    if (mIsNative)
        return;

    uint64 valueDigits = mValue % 1000000000ull;

    if (valueDigits == 1)
    {
        mValue -= 1;

        if (mValue < cMinValue)
            canonicalize ();
    }
    else if (valueDigits == 999999999ull)
    {
        mValue += 1;

        if (mValue > cMaxValue)
            canonicalize ();
    }
}

void STAmount::setJson (Json::Value& elem) const
{
    elem = Json::objectValue;

    if (!mIsNative)
    {
        // It is an error for currency or issuer not to be specified for valid json.

        elem["value"]       = getText ();
        elem["currency"]    = getHumanCurrency ();
        elem["issuer"]      = RippleAddress::createHumanAccountID (mIssuer);
    }
    else
    {
        elem = getText ();
    }
}

Json::Value STAmount::getJson (int) const
{
    Json::Value elem;
    setJson (elem);
    return elem;
}

//------------------------------------------------------------------------------

class STAmountTests : public UnitTest
{
public:
    STAmountTests () : UnitTest ("STAmount", "ripple")
    {
    }

    static STAmount serializeAndDeserialize (const STAmount& s)
    {
        Serializer ser;

        s.add (ser);

        SerializerIterator sit (ser);

        return STAmount::deserialize (sit);
    }

    //--------------------------------------------------------------------------

    bool roundTest (int n, int d, int m)
    {
        // check STAmount rounding
        STAmount num (CURRENCY_ONE, ACCOUNT_ONE, n);
        STAmount den (CURRENCY_ONE, ACCOUNT_ONE, d);
        STAmount mul (CURRENCY_ONE, ACCOUNT_ONE, m);
        STAmount quot = STAmount::divide (n, d, CURRENCY_ONE, ACCOUNT_ONE);
        STAmount res = STAmount::multiply (quot, mul, CURRENCY_ONE, ACCOUNT_ONE);

        expect (! res.isNative (), "Product should not be native");

        res.roundSelf ();

        STAmount cmp (CURRENCY_ONE, ACCOUNT_ONE, (n * m) / d);

        expect (! cmp.isNative (), "Comparison amount should not be native");

        if (res != cmp)
        {
            cmp.throwComparable (res);

            WriteLog (lsWARNING, STAmount) << "(" << num.getText () << "/" << den.getText () << ") X " << mul.getText () << " = "
                                       << res.getText () << " not " << cmp.getText ();

            fail ("Rounding");

            return false;
        }
        else
        {
            pass ();
        }
        
        return true;
    }

    void mulTest (int a, int b)
    {
        STAmount aa (CURRENCY_ONE, ACCOUNT_ONE, a);
        STAmount bb (CURRENCY_ONE, ACCOUNT_ONE, b);
        STAmount prod1 (STAmount::multiply (aa, bb, CURRENCY_ONE, ACCOUNT_ONE));

        expect (! prod1.isNative ());

        STAmount prod2 (CURRENCY_ONE, ACCOUNT_ONE, static_cast<uint64> (a) * static_cast<uint64> (b));

        if (prod1 != prod2)
        {
            WriteLog (lsWARNING, STAmount) << "nn(" << aa.getFullText () << " * " << bb.getFullText () << ") = " << prod1.getFullText ()
                                           << " not " << prod2.getFullText ();

            fail ("Multiplication result is not exact");
        }
        else
        {
            pass ();
        }

        aa = a;
        prod1 = STAmount::multiply (aa, bb, CURRENCY_ONE, ACCOUNT_ONE);

        if (prod1 != prod2)
        {
            WriteLog (lsWARNING, STAmount) << "n(" << aa.getFullText () << " * " << bb.getFullText () << ") = " << prod1.getFullText ()
                                           << " not " << prod2.getFullText ();
            fail ("Multiplication result is not exact");
        }
        else
        {
            pass ();
        }
    }

    //--------------------------------------------------------------------------

    void testSetValue ()
    {
        beginTestCase ("set value");

        STAmount    saTmp;

    #if 0
        // Check native floats
        saTmp.setFullValue ("1^0");
        BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS == saTmp.getNValue (), "float integer failed");
        saTmp.setFullValue ("0^1");
        BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS / 10 == saTmp.getNValue (), "float fraction failed");
        saTmp.setFullValue ("0^12");
        BOOST_CHECK_MESSAGE (12 * SYSTEM_CURRENCY_PARTS / 100 == saTmp.getNValue (), "float fraction failed");
        saTmp.setFullValue ("1^2");
        BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS + (2 * SYSTEM_CURRENCY_PARTS / 10) == saTmp.getNValue (), "float combined failed");
    #endif

        // Check native integer
        saTmp.setFullValue ("1");
        expect (1 == saTmp.getNValue (), "should be equal");
    }

    //--------------------------------------------------------------------------

    void testNativeCurrency ()
    {
        beginTestCase ("native currency");

        STAmount zero, one (1), hundred (100);

        unexpected (serializeAndDeserialize (zero) != zero, "STAmount fail");

        unexpected (serializeAndDeserialize (one) != one, "STAmount fail");

        unexpected (serializeAndDeserialize (hundred) != hundred, "STAmount fail");

        unexpected (!zero.isNative (), "STAmount fail");

        unexpected (!hundred.isNative (), "STAmount fail");

        unexpected (!zero.isZero (), "STAmount fail");

        unexpected (one.isZero (), "STAmount fail");

        unexpected (hundred.isZero (), "STAmount fail");

        unexpected ((zero < zero), "STAmount fail");

        unexpected (! (zero < one), "STAmount fail");

        unexpected (! (zero < hundred), "STAmount fail");

        unexpected ((one < zero), "STAmount fail");

        unexpected ((one < one), "STAmount fail");

        unexpected (! (one < hundred), "STAmount fail");

        unexpected ((hundred < zero), "STAmount fail");

        unexpected ((hundred < one), "STAmount fail");

        unexpected ((hundred < hundred), "STAmount fail");

        unexpected ((zero > zero), "STAmount fail");

        unexpected ((zero > one), "STAmount fail");

        unexpected ((zero > hundred), "STAmount fail");

        unexpected (! (one > zero), "STAmount fail");

        unexpected ((one > one), "STAmount fail");

        unexpected ((one > hundred), "STAmount fail");

        unexpected (! (hundred > zero), "STAmount fail");

        unexpected (! (hundred > one), "STAmount fail");

        unexpected ((hundred > hundred), "STAmount fail");

        unexpected (! (zero <= zero), "STAmount fail");

        unexpected (! (zero <= one), "STAmount fail");

        unexpected (! (zero <= hundred), "STAmount fail");

        unexpected ((one <= zero), "STAmount fail");

        unexpected (! (one <= one), "STAmount fail");

        unexpected (! (one <= hundred), "STAmount fail");

        unexpected ((hundred <= zero), "STAmount fail");

        unexpected ((hundred <= one), "STAmount fail");

        unexpected (! (hundred <= hundred), "STAmount fail");

        unexpected (! (zero >= zero), "STAmount fail");

        unexpected ((zero >= one), "STAmount fail");

        unexpected ((zero >= hundred), "STAmount fail");

        unexpected (! (one >= zero), "STAmount fail");

        unexpected (! (one >= one), "STAmount fail");

        unexpected ((one >= hundred), "STAmount fail");

        unexpected (! (hundred >= zero), "STAmount fail");

        unexpected (! (hundred >= one), "STAmount fail");

        unexpected (! (hundred >= hundred), "STAmount fail");

        unexpected (! (zero == zero), "STAmount fail");

        unexpected ((zero == one), "STAmount fail");

        unexpected ((zero == hundred), "STAmount fail");

        unexpected ((one == zero), "STAmount fail");

        unexpected (! (one == one), "STAmount fail");

        unexpected ((one == hundred), "STAmount fail");

        unexpected ((hundred == zero), "STAmount fail");

        unexpected ((hundred == one), "STAmount fail");

        unexpected (! (hundred == hundred), "STAmount fail");

        unexpected ((zero != zero), "STAmount fail");

        unexpected (! (zero != one), "STAmount fail");

        unexpected (! (zero != hundred), "STAmount fail");

        unexpected (! (one != zero), "STAmount fail");

        unexpected ((one != one), "STAmount fail");

        unexpected (! (one != hundred), "STAmount fail");

        unexpected (! (hundred != zero), "STAmount fail");

        unexpected (! (hundred != one), "STAmount fail");

        unexpected ((hundred != hundred), "STAmount fail");

        unexpected (STAmount ().getText () != "0", "STAmount fail");

        unexpected (STAmount (31).getText () != "31", "STAmount fail");

        unexpected (STAmount (310).getText () != "310", "STAmount fail");
    }

    //--------------------------------------------------------------------------

    void testCustomCurrency ()
    {
        beginTestCase ("custom currency");

        STAmount zero (CURRENCY_ONE, ACCOUNT_ONE), one (CURRENCY_ONE, ACCOUNT_ONE, 1), hundred (CURRENCY_ONE, ACCOUNT_ONE, 100);

        serializeAndDeserialize (one).getRaw ();

        unexpected (serializeAndDeserialize (zero) != zero, "STAmount fail");

        unexpected (serializeAndDeserialize (one) != one, "STAmount fail");

        unexpected (serializeAndDeserialize (hundred) != hundred, "STAmount fail");

        unexpected (zero.isNative (), "STAmount fail");

        unexpected (hundred.isNative (), "STAmount fail");

        unexpected (!zero.isZero (), "STAmount fail");

        unexpected (one.isZero (), "STAmount fail");

        unexpected (hundred.isZero (), "STAmount fail");

        unexpected ((zero < zero), "STAmount fail");

        unexpected (! (zero < one), "STAmount fail");

        unexpected (! (zero < hundred), "STAmount fail");

        unexpected ((one < zero), "STAmount fail");

        unexpected ((one < one), "STAmount fail");

        unexpected (! (one < hundred), "STAmount fail");

        unexpected ((hundred < zero), "STAmount fail");

        unexpected ((hundred < one), "STAmount fail");

        unexpected ((hundred < hundred), "STAmount fail");

        unexpected ((zero > zero), "STAmount fail");

        unexpected ((zero > one), "STAmount fail");

        unexpected ((zero > hundred), "STAmount fail");

        unexpected (! (one > zero), "STAmount fail");

        unexpected ((one > one), "STAmount fail");

        unexpected ((one > hundred), "STAmount fail");

        unexpected (! (hundred > zero), "STAmount fail");

        unexpected (! (hundred > one), "STAmount fail");

        unexpected ((hundred > hundred), "STAmount fail");

        unexpected (! (zero <= zero), "STAmount fail");

        unexpected (! (zero <= one), "STAmount fail");

        unexpected (! (zero <= hundred), "STAmount fail");

        unexpected ((one <= zero), "STAmount fail");

        unexpected (! (one <= one), "STAmount fail");

        unexpected (! (one <= hundred), "STAmount fail");

        unexpected ((hundred <= zero), "STAmount fail");

        unexpected ((hundred <= one), "STAmount fail");

        unexpected (! (hundred <= hundred), "STAmount fail");

        unexpected (! (zero >= zero), "STAmount fail");

        unexpected ((zero >= one), "STAmount fail");

        unexpected ((zero >= hundred), "STAmount fail");

        unexpected (! (one >= zero), "STAmount fail");

        unexpected (! (one >= one), "STAmount fail");

        unexpected ((one >= hundred), "STAmount fail");

        unexpected (! (hundred >= zero), "STAmount fail");

        unexpected (! (hundred >= one), "STAmount fail");

        unexpected (! (hundred >= hundred), "STAmount fail");

        unexpected (! (zero == zero), "STAmount fail");

        unexpected ((zero == one), "STAmount fail");

        unexpected ((zero == hundred), "STAmount fail");

        unexpected ((one == zero), "STAmount fail");

        unexpected (! (one == one), "STAmount fail");

        unexpected ((one == hundred), "STAmount fail");

        unexpected ((hundred == zero), "STAmount fail");

        unexpected ((hundred == one), "STAmount fail");

        unexpected (! (hundred == hundred), "STAmount fail");

        unexpected ((zero != zero), "STAmount fail");

        unexpected (! (zero != one), "STAmount fail");

        unexpected (! (zero != hundred), "STAmount fail");

        unexpected (! (one != zero), "STAmount fail");

        unexpected ((one != one), "STAmount fail");

        unexpected (! (one != hundred), "STAmount fail");

        unexpected (! (hundred != zero), "STAmount fail");

        unexpected (! (hundred != one), "STAmount fail");

        unexpected ((hundred != hundred), "STAmount fail");

        unexpected (STAmount (CURRENCY_ONE, ACCOUNT_ONE).getText () != "0", "STAmount fail");

        unexpected (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 31).getText () != "31", "STAmount fail");

        unexpected (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 31, 1).getText () != "310", "STAmount fail");

        unexpected (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 31, -1).getText () != "3.1", "STAmount fail");

        unexpected (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 31, -2).getText () != "0.31", "STAmount fail");

        unexpected (STAmount::multiply (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 20), STAmount (3), CURRENCY_ONE, ACCOUNT_ONE).getText () != "60",
            "STAmount multiply fail 1");

        unexpected (STAmount::multiply (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 20), STAmount (3), uint160 (), ACCOUNT_XRP).getText () != "60",
            "STAmount multiply fail 2");

        unexpected (STAmount::multiply (STAmount (20), STAmount (3), CURRENCY_ONE, ACCOUNT_ONE).getText () != "60",
            "STAmount multiply fail 3");

        unexpected (STAmount::multiply (STAmount (20), STAmount (3), uint160 (), ACCOUNT_XRP).getText () != "60",
            "STAmount multiply fail 4");

        if (STAmount::divide (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount (3), CURRENCY_ONE, ACCOUNT_ONE).getText () != "20")
        {
            WriteLog (lsFATAL, STAmount) << "60/3 = " <<
                                         STAmount::divide (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 60),
                                                 STAmount (3), CURRENCY_ONE, ACCOUNT_ONE).getText ();
            fail ("STAmount divide fail");
        }
        else
        {
            pass ();
        }

        unexpected (STAmount::divide (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount (3), uint160 (), ACCOUNT_XRP).getText () != "20",
            "STAmount divide fail");

        unexpected (STAmount::divide (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount (CURRENCY_ONE, ACCOUNT_ONE, 3), CURRENCY_ONE, ACCOUNT_ONE).getText () != "20",
            "STAmount divide fail");

        unexpected (STAmount::divide (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount (CURRENCY_ONE, ACCOUNT_ONE, 3), uint160 (), ACCOUNT_XRP).getText () != "20",
            "STAmount divide fail");

        STAmount a1 (CURRENCY_ONE, ACCOUNT_ONE, 60), a2 (CURRENCY_ONE, ACCOUNT_ONE, 10, -1);

        unexpected (STAmount::divide (a2, a1, CURRENCY_ONE, ACCOUNT_ONE) != STAmount::setRate (STAmount::getRate (a1, a2)),
            "STAmount setRate(getRate) fail");

        unexpected (STAmount::divide (a1, a2, CURRENCY_ONE, ACCOUNT_ONE) != STAmount::setRate (STAmount::getRate (a2, a1)),
            "STAmount setRate(getRate) fail");
    }

    //--------------------------------------------------------------------------

    void testArithmetic ()
    {
        beginTestCase ("arithmetic");

        CBigNum b;

        for (int i = 0; i < 16; ++i)
        {
            uint64 r = rand ();
            r <<= 32;
            r |= rand ();
            b.setuint64 (r);

            if (b.getuint64 () != r)
            {
                WriteLog (lsFATAL, STAmount) << r << " != " << b.getuint64 () << " " << b.ToString (16);
                fail ("setull64/getull64 failure");
            }
            else
            {
                pass ();
            }
        }

        // Test currency multiplication and division operations such as
        // convertToDisplayAmount, convertToInternalAmount, getRate, getClaimed, and getNeeded

        unexpected (STAmount::getRate (STAmount (1), STAmount (10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
            "STAmount getRate fail 1");

        unexpected (STAmount::getRate (STAmount (10), STAmount (1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
            "STAmount getRate fail 2");

        unexpected (STAmount::getRate (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 1), STAmount (CURRENCY_ONE, ACCOUNT_ONE, 10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
            "STAmount getRate fail 3");

        unexpected (STAmount::getRate (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 10), STAmount (CURRENCY_ONE, ACCOUNT_ONE, 1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
            "STAmount getRate fail 4");

        unexpected (STAmount::getRate (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 1), STAmount (10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
            "STAmount getRate fail 5");

        unexpected (STAmount::getRate (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 10), STAmount (1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
            "STAmount getRate fail 6");

        unexpected (STAmount::getRate (STAmount (1), STAmount (CURRENCY_ONE, ACCOUNT_ONE, 10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
            "STAmount getRate fail 7");

        unexpected (STAmount::getRate (STAmount (10), STAmount (CURRENCY_ONE, ACCOUNT_ONE, 1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
            "STAmount getRate fail 8");

        roundTest (1, 3, 3);
        roundTest (2, 3, 9);
        roundTest (1, 7, 21);
        roundTest (1, 2, 4);
        roundTest (3, 9, 18);
        roundTest (7, 11, 44);

        for (int i = 0; i <= 100000; ++i)
            mulTest (rand () % 10000000, rand () % 10000000);
    }

    //--------------------------------------------------------------------------

    void testUnderflow ()
    {
        beginTestCase ("underflow");

        STAmount bigNative (STAmount::cMaxNative / 2);
        STAmount bigValue (CURRENCY_ONE, ACCOUNT_ONE,
                           (STAmount::cMinValue + STAmount::cMaxValue) / 2, STAmount::cMaxOffset - 1);
        STAmount smallValue (CURRENCY_ONE, ACCOUNT_ONE,
                             (STAmount::cMinValue + STAmount::cMaxValue) / 2, STAmount::cMinOffset + 1);
        STAmount zero (CURRENCY_ONE, ACCOUNT_ONE, 0);

        STAmount smallXsmall = STAmount::multiply (smallValue, smallValue, CURRENCY_ONE, ACCOUNT_ONE);

        expect (smallXsmall.isZero (), "smallXsmall != 0");

        STAmount bigDsmall = STAmount::divide (smallValue, bigValue, CURRENCY_ONE, ACCOUNT_ONE);

        expect (bigDsmall.isZero (), String ("small/big != 0: ") + bigDsmall.getText ());

        bigDsmall = STAmount::divide (smallValue, bigNative, CURRENCY_ONE, uint160 ());

        expect (bigDsmall.isZero (), String ("small/bigNative != 0: ") + bigDsmall.getText ());

        bigDsmall = STAmount::divide (smallValue, bigValue, uint160 (), uint160 ());

        expect (bigDsmall.isZero (), String ("(small/big)->N != 0: ") + bigDsmall.getText ());

        bigDsmall = STAmount::divide (smallValue, bigNative, uint160 (), uint160 ());

        expect (bigDsmall.isZero (), String ("(small/bigNative)->N != 0: ") + bigDsmall.getText ());

        // very bad offer
        uint64 r = STAmount::getRate (smallValue, bigValue);

        expect (r == 0, "getRate(smallOut/bigIn) != 0");

        // very good offer
        r = STAmount::getRate (bigValue, smallValue);

        expect (r == 0, "getRate(smallIn/bigOUt) != 0");
    }

    //--------------------------------------------------------------------------

    void testRounding ()
    {
        // VFALCO TODO There are no actual tests here, just printed output?
        //             Change this to actually do something.

#if 0
        beginTestCase ("rounding ");

        uint64 value = 25000000000000000ull;
        int offset = -14;
        STAmount::canonicalizeRound (false, value, offset, true);

        STAmount one (CURRENCY_ONE, ACCOUNT_ONE, 1);
        STAmount two (CURRENCY_ONE, ACCOUNT_ONE, 2);
        STAmount three (CURRENCY_ONE, ACCOUNT_ONE, 3);

        STAmount oneThird1 = STAmount::divRound (one, three, CURRENCY_ONE, ACCOUNT_ONE, false);
        STAmount oneThird2 = STAmount::divide (one, three, CURRENCY_ONE, ACCOUNT_ONE);
        STAmount oneThird3 = STAmount::divRound (one, three, CURRENCY_ONE, ACCOUNT_ONE, true);
        WriteLog (lsINFO, STAmount) << oneThird1;
        WriteLog (lsINFO, STAmount) << oneThird2;
        WriteLog (lsINFO, STAmount) << oneThird3;

        STAmount twoThird1 = STAmount::divRound (two, three, CURRENCY_ONE, ACCOUNT_ONE, false);
        STAmount twoThird2 = STAmount::divide (two, three, CURRENCY_ONE, ACCOUNT_ONE);
        STAmount twoThird3 = STAmount::divRound (two, three, CURRENCY_ONE, ACCOUNT_ONE, true);
        WriteLog (lsINFO, STAmount) << twoThird1;
        WriteLog (lsINFO, STAmount) << twoThird2;
        WriteLog (lsINFO, STAmount) << twoThird3;

        STAmount oneA = STAmount::mulRound (oneThird1, three, CURRENCY_ONE, ACCOUNT_ONE, false);
        STAmount oneB = STAmount::multiply (oneThird2, three, CURRENCY_ONE, ACCOUNT_ONE);
        STAmount oneC = STAmount::mulRound (oneThird3, three, CURRENCY_ONE, ACCOUNT_ONE, true);
        WriteLog (lsINFO, STAmount) << oneA;
        WriteLog (lsINFO, STAmount) << oneB;
        WriteLog (lsINFO, STAmount) << oneC;

        STAmount fourThirdsA = STAmount::addRound (twoThird2, twoThird2, false);
        STAmount fourThirdsB = twoThird2 + twoThird2;
        STAmount fourThirdsC = STAmount::addRound (twoThird2, twoThird2, true);
        WriteLog (lsINFO, STAmount) << fourThirdsA;
        WriteLog (lsINFO, STAmount) << fourThirdsB;
        WriteLog (lsINFO, STAmount) << fourThirdsC;

        STAmount dripTest1 = STAmount::mulRound (twoThird2, two, uint160 (), uint160 (), false);
        STAmount dripTest2 = STAmount::multiply (twoThird2, two, uint160 (), uint160 ());
        STAmount dripTest3 = STAmount::mulRound (twoThird2, two, uint160 (), uint160 (), true);
        WriteLog (lsINFO, STAmount) << dripTest1;
        WriteLog (lsINFO, STAmount) << dripTest2;
        WriteLog (lsINFO, STAmount) << dripTest3;
#endif
    }

    //--------------------------------------------------------------------------

    void runTest ()
    {
        testSetValue ();
        testNativeCurrency ();
        testCustomCurrency ();
        testArithmetic ();
        testUnderflow ();
        testRounding ();
    }
};

static STAmountTests stAmountTests;
