You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kmymoney/kmymoney2/mymoney/mymoneymoney.cpp

795 lines
22 KiB

/***************************************************************************
mymoneymymoney.cpp - description
-------------------
begin : Thu Feb 21 2002
copyright : (C) 2000-2002 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@users.sourceforge.net>
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
// make sure, that this is defined before we even include any other header file
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS // force definition of min and max values
#endif
// ----------------------------------------------------------------------------
// QT Includes
#include <tqregexp.h>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneymoney.h"
#include "mymoneyaccount.h"
#include "mymoneysecurity.h"
unsigned char MyMoneyMoney::_thousandSeparator = ',';
unsigned char MyMoneyMoney::_decimalSeparator = '.';
MyMoneyMoney::signPosition MyMoneyMoney::_negativeMonetarySignPosition = BeforeQuantityMoney;
MyMoneyMoney::signPosition MyMoneyMoney::_positiveMonetarySignPosition = BeforeQuantityMoney;
bool MyMoneyMoney::_negativePrefixCurrencySymbol = false;
bool MyMoneyMoney::_positivePrefixCurrencySymbol = false;
MyMoneyMoney::fileVersionE MyMoneyMoney::_fileVersion = MyMoneyMoney::FILE_4_BYTE_VALUE;
MyMoneyMoney MyMoneyMoney::maxValue = MyMoneyMoney(INT64_MAX,100);
MyMoneyMoney MyMoneyMoney::minValue = MyMoneyMoney(INT64_MIN,100);
MyMoneyMoney MyMoneyMoney::autoCalc = MyMoneyMoney(INT64_MIN+1,100);
void MyMoneyMoney::setNegativePrefixCurrencySymbol(const bool flag)
{
_negativePrefixCurrencySymbol = flag;
}
void MyMoneyMoney::setPositivePrefixCurrencySymbol(const bool flag)
{
_positivePrefixCurrencySymbol = flag;
}
void MyMoneyMoney::setNegativeMonetarySignPosition(const signPosition pos)
{
_negativeMonetarySignPosition = pos;
}
MyMoneyMoney::signPosition MyMoneyMoney::negativeMonetarySignPosition(void)
{
return _negativeMonetarySignPosition;
}
void MyMoneyMoney::setPositiveMonetarySignPosition(const signPosition pos)
{
_positiveMonetarySignPosition = pos;
}
MyMoneyMoney::signPosition MyMoneyMoney::positiveMonetarySignPosition(void)
{
return _positiveMonetarySignPosition;
}
void MyMoneyMoney::setThousandSeparator(const unsigned char separator)
{
if(separator != ' ')
_thousandSeparator = separator;
else
_thousandSeparator = 0;
}
unsigned char MyMoneyMoney::thousandSeparator(void)
{
return _thousandSeparator;
}
void MyMoneyMoney::setDecimalSeparator(const unsigned char separator)
{
if(separator != ' ')
_decimalSeparator = separator;
else
_decimalSeparator = 0;
}
unsigned char MyMoneyMoney::decimalSeparator(void)
{
return _decimalSeparator;
}
void MyMoneyMoney::setFileVersion(fileVersionE version)
{
_fileVersion = version;
}
MyMoneyMoney::MyMoneyMoney(const TQString& pszAmount)
{
m_num = 0;
m_denom = 1;
// an empty string is zero
if (pszAmount.isEmpty())
return;
// take care of prices given in the form "8 5/16"
// and our own internal represenation
TQRegExp regExp("^((\\d+)\\s+|-)?(\\d+)/(\\d+)");
// +-#2-+ +-#3-+ +-#4-+
// +-----#1-----+
if (regExp.search(pszAmount) > -1) {
m_num = regExp.cap(3).toLongLong();
m_denom = regExp.cap(4).toLongLong();
const TQString& part1 = regExp.cap(1);
if(!part1.isEmpty()) {
if(part1 == TQString("-")) {
m_num = -m_num;
} else {
*this += MyMoneyMoney(regExp.cap(2));
}
}
return;
}
TQString res = pszAmount;
// get rid of anything that is not
// a) numeric
// b) _decimalSeparator
// c) negative indicator
TQString validChars = TQString("\\d%1").tqarg(TQChar(decimalSeparator()));
// we need to escape the minus sign here, because later on it will be
// part of "\d,-()" and that does not work. It needs to be "\d,\-()"
// And we need two of them, because we're in C
TQString negChars("\\-");
if(_negativeMonetarySignPosition == ParensAround) {
// Since we want to allow '-' as well as '()' for negative entry
// we just add the parens here.
negChars += "()";
}
validChars += negChars;
// qDebug("0: '%s'", validChars.data());
TQRegExp invChars(TQString("[^%1]").tqarg(validChars));
// qDebug("1: '%s'", res.data());
res.remove(invChars);
TQRegExp negCharSet(TQString("[%1]").tqarg(negChars));
bool isNegative = false;
if(res.tqfind(negCharSet) != -1) {
isNegative = true;
res.remove(negCharSet);
}
// qDebug("2: '%s' %s", res.data(), isNegative ? "(-)" : "");
int pos;
// qDebug("3: '%s'", res.data());
if((pos = res.tqfind(_decimalSeparator)) != -1) {
// make sure, we get the denominator right
m_denom = precToDenom(res.length() - pos - 1);
// now remove the decimal symbol
res.remove(pos, 1);
}
// qDebug("4: '%s'", res.data());
if(res.length() > 0)
m_num = atoll( res );
if(isNegative)
m_num = -m_num;
}
TQString MyMoneyMoney::formatMoney(int denom, bool showThousandSeparator) const
{
return formatMoney("", denomToPrec(denom), showThousandSeparator);
}
TQString MyMoneyMoney::formatMoney(const MyMoneyAccount& acc, const MyMoneySecurity& sec, bool showThousandSeparator) const
{
return formatMoney(sec.tradingSymbol(), denomToPrec(acc.fraction()), showThousandSeparator);
}
TQString MyMoneyMoney::formatMoney(const MyMoneySecurity& sec, bool showThousandSeparator) const
{
return formatMoney(sec.tradingSymbol(), denomToPrec(sec.smallestAccountFraction()), showThousandSeparator);
}
TQString MyMoneyMoney::formatMoney(const TQString& currency, const int prec, bool showThousandSeparator) const
{
TQString res;
TQString tmpCurrency = currency;
int tmpPrec = prec;
signed64 denom = 1;
signed64 m_64Value;
// if prec == -1 we want the maximum possible but w/o trailing zeroes
if(tmpPrec > -1) {
while(tmpPrec--) {
denom *= 10;
}
} else {
// fix it to a max of 8 digits on the right side for now
denom = 100000000;
}
m_64Value = convert(denom).m_num;
// Once we really support multiple currencies then this method will
// be much better than using KGlobal::locale()->formatMoney.
bool bNegative = false;
signed64 left = m_64Value / denom;
signed64 right = m_64Value % denom;
if (right < 0){
right = -right;
bNegative = true;
}
if (left < 0) {
left = -left;
bNegative = true;
}
if(left & 0xFFFFFFFF00000000LL) {
signed64 tmp = left;
// TQString.sprintf("%Ld") did not work :-(, so I had to
// do it the old ugly way.
while(tmp) {
res.insert(0, TQString("%1").tqarg(static_cast<int>(tmp % 10)));
tmp /= 10;
}
} else
res = TQString("%1").tqarg((long)left);
if(showThousandSeparator) {
int pos = res.length();
while((0 < (pos -= 3)) && thousandSeparator())
res.insert(pos, thousandSeparator());
}
if(prec > 0 || (prec == -1 && right != 0)) {
if(decimalSeparator())
res += decimalSeparator();
// using
//
// res += TQString("%1").tqarg(right).rightJustify(prec, '0', true);
//
// caused some weird results if right was rather large. Eg: right being
// 666600000 should have appended a 0, but instead it prepended a 0. With
// res being "2," the result wasn't "2,6666000000" as expected, but rather
// "2,0666600000" which was not usable. The code below works for me.
TQString rs = TQString("%1").tqarg(right);
if(prec != -1)
rs = rs.rightJustify(prec, '0', true);
else {
rs = rs.rightJustify(8, '0', true);
// no trailing zeroes or decimal separators
while(rs.endsWith("0"))
rs.truncate(rs.length()-1);
while(rs.endsWith(TQChar(decimalSeparator())))
rs.truncate(rs.length()-1);
}
res += rs;
}
signPosition signpos = bNegative ? _negativeMonetarySignPosition : _positiveMonetarySignPosition;
TQString sign = bNegative ? "-" : "";
switch(signpos) {
case ParensAround:
res.prepend('(');
res.append(')');
break;
case BeforeQuantityMoney:
res.prepend(sign);
break;
case AfterQuantityMoney:
res.append(sign);
break;
case BeforeMoney:
tmpCurrency.prepend(sign);
break;
case AfterMoney:
tmpCurrency.append(sign);
break;
}
if(!tmpCurrency.isEmpty()) {
if(bNegative ? _negativePrefixCurrencySymbol : _positivePrefixCurrencySymbol){
res.prepend(' ');
res.prepend(tmpCurrency);
} else {
res.append(' ');
res.append(tmpCurrency);
}
}
return res;
}
const TQString MyMoneyMoney::toString(void) const
{
signed64 tmp = m_num < 0 ? - m_num : m_num;
TQString res;
TQString resf;
// TQString.sprintf("%Ld") did not work :-(, so I had to
// do it the old ugly way.
while(tmp) {
res.prepend(TQString("%1").tqarg(static_cast<int>(tmp % 10)));
tmp /= 10;
}
if(res.isEmpty())
res = TQString("0");
if(m_num < 0)
res.prepend('-');
tmp = m_denom;
while(tmp) {
resf.prepend(TQString("%1").tqarg(static_cast<int>(tmp % 10)));
tmp /= 10;
}
return res + "/" + resf;
}
TQDataStream &operator<<(TQDataStream &s, const MyMoneyMoney &_money)
{
// We WILL lose data here if the user has more than 2 billion pounds :-(
// QT defined it here as long:
// qglobal.h:typedef long TQ_INT64;
MyMoneyMoney money = _money.convert(100);
switch(MyMoneyMoney::_fileVersion) {
case MyMoneyMoney::FILE_4_BYTE_VALUE:
if(money.m_num & 0xffffffff00000000LL)
qWarning("Lost data while writing out MyMoneyMoney object using deprecated 4 byte writer");
s << static_cast<TQ_INT32> (money.m_num & 0xffffffff);
break;
default:
qDebug("Unknown file version while writing MyMoneyMoney object! Use FILE_8_BYTE_VALUE");
// tricky fall through here
case MyMoneyMoney::FILE_8_BYTE_VALUE:
s << static_cast<TQ_INT32> (money.m_num >> 32);
s << static_cast<TQ_INT32> (money.m_num & 0xffffffff);
break;
}
return s;
}
TQDataStream &operator>>(TQDataStream &s, MyMoneyMoney &money)
{
TQ_INT32 tmp;
switch(MyMoneyMoney::_fileVersion) {
case MyMoneyMoney::FILE_4_BYTE_VALUE:
s >> tmp;
money.m_num = static_cast<signed64> (tmp);
money.m_denom = 100;
break;
default:
qDebug("Unknown file version while writing MyMoneyMoney object! FILE_8_BYTE_VALUE assumed");
// tricky fall through here
case MyMoneyMoney::FILE_8_BYTE_VALUE:
s >> tmp;
money.m_num = static_cast<signed64> (tmp);
money.m_num <<= 32;
s >> tmp;
money.m_num |= static_cast<signed64> (tmp);
money.m_denom = 100;
break;
}
return s;
}
////////////////////////////////////////////////////////////////////////////////
// Name: operator+
// Purpose: Addition operator - adds the input amount to the object
// Returns: The current object
// Throws: Nothing.
// Arguments: b - MyMoneyMoney object to be added
//
////////////////////////////////////////////////////////////////////////////////
MyMoneyMoney MyMoneyMoney::operator+( const MyMoneyMoney& _b) const
{
MyMoneyMoney a(*this);
MyMoneyMoney b(_b);
MyMoneyMoney sum;
signed64 lcd;
if(a.m_denom < 0) {
a.m_num *= a.m_denom;
a.m_denom = 1;
}
if(b.m_denom < 0) {
b.m_num *= b.m_denom;
b.m_denom = 1;
}
if(a.m_denom == b.m_denom) {
sum.m_num = a.m_num + b.m_num;
sum.m_denom = a.m_denom;
} else {
lcd = a.getLcd(b);
sum.m_num = a.m_num*(lcd/a.m_denom) + b.m_num*(lcd/b.m_denom);
sum.m_denom = lcd;
}
return sum;
}
////////////////////////////////////////////////////////////////////////////////
// Name: operator-
// Purpose: Addition operator - subtracts the input amount from the object
// Returns: The current object
// Throws: Nothing.
// Arguments: AmountInPence - MyMoneyMoney object to be subtracted
//
////////////////////////////////////////////////////////////////////////////////
MyMoneyMoney MyMoneyMoney::operator-( const MyMoneyMoney& _b) const
{
MyMoneyMoney a(*this);
MyMoneyMoney b(_b);
MyMoneyMoney diff;
signed64 lcd;
if(a.m_denom < 0) {
a.m_num *= a.m_denom;
a.m_denom = 1;
}
if(b.m_denom < 0) {
b.m_num *= b.m_denom;
b.m_denom = 1;
}
if(a.m_denom == b.m_denom) {
diff.m_num = a.m_num - b.m_num;
diff.m_denom = a.m_denom;
} else {
lcd = a.getLcd(b);
diff.m_num = a.m_num*(lcd/a.m_denom) - b.m_num*(lcd/b.m_denom);
diff.m_denom = lcd;
}
return diff;
}
////////////////////////////////////////////////////////////////////////////////
// Name: operator*
// Purpose: Multiplication operator - multiplies the input amount to the object
// Returns: The current object
// Throws: Nothing.
// Arguments: b - MyMoneyMoney object to be multiplied
//
////////////////////////////////////////////////////////////////////////////////
MyMoneyMoney MyMoneyMoney::operator*( const MyMoneyMoney& _b ) const
{
MyMoneyMoney a(*this);
MyMoneyMoney b(_b);
MyMoneyMoney product;
if(a.m_denom < 0) {
a.m_num *= a.m_denom;
a.m_denom = 1;
}
if(b.m_denom < 0) {
b.m_num *= b.m_denom;
b.m_denom = 1;
}
product.m_num = a.m_num * b.m_num;
product.m_denom = a.m_denom * b.m_denom;
if(product.m_denom < 0) {
product.m_num = -product.m_num;
product.m_denom = -product.m_denom;
}
return product;
}
////////////////////////////////////////////////////////////////////////////////
// Name: operator/
// Purpose: Division operator - divides the object by the input amount
// Returns: The current object
// Throws: Nothing.
// Arguments: b - MyMoneyMoney object to be used as dividend
//
////////////////////////////////////////////////////////////////////////////////
MyMoneyMoney MyMoneyMoney::operator / ( const MyMoneyMoney& _b ) const
{
MyMoneyMoney a(*this);
MyMoneyMoney b(_b);
MyMoneyMoney quotient;
signed64 lcd;
if(a.m_denom < 0) {
a.m_num *= a.m_denom;
a.m_denom = 1;
}
if(b.m_denom < 0) {
b.m_num *= b.m_denom;
b.m_denom = 1;
}
if(a.m_denom == b.m_denom) {
quotient.m_num = a.m_num;
quotient.m_denom = b.m_num;
}
else {
/* ok, convert to the lcd and compute from there... */
lcd = a.getLcd(b);
quotient.m_num = a.m_num*(lcd/a.m_denom);
quotient.m_denom = b.m_num*(lcd/b.m_denom);
}
if(quotient.m_denom < 0) {
quotient.m_num = -quotient.m_num;
quotient.m_denom = -quotient.m_denom;
}
Q_ASSERT(quotient.m_denom != 0);
return quotient;
}
signed64 MyMoneyMoney::getLcd(const MyMoneyMoney& b) const
{
signed64 current_divisor = 2;
signed64 max_square;
signed64 three_count = 0;
signed64 small_denom;
signed64 big_denom;
if(b.m_denom < m_denom) {
small_denom = b.m_denom;
big_denom = m_denom;
}
else {
small_denom = m_denom;
big_denom = b.m_denom;
}
/* special case: smaller divides smoothly into larger */
if((big_denom % small_denom) == 0) {
return big_denom;
}
max_square = small_denom;
/* the LCM algorithm : factor out the union of the prime factors of the
* two args and then multiply the remainders together.
*
* To do this, we find the successive prime factors of the smaller
* denominator and eliminate them from both the smaller and larger
* denominator (so we only count factors on a one-on-one basis),
* then multiply the original smaller by the remains of the larger.
*
* I.e. LCM 100,96875 == 2*2*5*5,31*5*5*5*5 = 2*2,31*5*5
* answer: multiply 100 by 31*5*5 == 387500
*/
while((current_divisor * current_divisor) <= max_square) {
if(((small_denom % current_divisor) == 0) &&
((big_denom % current_divisor) == 0)) {
big_denom = big_denom / current_divisor;
small_denom = small_denom / current_divisor;
}
else {
if(current_divisor == 2) {
current_divisor++;
}
else if(three_count == 3) {
current_divisor += 4;
three_count = 1;
}
else {
current_divisor += 2;
three_count++;
}
}
if((current_divisor > small_denom) ||
(current_divisor > big_denom)) {
break;
}
}
/* max_sqaure is the original small_denom */
return max_square * big_denom;
}
const MyMoneyMoney MyMoneyMoney::convert(const signed64 _denom, const roundingMethod how) const
{
MyMoneyMoney out(*this);
MyMoneyMoney in (*this);
MyMoneyMoney temp;
signed64 denom = _denom;
signed64 temp_bc;
signed64 temp_a;
signed64 remainder;
signed64 sign;
int denom_neg=0;
if(m_denom != denom) {
/* if the denominator of the input value is negative, get rid of that. */
if(m_denom < 0) {
in.m_num = in.m_num * (- in.m_denom);
in.m_denom = 1;
}
sign = (in.m_num < 0) ? -1 : 1;
/* if the denominator is less than zero, we are to interpret it as
* the reciprocal of its magnitude. */
if(denom < 0) {
denom = - denom;
denom_neg = 1;
temp_a = (in.m_num < 0) ? -in.m_num : in.m_num;
temp_bc = in.m_denom * denom;
remainder = in.m_num % temp_bc;
out.m_num = in.m_num / temp_bc;
out.m_denom = -denom;
}
else {
/* do all the modulo and int division on positive values to make
* things a little clearer. Reduce the fraction denom/in.denom to
* help with range errors (FIXME : need bigger intermediate rep) */
temp.m_num = denom;
temp.m_denom = in.m_denom;
temp = temp.reduce();
out.m_num = in.m_num * temp.m_num;
out.m_num = (out.m_num < 0) ? -out.m_num : out.m_num;
remainder = out.m_num % temp.m_denom;
out.m_num = out.m_num / temp.m_denom;
out.m_denom = denom;
}
if(remainder > 0) {
switch(how) {
case RndFloor:
if(sign < 0) {
out.m_num = out.m_num + 1;
}
break;
case RndCeil:
if(sign > 0) {
out.m_num = out.m_num + 1;
}
break;
case RndTrunc:
break;
case RndPromote:
out.m_num = out.m_num + 1;
break;
case RndHalfDown:
if(denom_neg) {
if((2 * remainder) > in.m_denom*denom) {
out.m_num = out.m_num + 1;
}
}
else if((2 * remainder) > temp.m_denom) {
out.m_num = out.m_num + 1;
}
break;
case RndHalfUp:
if(denom_neg) {
if((2 * remainder) >= in.m_denom*denom) {
out.m_num = out.m_num + 1;
}
}
else if((2 * remainder ) >= temp.m_denom) {
out.m_num = out.m_num + 1;
}
break;
case RndRound:
if(denom_neg) {
if((2 * remainder) > in.m_denom*denom) {
out.m_num = out.m_num + 1;
}
else if((2 * remainder) == in.m_denom*denom) {
if(out.m_num % 2) {
out.m_num = out.m_num + 1;
}
}
}
else {
if((2 * remainder ) > temp.m_denom) {
out.m_num = out.m_num + 1;
}
else if((2 * remainder) == temp.m_denom) {
if(out.m_num % 2) {
out.m_num = out.m_num + 1;
}
}
}
break;
case RndNever:
qWarning("MyMoneyMoney: have remainder \"%Ld/%Ld\"->convert(%Ld, %d)",
m_num, m_denom, _denom, how);
break;
}
}
out.m_num = (sign > 0) ? out.m_num : (-out.m_num);
}
return out;
}
/********************************************************************
* gnc_numeric_reduce
* reduce a fraction by GCF elimination. This is NOT done as a
* part of the arithmetic API unless GNC_DENOM_REDUCE is specified
* as the output denominator.
********************************************************************/
const MyMoneyMoney MyMoneyMoney::reduce(void) const
{
MyMoneyMoney out;
signed64 t;
signed64 num = (m_num < 0) ? (- m_num) : m_num ;
signed64 denom = m_denom;
/* the strategy is to use euclid's algorithm */
while (denom > 0) {
t = num % denom;
num = denom;
denom = t;
}
/* num = gcd */
/* all calculations are done on positive num, since it's not
* well defined what % does for negative values */
out.m_num = m_num / num;
out.m_denom = m_denom / num;
return out;
}
signed64 MyMoneyMoney::precToDenom(int prec)
{
signed64 denom = 1;
while(prec--)
denom *= 10;
return denom;
}
double MyMoneyMoney::toDouble(void) const
{
return static_cast<double>(m_num) / static_cast<double>(m_denom);
}
int MyMoneyMoney::denomToPrec(signed64 fract)
{
int rc = 0;
while(fract > 1) {
rc++;
fract /= 10;
}
return rc;
}
MyMoneyMoney::operator int() const
{
return static_cast<int> (m_num / m_denom);
}