diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,12 +68,16 @@ COMPONENTS Archive CoreAddons Config ConfigWidgets I18n Completion KCMUtils ItemModels ItemViews Service Wallet IconThemes XmlGui TextWidgets Notifications KIO OPTIONAL_COMPONENTS ${OPT_KF5_COMPONENTS} ) -find_package(LibAlkimia5 7.0.0 REQUIRED) -# Recent changes to LibAlkimia should allow us to remove this construct -#if(CMAKE_SYSTEM_NAME MATCHES "Windows") -# include_directories(${GMP_INCLUDE_DIR}) -#endif() +# figure out which multi-precision library to use +# MPIR is preferred over GMP +find_package(MPIR) +if(NOT MPIR_FOUND) + find_package(GMP REQUIRED) + set(MP_LIBRARIES ${GMP_LIBRARIES}) +else() + set(MP_LIBRARIES ${MPIR_LIBRARIES}) +endif() find_package(KChart 2.6.0) diff --git a/cmake/modules/FindGMP.cmake b/cmake/modules/FindGMP.cmake new file mode 100644 --- /dev/null +++ b/cmake/modules/FindGMP.cmake @@ -0,0 +1,23 @@ +# Try to find the GMP librairies +# GMP_FOUND - system has GMP lib +# GMP_INCLUDE_DIR - the GMP include directory +# GMP_LIBRARIES - Libraries needed to use GMP + +# Copyright (c) 2006, Laurent Montel, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +if (GMP_INCLUDE_DIR AND GMP_LIBRARIES) + # Already in cache, be silent + set(GMP_FIND_QUIETLY TRUE) +endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES) + +find_path(GMP_INCLUDE_DIR NAMES gmp.h ) +find_library(GMP_LIBRARIES NAMES gmp libgmp) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMP DEFAULT_MSG GMP_INCLUDE_DIR GMP_LIBRARIES) + +mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES) diff --git a/cmake/modules/FindMPIR.cmake b/cmake/modules/FindMPIR.cmake new file mode 100644 --- /dev/null +++ b/cmake/modules/FindMPIR.cmake @@ -0,0 +1,24 @@ +# Try to find the MPIR librairies +# MPIR_FOUND - system has MPIR lib +# MPIR_INCLUDE_DIR - the MPIR include directory +# MPIR_LIBRARIES - Libraries needed to use MPIR + +# Copyright (c) 2006, Laurent Montel, +# Copyright (c) 2018, Thomas Baumgart +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +if (MPIR_INCLUDE_DIR AND MPIR_LIBRARIES) + # Already in cache, be silent + set(MPIR_FIND_QUIETLY TRUE) +endif (MPIR_INCLUDE_DIR AND MPIR_LIBRARIES) + +find_path(MPIR_INCLUDE_DIR NAMES mpir.h ) +find_library(MPIR_LIBRARIES NAMES mpir libmpir) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(MPIR DEFAULT_MSG MPIR_INCLUDE_DIR MPIR_LIBRARIES) + +mark_as_advanced(MPIR_INCLUDE_DIR MPIR_LIBRARIES) diff --git a/config-kmymoney.h.cmake b/config-kmymoney.h.cmake --- a/config-kmymoney.h.cmake +++ b/config-kmymoney.h.cmake @@ -19,6 +19,8 @@ #cmakedefine ENABLE_SQLCIPHER 1 +#cmakedefine MPIR_FOUND 1 + #cmakedefine ENABLE_GPG 1 #cmakedefine IS_APPIMAGE 1 diff --git a/kmymoney/CMakeLists.txt b/kmymoney/CMakeLists.txt --- a/kmymoney/CMakeLists.txt +++ b/kmymoney/CMakeLists.txt @@ -4,6 +4,8 @@ ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/settings/ ${CMAKE_CURRENT_BINARY_DIR}/settings/ + ${CMAKE_CURRENT_SOURCE_DIR}/alkimia/ + ${CMAKE_CURRENT_BINARY_DIR}/alkimia/ ${CMAKE_CURRENT_BINARY_DIR}/dialogs/ ${CMAKE_CURRENT_SOURCE_DIR}/widgets/ ${CMAKE_CURRENT_BINARY_DIR}/widgets/ @@ -32,6 +34,7 @@ ${CMAKE_CURRENT_BINARY_DIR}/menus/ ) +add_subdirectory( alkimia ) add_subdirectory( mymoney ) add_subdirectory( settings ) add_subdirectory( models ) @@ -68,7 +71,7 @@ KF5::KIOFileWidgets KF5::KIOWidgets KF5::KIONTLM - Alkimia::alkimia + alkimia kmm_mymoney kmm_utils_webconnect kmm_utils_platformtools diff --git a/kmymoney/alkimia/CMakeLists.txt b/kmymoney/alkimia/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/kmymoney/alkimia/CMakeLists.txt @@ -0,0 +1,32 @@ +set(alkimia_SOURCES + alkvalue.cpp + ) + +set(alkimia_HEADERS + alkvalue.h + ${CMAKE_CURRENT_BINARY_DIR}/alk_export.h + ) + +add_library(alkimia SHARED ${alkimia_SOURCES}) + +generate_export_header(alkimia BASE_NAME alk) + +target_link_libraries(alkimia + PUBLIC + Qt5::Core + Qt5::DBus + ${MP_LIBRARIES} +) + +set_target_properties(alkimia PROPERTIES + VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) + +install(TARGETS alkimia ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) + +install(FILES ${alkimia_HEADERS} + DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney + COMPONENT Devel) + +if(BUILD_TESTING) + add_subdirectory(tests) +endif() diff --git a/kmymoney/alkimia/alkvalue.h b/kmymoney/alkimia/alkvalue.h new file mode 100644 --- /dev/null +++ b/kmymoney/alkimia/alkvalue.h @@ -0,0 +1,266 @@ +/* + * Copyright 2010-2018 Thomas Baumgart + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ALKVALUE_H +#define ALKVALUE_H + +#include + +// Workaround: include before gmpxx.h to fix build with gcc-4.9 +/** @todo When gmp version is higer than 5.1.3, remove cstddef include */ +#include +#ifdef MPIR_FOUND +#include +#else +#include +#endif +#include +#include + +#include "alk_export.h" + +/** + * This class represents a financial value within Alkimia. + * It can be used to represent balances, shares, amounts etc. + * + * @author Thomas Baumgart + */ +class ALK_EXPORT AlkValue +{ +public: + enum RoundingMethod { + RoundNever = 0, /**< + * Don't do any rounding, simply truncate and + * print a warning in case of a remainder. + * Otherwise the same as RoundTrunc. + */ + + RoundFloor, /**< + * Round to the largest integral value not + * greater than @p this. + * e.g. 0.5 -> 0.0 and -0.5 -> -1.0 + */ + + RoundCeil, /**< + * Round to the smallest integral value not + * less than @p this. + * e.g. 0.5 -> 1.0 and -0.5 -> -0.0 + */ + + RoundTruncate, /**< + * No rounding, simply truncate any fraction + */ + + RoundPromote, /**< + * Use RoundCeil for positive and RoundFloor + * for negative values of @p this. + * e.g. 0.5 -> 1.0 and -0.5 -> -1.0 + */ + + RoundHalfDown, /**< + * Round up or down with the following + * constraints: + * 0.1 .. 0.5 -> 0.0 and 0.6 .. 0.9 -> 1.0 + */ + + RoundHalfUp, /**< + * Round up or down with the following + * constraints: + * 0.1 .. 0.4 -> 0.0 and 0.5 .. 0.9 -> 1.0 + */ + + RoundRound /**< + * Use RoundHalfDown for 0.1 .. 0.4 and + * RoundHalfUp for 0.6 .. 0.9. Use RoundHalfUp + * for 0.5 in case the resulting numerator + * is odd, RoundHalfDown in case the resulting + * numerator is even. + * e.g. 0.5 -> 0.0 and 1.5 -> 2.0 + */ + }; + + // Constructors / Destructor + /** + * This is the standard constructor of an AlkValue object. + * The value will be initialized to 0. + */ + AlkValue(); + + /// The destructor + ~AlkValue(); + + /// Copy constructor + AlkValue(const AlkValue &val); + + /** + * This constructor converts an int into an AlkValue. It can + * also convert a rational number when a @a denom is supplied. + * + * @param num numerator of the rational number + * @param denom denominator of the rational number (defaults to 1) + */ + explicit AlkValue(const int num, const unsigned int denom = 1); + + /** + * Convenience ctor for usage with mpz_class objects as numerator + * and denominator. + * + * @param num numerator of the rational number + * @param denom denominator of the rational number (defaults to 1) + */ + explicit AlkValue(const mpz_class &num, const mpz_class &denom); + + /** + * Convenience ctor to create an AlkValue object based on an mpq_class object + */ + explicit AlkValue(const mpq_class &val); + + /** + * This constructor converts a double into an AlkValue. In case + * a @a denom is supplied with a value different from zero, the + * @a val will be rounded to be based on the supplied @a denom. + * e.g. val = 1.234 and denom = 100 will construct an AlkValue + * of 1.23. The rounding method is @p RoundRound. + * + * @sa AlkValue::convertDenominator() + * + * @param val the double value + * @param denom the denominator of the resulting AlkValue + * + * @note In case one wants to use the number of decimal places + * to specify the length of the fractional part, use + * + * @code + * AlkValue alk(1.234, AlkValue::precisionToDenominator(2).get_ui()); + * // alk == 1.23 + * @endcode + */ + explicit AlkValue(const double &val, const unsigned int denom = 0); + + /** + * This constructor converts a QString into an AlkValue. + * Several formats are supported: + * + * -# prices in the form "8 5/16" + * -# our own toString() format + * -# others + + * Others may be enclosed in "(" and ")" and treated as negative. + * They may start or end with a dash and treated as negative. + * The decimal symbols is identified as provided in @a decimalSymbol. + * All other non-numeric characters are skipped + */ + AlkValue(const QString &str, const QChar &decimalSymbol); + + /** + * Returns the current value converted to the given @a denom (default is 100 + * or two digits of precision). The rounding method used is controlled by + * the @a how argument and defaults to @p RoundRound. + */ + AlkValue convertDenominator(const int denom = 100, const RoundingMethod how = RoundRound) const; + + /** + * This is a convenience function for convertDenom but instead of providing + * the new denominator one provides the number of digits for the @a precision. + * This value defaults to 2. The rounding method used is controlled by + * the @a how argument and defaults to @p RoundRound. + */ + AlkValue convertPrecision(const int precision = 2, const RoundingMethod how = RoundRound) const; + + // assignment operators + const AlkValue & operator=(const AlkValue &val); + const AlkValue & operator=(int num); + const AlkValue & operator=(double num); + const AlkValue & operator=(const QString &str); + + // comparison + bool operator==(const AlkValue &val) const; + bool operator!=(const AlkValue &val) const; + bool operator<(const AlkValue &val) const; + bool operator>(const AlkValue &val) const; + bool operator<=(const AlkValue &val) const; + bool operator>=(const AlkValue &val) const; + + // calculation + AlkValue operator+(const AlkValue &summand) const; + AlkValue operator-(const AlkValue &minuend) const; + AlkValue operator*(const AlkValue &factor) const; + AlkValue operator/(const AlkValue &divisor) const; + + AlkValue operator*(int factor) const; + + // unary operators + AlkValue operator-() const; + AlkValue & operator+= (const AlkValue &val); + AlkValue & operator-= (const AlkValue &val); + AlkValue & operator/= (const AlkValue &val); + AlkValue & operator*= (const AlkValue &val); + + // functions + + /// @return the absolute value of the AlkValue + AlkValue abs() const; + + /// @return QString representation in form '[-]num/denom'. + QString toString() const; + + /** + * This method transforms the AlkValue into its canonicalized + * form by reducing it to the smallest denominator. Example: + * 25/100 will be converted to 1/4. Use this function at the + * end of a longer calculation as all AlkValue methods require + * the object to be in the canonicalized form. For speed purposes + * the conversion is not performed before each operation. + * + * @return const reference to the object + */ + const AlkValue& canonicalize(); + + /// convert a denominator to a precision + /// e.g. 100 -> 2, 1000 -> 3 + /// in case of a negative @a denom, the function returns 0 + static mpz_class denominatorToPrecision(mpz_class denom); + + /// convert a precision to the corresponding denominator + /// e.g. 2 -> 100, 4 -> 10000 + /// in case of a negative @a prec, the function returns 1 + static mpz_class precisionToDenominator(mpz_class prec); + +protected: + /// \internal unit test class + friend class AlkValueTest; + + /// provides an access method to the private value storage + /// for derived classes + const mpq_class &valueRef() const; + mpq_class &valueRef(); + +private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + QSharedDataPointer d; + /// \internal shared zero value. + static QSharedDataPointer& sharedZero(); + + // The following methods are not implemented (yet) + // ALKIMIA_EXPORT friend QDataStream &operator<<(QDataStream &, const AlkValue &); + // ALKIMIA_EXPORT friend QDataStream &operator>>(QDataStream &, AlkValue &); +}; + +#endif + diff --git a/kmymoney/alkimia/alkvalue.cpp b/kmymoney/alkimia/alkvalue.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/alkimia/alkvalue.cpp @@ -0,0 +1,563 @@ +/* + * Copyright 2010-2018 Thomas Baumgart + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "alkvalue.h" + +#include +#include +#include + +class AlkValue::Private : public QSharedData +{ +public: + Private() + { + } + + Private(const Private &other) : QSharedData(other) + , m_val(other.m_val) + { + } + + mpq_class m_val; +}; + +/** + * Helper function to convert an mpq_class object into + * its internal QString representation. Mainly used for + * debugging. + */ +static QString mpqToString(const mpq_class &val) +{ + char *p = 0; + // use the gmp provided conversion routine + gmp_asprintf(&p, "%Qd", val.get_mpq_t()); + + // convert it into a QString + QString result = QString::fromLatin1(p); + + // and free up the resources allocated by gmp_asprintf + void (*freefunc)(void *, size_t); + mp_get_memory_functions(NULL, NULL, &freefunc); + (*freefunc)(p, std::strlen(p) + 1); + + if (!result.contains(QLatin1Char('/'))) { + result += QString::fromLatin1("/1"); + } + + // done + return result; +} + +#if 0 +/** + * Helper function to convert an mpz_class object into + * its internal QString representation. Mainly used for + * debugging. + */ +static QString mpzToString(const mpz_class &val) +{ + char *p = 0; + // use the gmp provided conversion routine + gmp_asprintf(&p, "%Zd", val.get_mpz_t()); + + // convert it into a QString + QString result(QString::fromLatin1(p)); + + // and free up the resources allocated by gmp_asprintf + __gmp_freefunc_t freefunc; + mp_get_memory_functions(NULL, NULL, &freefunc); + (*freefunc)(p, std::strlen(p) + 1); + + // done + return result; +} + +#endif + +QSharedDataPointer &AlkValue::sharedZero() +{ + static QSharedDataPointer sharedZeroPointer(new AlkValue::Private); + return sharedZeroPointer; +} + +AlkValue::AlkValue() + : d(sharedZero()) +{ +} + +AlkValue::AlkValue(const AlkValue &val) + : d(val.d) +{ +} + +AlkValue::AlkValue(const int num, const unsigned int denom) + : d(new Private) +{ + d->m_val = mpq_class(num, denom); + d->m_val.canonicalize(); +} + +AlkValue::AlkValue(const mpz_class &num, const mpz_class &denom) + : d(new Private) +{ + mpz_set(d->m_val.get_num_mpz_t(), num.get_mpz_t()); + mpz_set(d->m_val.get_den_mpz_t(), denom.get_mpz_t()); + d->m_val.canonicalize(); +} + +AlkValue::AlkValue(const mpq_class &val) + : d(new Private) +{ + d->m_val = val; + d->m_val.canonicalize(); +} + +AlkValue::AlkValue(const double &dAmount, const unsigned int denom) + : d(new Private) +{ + d->m_val = dAmount; + d->m_val.canonicalize(); + if (denom != 0) { + *this = convertDenominator(denom); + } +} + +AlkValue::AlkValue(const QString &str, const QChar &decimalSymbol) + : d(new Private) +{ + // empty strings are easy + if (str.isEmpty()) { + return; + } + + // take care of mixed prices of the form "5 8/16" as well + // as own internal string representation + QRegExp regExp(QLatin1String("^((\\d+)\\s+|-)?(\\d+/\\d+)")); + // +-#2-+ +---#3----+ + // +-----#1-----+ + if (regExp.indexIn(str) > -1) { + d->m_val = qPrintable(str.mid(regExp.pos(3))); + d->m_val.canonicalize(); + const QString &part1 = regExp.cap(1); + if (!part1.isEmpty()) { + if (part1 == QLatin1String("-")) { + mpq_neg(d->m_val.get_mpq_t(), d->m_val.get_mpq_t()); + } else { + mpq_class summand(qPrintable(part1)); + mpq_add(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), summand.get_mpq_t()); + d->m_val.canonicalize(); + } + } + return; + } + + // qDebug("we got '%s' to convert", qPrintable(str)); + // everything else gets down here + const QString negChars = QString::fromLatin1("\\-\\(\\)"); + const QString validChars = QString::fromLatin1("\\d\\%1%2").arg(decimalSymbol, negChars); + QRegExp invCharSet(QString::fromLatin1("[^%1]").arg(validChars)); + QRegExp negCharSet(QString::fromLatin1("[%1]").arg(negChars)); + + QString res(str); + // get rid of any character that is not allowed. + res.remove(invCharSet); + + // qDebug("we reduced it to '%s'", qPrintable(res)); + // check if number is negative + bool isNegative = false; + if (res.indexOf(negCharSet) != -1) { + isNegative = true; + res.remove(negCharSet); + } + + // qDebug("and modified it to '%s'", qPrintable(res)); + // if someone uses the decimal symbol more than once, we get + // rid of them except the right most one + int pos; + while (res.count(decimalSymbol) > 1) { + pos = res.indexOf(decimalSymbol); + res.remove(pos, 1); + } + + // take care of any fractional part + pos = res.indexOf(decimalSymbol); + int len = res.length(); + QString fraction = QString::fromLatin1("/1"); + if ((pos != -1) && (pos < len)) { + fraction += QString(len - pos - 1, QLatin1Char('0')); + res.remove(pos, 1); + } + + // check if the resulting numerator contains any leading zeros ... + int cnt = 0; + len = res.length() - 1; + while (res[cnt] == QLatin1Char('0') && cnt < len) { + ++cnt; + } + + // ... and remove them + if (cnt) { + res.remove(0, cnt); + } + + // in case the numerator is empty, we convert it to "0" + if (res.isEmpty()) { + res = QLatin1Char('0'); + } + res += fraction; + + // looks like we now have a pretty normalized string that we + // can convert right away + // qDebug("and try to convert '%s'", qPrintable(res)); + try { + d->m_val = mpq_class(qPrintable(res)); + } catch (const std::invalid_argument &) { + qWarning("Invalid argument '%s' to mpq_class() in AlkValue. Arguments to ctor: '%s', '%c'", qPrintable( + res), qPrintable(str), decimalSymbol.toLatin1()); + d->m_val = mpq_class(0); + } + d->m_val.canonicalize(); + + // now we make sure that we use the right sign + if (isNegative) { + d->m_val = -d->m_val; + } +} + +AlkValue::~AlkValue() +{ +} + +QString AlkValue::toString() const +{ + return mpqToString(d->m_val); +} + +AlkValue AlkValue::convertDenominator(int _denom, const RoundingMethod how) const +{ + AlkValue in(*this); + mpz_class in_num(mpq_numref(in.d->m_val.get_mpq_t())); + + AlkValue out; // initialize to zero + + int sign = sgn(in_num); + if (sign != 0) { + // sign is either -1 for negative numbers or +1 in all other cases + + AlkValue temp; + mpz_class denom = _denom; + // only process in case the denominators are different + if (mpz_cmpabs(denom.get_mpz_t(), mpq_denref(d->m_val.get_mpq_t())) != 0) { + mpz_class in_denom(mpq_denref(in.d->m_val.get_mpq_t())); + mpz_class out_num, out_denom; + + if (sgn(in_denom) == -1) { // my denom is negative + in_num = in_num * (-in_denom); + in_num = 1; + } + + mpz_class remainder; + int denom_neg = 0; + + // if the denominator is less than zero, we are to interpret it as + // the reciprocal of its magnitude. + if (sgn(denom) < 0) { + mpz_class temp_a; + mpz_class temp_bc; + denom = -denom; + denom_neg = 1; + temp_a = ::abs(in_num); + temp_bc = in_denom * denom; + remainder = temp_a % temp_bc; + out_num = temp_a / temp_bc; + out_denom = denom; + } else { + temp = AlkValue(denom, in_denom); + // the canonicalization required here is part of the ctor + // temp.d->m_val.canonicalize(); + + out_num = ::abs(in_num * temp.d->m_val.get_num()); + remainder = out_num % temp.d->m_val.get_den(); + out_num = out_num / temp.d->m_val.get_den(); + out_denom = denom; + } + + if (remainder != 0) { + switch (how) { + case RoundFloor: + if (sign < 0) { + out_num = out_num + 1; + } + break; + + case RoundCeil: + if (sign > 0) { + out_num = out_num + 1; + } + break; + + case RoundTruncate: + break; + + case RoundPromote: + out_num = out_num + 1; + break; + + case RoundHalfDown: + if (denom_neg) { + if ((2 * remainder) > (in_denom * denom)) { + out_num = out_num + 1; + } + } else if ((2 * remainder) > (temp.d->m_val.get_den())) { + out_num = out_num + 1; + } + break; + + case RoundHalfUp: + if (denom_neg) { + if ((2 * remainder) >= (in_denom * denom)) { + out_num = out_num + 1; + } + } else if ((2 * remainder) >= temp.d->m_val.get_den()) { + out_num = out_num + 1; + } + break; + + case RoundRound: + if (denom_neg) { + if ((remainder * 2) > (in_denom * denom)) { + out_num = out_num + 1; + } else if ((2 * remainder) == (in_denom * denom)) { + if ((out_num % 2) != 0) { + out_num = out_num + 1; + } + } + } else { + if ((remainder * 2) > temp.d->m_val.get_den()) { + out_num = out_num + 1; + } else if ((2 * remainder) == temp.d->m_val.get_den()) { + if ((out_num % 2) != 0) { + out_num = out_num + 1; + } + } + } + break; + + case RoundNever: + qWarning("AlkValue: have remainder \"%s\"->convert(%d, %d)", + qPrintable(toString()), _denom, how); + break; + } + } + + // construct the new output value + out = AlkValue(out_num * sign, out_denom); + } else { + out = *this; + } + } + return out; +} + +AlkValue AlkValue::convertPrecision(int prec, const RoundingMethod how) const +{ + return convertDenominator(precisionToDenominator(prec).get_si(), how); +} + +mpz_class AlkValue::denominatorToPrecision(mpz_class denom) +{ + mpz_class rc = 0; + while (denom > 1) { + ++rc; + denom /= 10; + } + return rc; +} + +mpz_class AlkValue::precisionToDenominator(mpz_class prec) +{ + mpz_class denom = 1; + while ((prec--) > 0) { + denom *= 10; + } + return denom; +} + +const AlkValue &AlkValue::canonicalize() +{ + d->m_val.canonicalize(); + return *this; +} + +AlkValue AlkValue::operator+(const AlkValue &right) const +{ + AlkValue result; + mpq_add(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t()); + result.d->m_val.canonicalize(); + return result; +} + +AlkValue AlkValue::operator-(const AlkValue &right) const +{ + AlkValue result; + mpq_sub(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t()); + result.d->m_val.canonicalize(); + return result; +} + +AlkValue AlkValue::operator*(const AlkValue &right) const +{ + AlkValue result; + mpq_mul(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t()); + result.d->m_val.canonicalize(); + return result; +} + +AlkValue AlkValue::operator/(const AlkValue &right) const +{ + AlkValue result; + mpq_div(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t()); + result.d->m_val.canonicalize(); + return result; +} + +AlkValue AlkValue::operator*(int factor) const +{ + AlkValue result; + mpq_class right(factor); + mpq_mul(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.get_mpq_t()); + result.d->m_val.canonicalize(); + return result; +} + +const AlkValue &AlkValue::operator=(const AlkValue &right) +{ + d = right.d; + return *this; +} + +const AlkValue &AlkValue::operator=(int right) +{ + d->m_val = right; + d->m_val.canonicalize(); + return *this; +} + +const AlkValue &AlkValue::operator=(double right) +{ + d->m_val = right; + d->m_val.canonicalize(); + return *this; +} + +const AlkValue &AlkValue::operator=(const QString &right) +{ + AlkValue other(right, QLatin1Char('.')); + d->m_val = other.d->m_val; + return *this; +} + +AlkValue AlkValue::abs() const +{ + AlkValue result; + mpq_abs(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t()); + result.d->m_val.canonicalize(); + return result; +} + +bool AlkValue::operator==(const AlkValue &val) const +{ + if (d == val.d) { + return true; + } + return mpq_equal(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()); +} + +bool AlkValue::operator!=(const AlkValue &val) const +{ + if (d == val.d) { + return false; + } + return !mpq_equal(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()); +} + +bool AlkValue::operator<(const AlkValue &val) const +{ + return mpq_cmp(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()) < 0 ? true : false; +} + +bool AlkValue::operator>(const AlkValue &val) const +{ + return mpq_cmp(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()) > 0 ? true : false; +} + +bool AlkValue::operator<=(const AlkValue &val) const +{ + return mpq_cmp(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()) <= 0 ? true : false; +} + +bool AlkValue::operator>=(const AlkValue &val) const +{ + return mpq_cmp(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()) >= 0 ? true : false; +} + +AlkValue AlkValue::operator-() const +{ + AlkValue result; + mpq_neg(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t()); + result.d->m_val.canonicalize(); + return result; +} + +AlkValue &AlkValue::operator+=(const AlkValue &right) +{ + mpq_add(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t()); + d->m_val.canonicalize(); + return *this; +} + +AlkValue &AlkValue::operator-=(const AlkValue &right) +{ + mpq_sub(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t()); + d->m_val.canonicalize(); + return *this; +} + +AlkValue &AlkValue::operator*=(const AlkValue &right) +{ + mpq_mul(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t()); + d->m_val.canonicalize(); + return *this; +} + +AlkValue &AlkValue::operator/=(const AlkValue &right) +{ + mpq_div(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t()); + d->m_val.canonicalize(); + return *this; +} + +const mpq_class &AlkValue::valueRef() const +{ + return d->m_val; +} + +mpq_class &AlkValue::valueRef() +{ + return d->m_val; +} diff --git a/kmymoney/alkimia/tests/CMakeLists.txt b/kmymoney/alkimia/tests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/kmymoney/alkimia/tests/CMakeLists.txt @@ -0,0 +1,9 @@ +include(ECMAddTests) + +file(GLOB tests_sources "*-test.cpp") + +ecm_add_tests(${tests_sources} + LINK_LIBRARIES + alkimia + Qt5::Test +) diff --git a/kmymoney/alkimia/tests/alkvalue-test.h b/kmymoney/alkimia/tests/alkvalue-test.h new file mode 100644 --- /dev/null +++ b/kmymoney/alkimia/tests/alkvalue-test.h @@ -0,0 +1,59 @@ +/* + * Copyright 2010 Thomas Baumgart + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ALKVALUETEST_H +#define ALKVALUETEST_H + +#include + +class AlkValue; + +class AlkValueTest : public QObject +{ + Q_OBJECT + +private slots: + void init(); + void cleanup(); + void emptyCtor(); + void copyCtor(); + void intCtor(); + void stringCtor(); + void doubleCtor(); + void assignment(); + void equality(); + void inequality(); + void less(); + void greater(); + void lessThan(); + void greaterThan(); + void addition(); + void subtraction(); + void multiplication(); + void division(); + void unaryMinus(); + void abs(); + void precision(); + void convertDenominator(); + void convertPrecision(); + void denominatorToPrecision(); + void precisionToDenominator(); + void valueRef(); + void canonicalize(); +}; + +#endif // ALKVALUETEST_H diff --git a/kmymoney/alkimia/tests/alkvalue-test.cpp b/kmymoney/alkimia/tests/alkvalue-test.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/alkimia/tests/alkvalue-test.cpp @@ -0,0 +1,577 @@ +/* + * Copyright 2010 Thomas Baumgart + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "alkvalue-test.h" +#include "alkvalue.h" + +#include + +QTEST_MAIN(AlkValueTest) + +namespace QTest { +template<> +char *toString(const AlkValue &val) +{ + return qstrdup(val.toString().toUtf8()); +} +} + +void AlkValueTest::init() +{ +} + +void AlkValueTest::cleanup() +{ +} + +void AlkValueTest::emptyCtor() +{ + AlkValue *m = new AlkValue(); + QCOMPARE(m->toString(), QLatin1String("0/1")); + delete m; +} + +void AlkValueTest::copyCtor() +{ + AlkValue a(41, 152); + AlkValue b(a); + + QCOMPARE(a.toString(), QLatin1String("41/152")); + QCOMPARE(b.toString(), QLatin1String("41/152")); +} + +void AlkValueTest::intCtor() +{ + AlkValue *m; + m = new AlkValue(10); + QCOMPARE(m->toString(), QLatin1String("10/1")); + delete m; + + m = new AlkValue(-10); + QCOMPARE(m->toString(), QLatin1String("-10/1")); + delete m; + + m = new AlkValue(10, 2); + QCOMPARE(m->toString(), QLatin1String("5/1")); + delete m; + + m = new AlkValue(-10, 2); + QCOMPARE(m->toString(), QLatin1String("-5/1")); + delete m; + + // the denominator is an unsigned value thus sticking in a negative + // one does not make sense + m = new AlkValue(-10, -2); + QVERIFY(m->toString() != QLatin1String("-5/1")); + delete m; +} + +void AlkValueTest::stringCtor() +{ + AlkValue *m; + + // mixed mode used for some price information in QIF + m = new AlkValue(QLatin1String("5 8/16"), QLatin1Char('.')); + QCOMPARE(*m, AlkValue(5.5)); + delete m; + + // standard representation + m = new AlkValue(QLatin1String("12345/100"), QLatin1Char('.')); + QCOMPARE(m->toString(), QLatin1String("2469/20")); + delete m; + + // negative standard representation + m = new AlkValue(QLatin1String("-8/32"), QLatin1Char('.')); + QCOMPARE(*m, AlkValue(-0.25)); + delete m; + + // false negative standard representation + m = new AlkValue(QLatin1String("(8/32)"), QLatin1Char('.')); + QCOMPARE(*m, AlkValue(-832)); + delete m; + + // duplicate negative standard representation + m = new AlkValue(QLatin1String("(-8/32)"), QLatin1Char('.')); + QCOMPARE(*m, AlkValue(-832)); + delete m; + + // duplicate negative standard representation + m = new AlkValue(QLatin1String("-(8/32)"), QLatin1Char('.')); + QCOMPARE(*m, AlkValue(-832)); + delete m; + + // different decimal symbol + m = new AlkValue(QLatin1String("x1.234,568 EUR"), QLatin1Char(',')); + QCOMPARE(m->toString(), QLatin1String("154321/125")); + delete m; + + // octal leadin + m = new AlkValue(QLatin1String("0.07"), QLatin1Char('.')); + QCOMPARE(m->toString(), QLatin1String("7/100")); + delete m; + m = new AlkValue(QLatin1String("0.09"), QLatin1Char('.')); + QCOMPARE(m->toString(), QLatin1String("9/100")); + delete m; + m = new AlkValue(QLatin1String("09"), QLatin1Char('.')); + QCOMPARE(m->toString(), QLatin1String("9/1")); + delete m; + + // negative numbers + m = new AlkValue(QLatin1String("x(1,234.)"), QLatin1Char('.')); + QCOMPARE(*m, AlkValue(-1234)); + delete m; + m = new AlkValue(QLatin1String("x-1,234."), QLatin1Char('.')); + QCOMPARE(*m, AlkValue(-1234)); + delete m; + m = new AlkValue(QLatin1String("x1,234.-"), QLatin1Char('.')); + QCOMPARE(*m, AlkValue(-1234)); + delete m; + + // empty string + m = new AlkValue(QLatin1String(""), QLatin1Char('.')); + QCOMPARE(*m, AlkValue()); + delete m; + m = new AlkValue(QLatin1String("."), QLatin1Char('.')); + QCOMPARE(*m, AlkValue()); + delete m; + m = new AlkValue(QLatin1String(","), QLatin1Char('.')); + QCOMPARE(*m, AlkValue()); + delete m; + m = new AlkValue(QLatin1String("."), QLatin1Char(',')); + QCOMPARE(*m, AlkValue()); + delete m; +} + +void AlkValueTest::doubleCtor() +{ + for (int i = -123456; i < 123456; ++i) { + double d = i; + AlkValue r(i, 100); + d /= 100; + AlkValue t(d, 100); + QCOMPARE(t, r); + } + + AlkValue a = AlkValue(1.9999999999998); + QVERIFY(a != AlkValue(2, 1)); + QVERIFY(a < AlkValue(2, 1)); + QVERIFY(a > AlkValue(QLatin1String("1.999999999"), QLatin1Char('.'))); + + a = AlkValue(1.9999999999998, 100); + QCOMPARE(a, AlkValue(2, 1)); + + a = AlkValue(1.234, AlkValue::precisionToDenominator(2).get_ui()); + QCOMPARE(a, AlkValue(123, 100)); +} + +void AlkValueTest::assignment() +{ + // const AlkValue& operator=(const AlkValue& val); + AlkValue m0; + AlkValue m1(10, 2); + m0 = m1; + QCOMPARE(m0.toString(), QLatin1String("5/1")); + + // const AlkValue& operator=(int num); + m0 = 6; + QCOMPARE(m0.toString(), QLatin1String("6/1")); + m0 = -12; + QCOMPARE(m0.toString(), QLatin1String("-12/1")); + + // const AlkValue& operator=(double num); + m0 = 123.45; + QCOMPARE(m0.toString(), QLatin1String("8687021468732621/70368744177664")); + m0 = 1.23e7; + QCOMPARE(m0.toString(), QLatin1String("12300000/1")); + + // const AlkValue& operator=(const QString& str); + m0 = QLatin1String("x1234.567"); + QCOMPARE(m0.toString(), QLatin1String("1234567/1000")); + m0 = QLatin1String("(x1234.567)"); + QCOMPARE(m0.toString(), QLatin1String("-1234567/1000")); + m0 = QLatin1String("-1234.567"); + QCOMPARE(m0.toString(), QLatin1String("-1234567/1000")); +} + +void AlkValueTest::equality() +{ + AlkValue m0, m1; + m0 = 123; + m1 = QLatin1String("123"); + QCOMPARE(m0, m1); + + m0 = QLatin1String("511/100"); + m1 = QLatin1String("5753348523965686/1125899906842600"); + QCOMPARE(m0, m1); + + m0 = QLatin1String("-14279570/100"); + m1 = QLatin1String("-1427957/10"); + QCOMPARE(m0, m1); + + m0 = QLatin1String("-7301028/100"); + m1 = QLatin1String("-1825257/25"); + QCOMPARE(m0, m1); +} + +void AlkValueTest::inequality() +{ + AlkValue m0, m1; + m0 = 123; + m1 = QLatin1String("124"); + QVERIFY(m0 != m1); + + m0 = QLatin1String("511/100"); + m1 = QLatin1String("5753348523965809/1125899906842624"); + QVERIFY(m0 != m1); +} + +void AlkValueTest::less() +{ + AlkValue m0, m1; + m0 = 12; + m1 = 13; + QVERIFY(m0 < m1); + m0 = -m0; + m1 = -m1; + QVERIFY(m1 < m0); + + m0 = 12; + m1 = AlkValue(QLatin1String("12.0000000000000000000000000000001"), QLatin1Char('.')); + QVERIFY(m0 < m1); + QVERIFY(!(m1 < m0)); + m0 = -m0; + m1 = -m1; + QVERIFY(m1 < m0); + QVERIFY(!(m0 < m1)); + + m0 = 12; + m1 = m0; + QVERIFY(!(m0 < m1)); +} + +void AlkValueTest::greater() +{ + AlkValue m0, m1; + m0 = 12; + m1 = 13; + QVERIFY(m1 > m0); + m0 = -m0; + m1 = -m1; + QVERIFY(m0 > m1); + + m0 = 12; + m1 = AlkValue(QLatin1String("12.0000000000000000000000000000001"), QLatin1Char('.')); + QVERIFY(m1 > m0); + QVERIFY(!(m0 > m1)); + m0 = -m0; + m1 = -m1; + QVERIFY(m0 > m1); + QVERIFY(!(m1 > m0)); + + m0 = 12; + m1 = m0; + QVERIFY(!(m0 > m1)); +} + +void AlkValueTest::lessThan() +{ + AlkValue m0, m2; + AlkValue m1 = AlkValue(QLatin1String("12.0000000000000000000000000000001"), QLatin1Char('.')); + m0 = 12; + m2 = 12; + QVERIFY(m0 <= m1); + QVERIFY(m0 <= m2); + m0 = -m0; + m1 = -m1; + m2 = -m2; + QVERIFY(m1 <= m0); + QVERIFY(m2 <= m0); +} + +void AlkValueTest::greaterThan() +{ + AlkValue m0, m2; + AlkValue m1 = AlkValue(QLatin1String("12.0000000000000000000000000000001"), QLatin1Char('.')); + m0 = 12; + m2 = 12; + QVERIFY(m1 >= m0); + QVERIFY(m2 >= m0); + m0 = -m0; + m1 = -m1; + m2 = -m2; + QVERIFY(m0 >= m1); + QVERIFY(m0 >= m2); +} + +void AlkValueTest::addition() +{ + // AlkValue operator+( const AlkValue& summand ) const; + AlkValue m0, m1; + m0 = 100; + m1 = 23; + QCOMPARE((m0 + m1), AlkValue(123)); + + // AlkValue& operator+= ( const AlkValue& val ); + m0 += m1; + QCOMPARE(m0, AlkValue(123)); + + m0 = 100; + m1 = -23; + QCOMPARE((m0 + m1), AlkValue(77)); + + m0 += m1; + QCOMPARE(m0, AlkValue(77)); +} + +void AlkValueTest::subtraction() +{ + // AlkValue operator-( const AlkValue& minuend ) const; + AlkValue m0, m1; + m0 = 100; + m1 = 23; + QCOMPARE((m0 - m1), AlkValue(77)); + + // AlkValue& operator-= ( const AlkValue& val ); + m0 -= m1; + QCOMPARE(m0, AlkValue(77)); + + m0 = 100; + m1 = -23; + QCOMPARE((m0 - m1), AlkValue(123)); + + m0 -= m1; + QCOMPARE(m0, AlkValue(123)); +} + +void AlkValueTest::multiplication() +{ + // AlkValue operator*( const AlkValue& factor ) const; + AlkValue m0, m1; + m0 = 100; + m1 = 23; + QCOMPARE((m0 * m1), AlkValue(2300)); + + // AlkValue& operator*= ( const AlkValue& val ); + m0 *= m1; + QCOMPARE(m0, AlkValue(2300)); + + m0 = 100; + m1 = -23; + QCOMPARE((m0 * m1), AlkValue(-2300)); + + m0 *= m1; + QCOMPARE(m0, AlkValue(-2300)); + + // AlkValue operator*( int factor) const; + QCOMPARE((m1 * 4), AlkValue(-92)); + QCOMPARE((m1 *(-4)), AlkValue(92)); +} + +void AlkValueTest::division() +{ + // AlkValue operator/( const AlkValue& divisor ) const; + AlkValue m0, m1; + m0 = 100; + m1 = 20; + QCOMPARE((m0 / m1), AlkValue(5)); + + // AlkValue& operator/= ( const AlkValue& val ); + m0 /= m1; + QCOMPARE(m0, AlkValue(5)); + + m0 = 100; + m1 = -20; + QCOMPARE((m0 / m1), AlkValue(-5)); + + m0 /= m1; + QCOMPARE(m0, AlkValue(-5)); +} + +void AlkValueTest::unaryMinus() +{ + // AlkValue operator-() const; + AlkValue m0(5); + QCOMPARE(-m0, AlkValue(-5)); +} + +void AlkValueTest::abs() +{ + AlkValue m0(-5); + AlkValue m1(5); + QCOMPARE(m0.abs(), AlkValue(5)); + QCOMPARE(m1.abs(), AlkValue(5)); +} + +void AlkValueTest::precision() +{ + AlkValue a(QLatin1String("1234567890"), QLatin1Char('.')); + AlkValue b(QLatin1String("1234567890"), QLatin1Char('.')); + AlkValue c; + + // QVERIFY(c.isZero() == true); + c = a * b; + QCOMPARE(c, AlkValue(QLatin1String("1524157875019052100"), QLatin1Char('.'))); + c /= b; + QCOMPARE(c, AlkValue(QLatin1String("1234567890"), QLatin1Char('.'))); +} + +void AlkValueTest::convertDenominator() +{ + AlkValue a(123.456); + QCOMPARE(a.convertDenominator(), AlkValue(12346, 100)); + + AlkValue b; + a = QLatin1String("-73010.28"); + b = QLatin1String("1.95583"); + QCOMPARE((a * b).convertDenominator(100), AlkValue(-14279570, 100)); + + a = QLatin1String("-142795.69"); + QCOMPARE((a / b).convertDenominator(100), AlkValue(-7301028, 100)); + + a = QLatin1String("142795.69"); + QCOMPARE((a / b).convertDenominator(100), AlkValue(7301028, 100)); + + a = AlkValue(1.9999999999998); + QVERIFY(a != AlkValue(2, 1)); + QVERIFY(a < AlkValue(2, 1)); + QVERIFY(a > AlkValue(QLatin1String("1.999999999"), QLatin1Char('.'))); + + a = AlkValue(1.9999999999998, 100); + QCOMPARE(a, AlkValue(2, 1)); +} + +void AlkValueTest::convertPrecision() +{ + AlkValue a(123.456); + QCOMPARE(a.convertPrecision(), AlkValue(12346, 100)); + + AlkValue b; + a = QLatin1String("-73010.28"); + b = QLatin1String("1.95583"); + QCOMPARE((a * b).convertPrecision(2), AlkValue(-14279570, 100)); + + a = QLatin1String("-142795.69"); + QCOMPARE((a / b).convertPrecision(2), AlkValue(-7301028, 100)); + + a = QLatin1String("142795.69"); + QCOMPARE((a / b).convertPrecision(2), AlkValue(7301028, 100)); + + QCOMPARE(AlkValue(5, 10).convertPrecision(0, AlkValue::RoundFloor), AlkValue()); + QCOMPARE(AlkValue(-5, 10).convertPrecision(0, AlkValue::RoundFloor), AlkValue(-1)); + QCOMPARE(AlkValue(15, 10).convertPrecision(0, AlkValue::RoundFloor), AlkValue(1)); + QCOMPARE(AlkValue(-15, 10).convertPrecision(0, AlkValue::RoundFloor), AlkValue(-2)); + + QCOMPARE(AlkValue(5, 10).convertPrecision(0, AlkValue::RoundCeil), AlkValue(1)); + QCOMPARE(AlkValue(-5, 10).convertPrecision(0, AlkValue::RoundCeil), AlkValue()); + QCOMPARE(AlkValue(15, 10).convertPrecision(0, AlkValue::RoundCeil), AlkValue(2)); + QCOMPARE(AlkValue(-15, 10).convertPrecision(0, AlkValue::RoundCeil), AlkValue(-1)); + + QCOMPARE(AlkValue(5, 10).convertPrecision(0, AlkValue::RoundTruncate), AlkValue()); + QCOMPARE(AlkValue(-5, 10).convertPrecision(0, AlkValue::RoundTruncate), AlkValue()); + QCOMPARE(AlkValue(15, 10).convertPrecision(0, AlkValue::RoundTruncate), AlkValue(1)); + QCOMPARE(AlkValue(-15, 10).convertPrecision(0, AlkValue::RoundTruncate), AlkValue(-1)); + + QCOMPARE(AlkValue(5, 10).convertPrecision(0, AlkValue::RoundPromote), AlkValue(1)); + QCOMPARE(AlkValue(-5, 10).convertPrecision(0, AlkValue::RoundPromote), AlkValue(-1)); + QCOMPARE(AlkValue(15, 10).convertPrecision(0, AlkValue::RoundPromote), AlkValue(2)); + QCOMPARE(AlkValue(-15, 10).convertPrecision(0, AlkValue::RoundPromote), AlkValue(-2)); + + QCOMPARE(AlkValue(4, 10).convertPrecision(0, AlkValue::RoundHalfDown), AlkValue()); + QCOMPARE(AlkValue(5, 10).convertPrecision(0, AlkValue::RoundHalfDown), AlkValue()); + QCOMPARE(AlkValue(6, 10).convertPrecision(0, AlkValue::RoundHalfDown), AlkValue(1)); + QCOMPARE(AlkValue(-4, 10).convertPrecision(0, AlkValue::RoundHalfDown), AlkValue()); + QCOMPARE(AlkValue(-5, 10).convertPrecision(0, AlkValue::RoundHalfDown), AlkValue()); + QCOMPARE(AlkValue(-6, 10).convertPrecision(0, AlkValue::RoundHalfDown), AlkValue(-1)); + QCOMPARE(AlkValue(14, 10).convertPrecision(0, AlkValue::RoundHalfDown), AlkValue(1)); + QCOMPARE(AlkValue(15, 10).convertPrecision(0, AlkValue::RoundHalfDown), AlkValue(1)); + QCOMPARE(AlkValue(16, 10).convertPrecision(0, AlkValue::RoundHalfDown), AlkValue(2)); + QCOMPARE(AlkValue(-14, 10).convertPrecision(0, AlkValue::RoundHalfDown), AlkValue(-1)); + QCOMPARE(AlkValue(-15, 10).convertPrecision(0, AlkValue::RoundHalfDown), AlkValue(-1)); + QCOMPARE(AlkValue(-16, 10).convertPrecision(0, AlkValue::RoundHalfDown), AlkValue(-2)); + + QCOMPARE(AlkValue(4, 10).convertPrecision(0, AlkValue::RoundHalfUp), AlkValue()); + QCOMPARE(AlkValue(5, 10).convertPrecision(0, AlkValue::RoundHalfUp), AlkValue(1)); + QCOMPARE(AlkValue(6, 10).convertPrecision(0, AlkValue::RoundHalfUp), AlkValue(1)); + QCOMPARE(AlkValue(-4, 10).convertPrecision(0, AlkValue::RoundHalfUp), AlkValue()); + QCOMPARE(AlkValue(-5, 10).convertPrecision(0, AlkValue::RoundHalfUp), AlkValue(-1)); + QCOMPARE(AlkValue(-6, 10).convertPrecision(0, AlkValue::RoundHalfUp), AlkValue(-1)); + QCOMPARE(AlkValue(14, 10).convertPrecision(0, AlkValue::RoundHalfUp), AlkValue(1)); + QCOMPARE(AlkValue(15, 10).convertPrecision(0, AlkValue::RoundHalfUp), AlkValue(2)); + QCOMPARE(AlkValue(16, 10).convertPrecision(0, AlkValue::RoundHalfUp), AlkValue(2)); + QCOMPARE(AlkValue(-14, 10).convertPrecision(0, AlkValue::RoundHalfUp), AlkValue(-1)); + QCOMPARE(AlkValue(-15, 10).convertPrecision(0, AlkValue::RoundHalfUp), AlkValue(-2)); + QCOMPARE(AlkValue(-16, 10).convertPrecision(0, AlkValue::RoundHalfUp), AlkValue(-2)); + + QCOMPARE(AlkValue(5, 10).convertPrecision(0, AlkValue::RoundRound), AlkValue()); + QCOMPARE(AlkValue(-5, 10).convertPrecision(0, AlkValue::RoundRound), AlkValue()); + QCOMPARE(AlkValue(15, 10).convertPrecision(0, AlkValue::RoundRound), AlkValue(2)); + QCOMPARE(AlkValue(-15, 10).convertPrecision(0, AlkValue::RoundRound), AlkValue(-2)); + QCOMPARE(AlkValue(25, 10).convertPrecision(0, AlkValue::RoundRound), AlkValue(2)); + QCOMPARE(AlkValue(-25, 10).convertPrecision(0, AlkValue::RoundRound), AlkValue(-2)); +} + +void AlkValueTest::denominatorToPrecision() +{ + QVERIFY(AlkValue::denominatorToPrecision(100) == 2); + QVERIFY(AlkValue::denominatorToPrecision(1000000) == 6); + QVERIFY(AlkValue::denominatorToPrecision(1) == 0); + QVERIFY(AlkValue::denominatorToPrecision(0) == 0); + QVERIFY(AlkValue::denominatorToPrecision(-1) == 0); + QVERIFY(AlkValue::denominatorToPrecision(-188) == 0); + QVERIFY(AlkValue::denominatorToPrecision(200) == 3); +} + +void AlkValueTest::precisionToDenominator() +{ + QVERIFY(AlkValue::precisionToDenominator(2) == 100); + QVERIFY(AlkValue::precisionToDenominator(6) == 1000000); + QVERIFY(AlkValue::precisionToDenominator(0) == 1); + QVERIFY(AlkValue::precisionToDenominator(-1) == 1); + QVERIFY(AlkValue::precisionToDenominator(-5) == 1); +} + +void AlkValueTest::valueRef() +{ + AlkValue a(5); + + mpq_class &val = a.valueRef(); + val = mpq_class(1, 3); + + QCOMPARE(a, AlkValue(1, 3)); + + a = QLatin1String("1/7"); + + QCOMPARE(val, mpq_class(1, 7)); +} + +void AlkValueTest::canonicalize() +{ + AlkValue a(5); + mpq_class &val(a.valueRef()); + QCOMPARE(val, mpq_class(5, 1)); + + mpz_class i; + i = 10; + mpq_set_num(val.get_mpq_t(), i.get_mpz_t()); + i = 2; + mpq_set_den(val.get_mpq_t(), i.get_mpz_t()); + QVERIFY(val != mpq_class(5, 1)); + QCOMPARE(val, mpq_class(10, 2)); + + a.canonicalize(); + QCOMPARE(val, mpq_class(5, 1)); +} diff --git a/kmymoney/converter/CMakeLists.txt b/kmymoney/converter/CMakeLists.txt --- a/kmymoney/converter/CMakeLists.txt +++ b/kmymoney/converter/CMakeLists.txt @@ -23,7 +23,7 @@ KF5::TextWidgets KF5::WidgetsAddons KF5::ConfigCore - Alkimia::alkimia + alkimia KF5::KIOWidgets kmm_csvimportercore PRIVATE diff --git a/kmymoney/dialogs/CMakeLists.txt b/kmymoney/dialogs/CMakeLists.txt --- a/kmymoney/dialogs/CMakeLists.txt +++ b/kmymoney/dialogs/CMakeLists.txt @@ -81,7 +81,7 @@ KF5::TextWidgets KF5::Completion Qt5::Widgets - Alkimia::alkimia + alkimia kmm_mymoney onlinetask_interfaces kmm_widgets diff --git a/kmymoney/dialogs/settings/CMakeLists.txt b/kmymoney/dialogs/settings/CMakeLists.txt --- a/kmymoney/dialogs/settings/CMakeLists.txt +++ b/kmymoney/dialogs/settings/CMakeLists.txt @@ -37,7 +37,7 @@ KF5::Completion KF5::KCMUtils KF5::ItemViews - Alkimia::alkimia + alkimia ) if (ENABLE_HOLIDAYS) diff --git a/kmymoney/mymoney/CMakeLists.txt b/kmymoney/mymoney/CMakeLists.txt --- a/kmymoney/mymoney/CMakeLists.txt +++ b/kmymoney/mymoney/CMakeLists.txt @@ -88,7 +88,7 @@ Qt5::Gui KF5::Service KF5::I18n - Alkimia::alkimia + alkimia kmm_payeeidentifier kmm_plugin # TODO: fix this diff --git a/kmymoney/plugins/csv/export/CMakeLists.txt b/kmymoney/plugins/csv/export/CMakeLists.txt --- a/kmymoney/plugins/csv/export/CMakeLists.txt +++ b/kmymoney/plugins/csv/export/CMakeLists.txt @@ -26,7 +26,7 @@ target_link_libraries(csvexporter PUBLIC kmm_mymoney - Alkimia::alkimia + alkimia PRIVATE KF5::Completion KF5::KIOWidgets diff --git a/kmymoney/plugins/gnc/import/CMakeLists.txt b/kmymoney/plugins/gnc/import/CMakeLists.txt --- a/kmymoney/plugins/gnc/import/CMakeLists.txt +++ b/kmymoney/plugins/gnc/import/CMakeLists.txt @@ -34,5 +34,5 @@ PRIVATE KF5::Completion KF5::Archive - Alkimia::alkimia + alkimia ) diff --git a/kmymoney/plugins/interfaces/CMakeLists.txt b/kmymoney/plugins/interfaces/CMakeLists.txt --- a/kmymoney/plugins/interfaces/CMakeLists.txt +++ b/kmymoney/plugins/interfaces/CMakeLists.txt @@ -7,4 +7,4 @@ add_library(interfaces STATIC ${libinterfaces_a_SOURCES}) # TODO: fix dependencies -target_link_libraries(interfaces KF5::Service KF5::XmlGui Alkimia::alkimia Qt5::Xml KF5::I18n) +target_link_libraries(interfaces KF5::Service KF5::XmlGui alkimia Qt5::Xml KF5::I18n) diff --git a/kmymoney/plugins/kbanking/CMakeLists.txt b/kmymoney/plugins/kbanking/CMakeLists.txt --- a/kmymoney/plugins/kbanking/CMakeLists.txt +++ b/kmymoney/plugins/kbanking/CMakeLists.txt @@ -36,7 +36,7 @@ KF5::ConfigCore KF5::ConfigGui Qt5::Core - Alkimia::alkimia + alkimia gwenhywfar::core gwenhywfar::gui-cpp gwenhywfar::gui-qt5 diff --git a/kmymoney/plugins/ofx/import/CMakeLists.txt b/kmymoney/plugins/ofx/import/CMakeLists.txt --- a/kmymoney/plugins/ofx/import/CMakeLists.txt +++ b/kmymoney/plugins/ofx/import/CMakeLists.txt @@ -37,7 +37,7 @@ kmm_plugin KF5::Wallet Qt5::Xml - Alkimia::alkimia + alkimia ${LIBOFX_LIBRARIES} ) diff --git a/kmymoney/plugins/onlinetasks/interfaces/CMakeLists.txt b/kmymoney/plugins/onlinetasks/interfaces/CMakeLists.txt --- a/kmymoney/plugins/onlinetasks/interfaces/CMakeLists.txt +++ b/kmymoney/plugins/onlinetasks/interfaces/CMakeLists.txt @@ -22,7 +22,7 @@ PUBLIC kmm_payeeidentifier Qt5::Widgets - Alkimia::alkimia + alkimia ) install(FILES ${onlinetask_interfaces_HEADER} diff --git a/kmymoney/plugins/qif/import/CMakeLists.txt b/kmymoney/plugins/qif/import/CMakeLists.txt --- a/kmymoney/plugins/qif/import/CMakeLists.txt +++ b/kmymoney/plugins/qif/import/CMakeLists.txt @@ -32,7 +32,7 @@ PRIVATE KF5::Completion KF5::KIOWidgets - Alkimia::alkimia + alkimia kmm_settings ) diff --git a/kmymoney/plugins/views/reports/core/CMakeLists.txt b/kmymoney/plugins/views/reports/core/CMakeLists.txt --- a/kmymoney/plugins/views/reports/core/CMakeLists.txt +++ b/kmymoney/plugins/views/reports/core/CMakeLists.txt @@ -17,7 +17,7 @@ target_link_libraries(reports PUBLIC KChart - Alkimia::alkimia + alkimia Qt5::PrintSupport kmymoney_common kmm_settings diff --git a/kmymoney/plugins/weboob/dialogs/CMakeLists.txt b/kmymoney/plugins/weboob/dialogs/CMakeLists.txt --- a/kmymoney/plugins/weboob/dialogs/CMakeLists.txt +++ b/kmymoney/plugins/weboob/dialogs/CMakeLists.txt @@ -21,5 +21,5 @@ Qt5::Widgets KF5::WidgetsAddons KF5::I18n - Alkimia::alkimia + alkimia ) diff --git a/kmymoney/plugins/weboob/interface/CMakeLists.txt b/kmymoney/plugins/weboob/interface/CMakeLists.txt --- a/kmymoney/plugins/weboob/interface/CMakeLists.txt +++ b/kmymoney/plugins/weboob/interface/CMakeLists.txt @@ -15,7 +15,7 @@ weboob_interface PUBLIC Qt5::Core - Alkimia::alkimia + alkimia PRIVATE ${PYTHON_LIBRARIES} ) diff --git a/kmymoney/widgets/CMakeLists.txt b/kmymoney/widgets/CMakeLists.txt --- a/kmymoney/widgets/CMakeLists.txt +++ b/kmymoney/widgets/CMakeLists.txt @@ -114,7 +114,7 @@ KF5::I18n Qt5::Gui Qt5::Core - Alkimia::alkimia + alkimia kmm_mymoney kmm_models kmm_plugin @@ -191,7 +191,7 @@ # We can compile the uncritical sources without KMM_DESIGNER flags add_library(kmymoney_base STATIC ${_uncritial_common_sources}) # TODO: fix dependencies -target_link_libraries(kmymoney_base KF5::XmlGui KF5::TextWidgets KF5::IconThemes KF5::I18n KF5::ConfigWidgets KF5::ConfigCore KF5::Completion KF5::Service Qt5::Gui Qt5::Widgets Qt5::Xml kmm_settings Alkimia::alkimia) +target_link_libraries(kmymoney_base KF5::XmlGui KF5::TextWidgets KF5::IconThemes KF5::I18n KF5::ConfigWidgets KF5::ConfigCore KF5::Completion KF5::Service Qt5::Gui Qt5::Widgets Qt5::Xml kmm_settings alkimia) add_dependencies(kmymoney_base kmm_settings) diff --git a/kmymoney/wizards/endingbalancedlg/CMakeLists.txt b/kmymoney/wizards/endingbalancedlg/CMakeLists.txt --- a/kmymoney/wizards/endingbalancedlg/CMakeLists.txt +++ b/kmymoney/wizards/endingbalancedlg/CMakeLists.txt @@ -19,7 +19,7 @@ ki18n_wrap_ui(libendingbalancedlg_a_SOURCES ${libendingbalancedlg_a_UI} ) add_library(endingbalancedlg STATIC ${libendingbalancedlg_a_SOURCES}) -target_link_libraries(endingbalancedlg KF5::ConfigWidgets KF5::WidgetsAddons KF5::I18n KF5::Completion Qt5::Widgets Qt5::Xml Alkimia::alkimia) +target_link_libraries(endingbalancedlg KF5::ConfigWidgets KF5::WidgetsAddons KF5::I18n KF5::Completion Qt5::Widgets Qt5::Xml alkimia) # we rely on some widgets to be generated add_dependencies(endingbalancedlg widgets dialogs) diff --git a/kmymoney/wizards/newaccountwizard/CMakeLists.txt b/kmymoney/wizards/newaccountwizard/CMakeLists.txt --- a/kmymoney/wizards/newaccountwizard/CMakeLists.txt +++ b/kmymoney/wizards/newaccountwizard/CMakeLists.txt @@ -28,7 +28,7 @@ add_library(newaccountwizard STATIC ${libnewaccountwizard_a_SOURCES}) # TODO: clean dependencies -target_link_libraries(newaccountwizard kmymoneywizard KF5::XmlGui KF5::Service KF5::IconThemes KF5::ConfigGui KF5::WidgetsAddons KF5::TextWidgets KF5::Completion Qt5::Widgets Qt5::Xml Alkimia::alkimia) +target_link_libraries(newaccountwizard kmymoneywizard KF5::XmlGui KF5::Service KF5::IconThemes KF5::ConfigGui KF5::WidgetsAddons KF5::TextWidgets KF5::Completion Qt5::Widgets Qt5::Xml alkimia) # we rely on some dialogs to be generated add_dependencies(newaccountwizard dialogs) diff --git a/kmymoney/wizards/newinvestmentwizard/CMakeLists.txt b/kmymoney/wizards/newinvestmentwizard/CMakeLists.txt --- a/kmymoney/wizards/newinvestmentwizard/CMakeLists.txt +++ b/kmymoney/wizards/newinvestmentwizard/CMakeLists.txt @@ -18,7 +18,7 @@ add_library(newinvestmentwizard STATIC ${libnewinvestmentwizard_a_SOURCES}) # TODO: cleanup dependencies -target_link_libraries(newinvestmentwizard KF5::I18n KF5::ConfigWidgets KF5::Completion KF5::KIOWidgets KF5::WidgetsAddons Alkimia::alkimia Qt5::Widgets Qt5::Xml) +target_link_libraries(newinvestmentwizard KF5::I18n KF5::ConfigWidgets KF5::Completion KF5::KIOWidgets KF5::WidgetsAddons alkimia Qt5::Widgets Qt5::Xml) # we rely on some widgets to be generated add_dependencies(newinvestmentwizard widgets) diff --git a/kmymoney/wizards/newloanwizard/CMakeLists.txt b/kmymoney/wizards/newloanwizard/CMakeLists.txt --- a/kmymoney/wizards/newloanwizard/CMakeLists.txt +++ b/kmymoney/wizards/newloanwizard/CMakeLists.txt @@ -45,7 +45,7 @@ add_library(newloanwizard STATIC ${libnewloanwizard_a_SOURCES}) # TODO: clean dependencies -target_link_libraries(newloanwizard newaccountwizard KF5::XmlGui KF5::IconThemes KF5::Service KF5::I18n KF5::Completion KF5::WidgetsAddons KF5::TextWidgets Qt5::Widgets Qt5::Xml Alkimia::alkimia) +target_link_libraries(newloanwizard newaccountwizard KF5::XmlGui KF5::IconThemes KF5::Service KF5::I18n KF5::Completion KF5::WidgetsAddons KF5::TextWidgets Qt5::Widgets Qt5::Xml alkimia) # we rely on some dialogs to be generated add_dependencies(newloanwizard dialogs) diff --git a/kmymoney/wizards/newuserwizard/CMakeLists.txt b/kmymoney/wizards/newuserwizard/CMakeLists.txt --- a/kmymoney/wizards/newuserwizard/CMakeLists.txt +++ b/kmymoney/wizards/newuserwizard/CMakeLists.txt @@ -23,6 +23,6 @@ add_library(newuserwizard STATIC ${libnewuserwizard_a_SOURCES}) # TODO: clean dependencies -target_link_libraries(newuserwizard kmymoneywizard KF5::ConfigGui KF5::KIOWidgets KF5::TextWidgets KF5::Completion KF5::ConfigWidgets Qt5::Widgets Qt5::Xml Alkimia::alkimia) +target_link_libraries(newuserwizard kmymoneywizard KF5::ConfigGui KF5::KIOWidgets KF5::TextWidgets KF5::Completion KF5::ConfigWidgets Qt5::Widgets Qt5::Xml alkimia) add_dependencies(newuserwizard widgets wizardpages) diff --git a/kmymoney/wizards/wizardpages/CMakeLists.txt b/kmymoney/wizards/wizardpages/CMakeLists.txt --- a/kmymoney/wizards/wizardpages/CMakeLists.txt +++ b/kmymoney/wizards/wizardpages/CMakeLists.txt @@ -16,5 +16,5 @@ add_library(wizardpages STATIC ${libwizardpages_a_SOURCES} ${wizardpages_ui_srcs}) # TODO: fix widgets library public dependencies -target_link_libraries(wizardpages KF5::KIOWidgets KF5::TextWidgets Alkimia::alkimia Qt5::Xml) +target_link_libraries(wizardpages KF5::KIOWidgets KF5::TextWidgets alkimia Qt5::Xml)