File indexing completed on 2024-12-08 04:35:12

0001 /*
0002     SPDX-FileCopyrightText: 2010-2021 Thomas Baumgart tbaumgart @kde.org
0003 
0004     This file is part of libalkimia.
0005 
0006     SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "alkimia/alkvalue.h"
0010 
0011 #include <iostream>
0012 #include <QSharedData>
0013 
0014 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
0015 #include <QRegularExpression>
0016 #else
0017 #include <QRegExp>
0018 #endif
0019 
0020 class AlkValue::Private : public QSharedData
0021 {
0022 public:
0023     Private()
0024     {
0025     }
0026 
0027     Private(const Private &other) : QSharedData(other)
0028         , m_val(other.m_val)
0029     {
0030     }
0031 
0032     mpq_class m_val;
0033 };
0034 
0035 /**
0036   * Helper function to convert an mpq_class object into
0037   * its internal QString representation. Mainly used for
0038   * debugging.
0039   */
0040 static QString mpqToString(const mpq_class &val)
0041 {
0042     char *p = 0;
0043     // use the gmp provided conversion routine
0044     gmp_asprintf(&p, "%Qd", val.get_mpq_t());
0045 
0046     // convert it into a QString
0047     QString result = QString::fromLatin1(p);
0048 
0049     // and free up the resources allocated by gmp_asprintf
0050     void (*freefunc)(void *, size_t);
0051     mp_get_memory_functions(NULL, NULL, &freefunc);
0052     (*freefunc)(p, std::strlen(p) + 1);
0053 
0054     if (!result.contains(QLatin1Char('/'))) {
0055         result += QString::fromLatin1("/1");
0056     }
0057 
0058     // done
0059     return result;
0060 }
0061 
0062 #if 0
0063 /**
0064   * Helper function to convert an mpz_class object into
0065   * its internal QString representation. Mainly used for
0066   * debugging.
0067   */
0068 static QString mpzToString(const mpz_class &val)
0069 {
0070     char *p = 0;
0071     // use the gmp provided conversion routine
0072     gmp_asprintf(&p, "%Zd", val.get_mpz_t());
0073 
0074     // convert it into a QString
0075     QString result(QString::fromLatin1(p));
0076 
0077     // and free up the resources allocated by gmp_asprintf
0078     __gmp_freefunc_t freefunc;
0079     mp_get_memory_functions(NULL, NULL, &freefunc);
0080     (*freefunc)(p, std::strlen(p) + 1);
0081 
0082     // done
0083     return result;
0084 }
0085 
0086 #endif
0087 
0088 QSharedDataPointer<AlkValue::Private> &AlkValue::sharedZero()
0089 {
0090     static QSharedDataPointer<AlkValue::Private> sharedZeroPointer(new AlkValue::Private);
0091     return sharedZeroPointer;
0092 }
0093 
0094 AlkValue::AlkValue()
0095     : d(sharedZero())
0096 {
0097 }
0098 
0099 AlkValue::AlkValue(const AlkValue &val)
0100     : d(val.d)
0101 {
0102 }
0103 
0104 AlkValue::AlkValue(const int num, const unsigned int denom)
0105     : d(new Private)
0106 {
0107     d->m_val = mpq_class(num, denom);
0108     d->m_val.canonicalize();
0109 }
0110 
0111 AlkValue::AlkValue(const mpz_class &num, const mpz_class &denom)
0112     : d(new Private)
0113 {
0114     mpz_set(d->m_val.get_num_mpz_t(), num.get_mpz_t());
0115     mpz_set(d->m_val.get_den_mpz_t(), denom.get_mpz_t());
0116     d->m_val.canonicalize();
0117 }
0118 
0119 AlkValue::AlkValue(const mpq_class &val)
0120     : d(new Private)
0121 {
0122     d->m_val = val;
0123     d->m_val.canonicalize();
0124 }
0125 
0126 AlkValue::AlkValue(const double &dAmount, const unsigned int denom)
0127     : d(new Private)
0128 {
0129     d->m_val = dAmount;
0130     d->m_val.canonicalize();
0131     if (denom != 0) {
0132         *this = convertDenominator(denom);
0133     }
0134 }
0135 
0136 AlkValue::AlkValue(const QString &str, const QChar &decimalSymbol)
0137     : d(new Private)
0138 {
0139     // empty strings are easy
0140     if (str.isEmpty()) {
0141         return;
0142     }
0143 
0144     // take care of mixed prices of the form "5 8/16" as well
0145     // as own internal string representation
0146 #if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
0147     static QRegularExpression regExp(QLatin1String("^((\\d+)\\s+|-)?(\\d+/\\d+)"));
0148     //                                                 +-#2-+        +---#3----+
0149     //                                                +-----#1-----+
0150     auto match = regExp.match(str);
0151     if (match.hasMatch()) {
0152         d->m_val = qPrintable(str.mid(match.capturedStart(3)));
0153         d->m_val.canonicalize();
0154         const QString &part1 = match.captured(1);
0155 #else
0156     QRegExp regExp(QLatin1String("^((\\d+)\\s+|-)?(\\d+/\\d+)"));
0157     //                               +-#2-+        +---#3----+
0158     //                              +-----#1-----+
0159     //
0160     if (regExp.indexIn(str) > -1) {
0161         d->m_val = qPrintable(str.mid(regExp.pos(3)));
0162         d->m_val.canonicalize();
0163         const QString &part1 = regExp.cap(1);
0164 #endif
0165         if (!part1.isEmpty()) {
0166             if (part1 == QLatin1String("-")) {
0167                 mpq_neg(d->m_val.get_mpq_t(), d->m_val.get_mpq_t());
0168             } else {
0169                 mpq_class summand(qPrintable(part1));
0170                 mpq_add(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), summand.get_mpq_t());
0171                 d->m_val.canonicalize();
0172             }
0173         }
0174         return;
0175     }
0176 
0177     // qDebug("we got '%s' to convert", qPrintable(str));
0178     // everything else gets down here
0179     const QString negChars = QString::fromLatin1("\\-\\(\\)");
0180     const QString validChars = QString::fromLatin1("\\d\\%1%2").arg(decimalSymbol, negChars);
0181 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
0182     QRegExp invCharSet(QString::fromLatin1("[^%1]").arg(validChars));
0183     QRegExp negCharSet(QString::fromLatin1("[%1]").arg(negChars));
0184 #else
0185     QRegularExpression invCharSet(QString::fromLatin1("[^%1]").arg(validChars));
0186     QRegularExpression negCharSet(QString::fromLatin1("[%1]").arg(negChars));
0187 #endif
0188 
0189     QString res(str);
0190     // get rid of any character that is not allowed.
0191     res.remove(invCharSet);
0192 
0193     // qDebug("we reduced it to '%s'", qPrintable(res));
0194     // check if number is negative
0195     bool isNegative = false;
0196     if (res.indexOf(negCharSet) != -1) {
0197         isNegative = true;
0198         res.remove(negCharSet);
0199     }
0200 
0201     // qDebug("and modified it to '%s'", qPrintable(res));
0202     // if someone uses the decimal symbol more than once, we get
0203     // rid of them except the right most one
0204     int pos;
0205     while (res.count(decimalSymbol) > 1) {
0206         pos = res.indexOf(decimalSymbol);
0207         res.remove(pos, 1);
0208     }
0209 
0210     // take care of any fractional part
0211     pos = res.indexOf(decimalSymbol);
0212     int len = res.length();
0213     QString fraction = QString::fromLatin1("/1");
0214     if ((pos != -1) && (pos < len)) {
0215         fraction += QString(len - pos - 1, QLatin1Char('0'));
0216         res.remove(pos, 1);
0217     }
0218 
0219     // check if the resulting numerator contains any leading zeros ...
0220     int cnt = 0;
0221     len = res.length() - 1;
0222     while (res.size() > cnt && res[cnt] == QLatin1Char('0') && cnt < len) {
0223         ++cnt;
0224     }
0225 
0226     // ... and remove them
0227     if (cnt) {
0228         res.remove(0, cnt);
0229     }
0230 
0231     // in case the numerator is empty, we convert it to "0"
0232     if (res.isEmpty()) {
0233         res = QLatin1Char('0');
0234     }
0235     res += fraction;
0236 
0237     // looks like we now have a pretty normalized string that we
0238     // can convert right away
0239     // qDebug("and try to convert '%s'", qPrintable(res));
0240     try {
0241         d->m_val = mpq_class(qPrintable(res));
0242     } catch (const std::invalid_argument &) {
0243         qWarning("Invalid argument '%s' to mpq_class() in AlkValue. Arguments to ctor: '%s', '%c'", qPrintable(
0244                      res), qPrintable(str), decimalSymbol.toLatin1());
0245         d->m_val = mpq_class(0);
0246     }
0247     d->m_val.canonicalize();
0248 
0249     // now we make sure that we use the right sign
0250     if (isNegative) {
0251         d->m_val = -d->m_val;
0252     }
0253 }
0254 
0255 AlkValue::~AlkValue()
0256 {
0257 }
0258 
0259 QString AlkValue::toString() const
0260 {
0261     return mpqToString(d->m_val);
0262 }
0263 
0264 double AlkValue::toDouble() const
0265 {
0266     return d->m_val.get_d();
0267 }
0268 
0269 AlkValue AlkValue::convertDenominator(int _denom, const AlkValue::RoundingMethod how) const
0270 {
0271     mpz_class denom(_denom);
0272     return convertDenominator(denom, how);
0273 }
0274 
0275 AlkValue AlkValue::convertDenominator(const mpz_class _denom, const AlkValue::RoundingMethod how) const
0276 {
0277     AlkValue in(*this);
0278     mpz_class in_num(mpq_numref(in.d->m_val.get_mpq_t()));
0279 
0280     AlkValue out; // initialize to zero
0281 
0282     int sign = sgn(in_num);
0283     if (sign != 0) {
0284         // sign is either -1 for negative numbers or +1 in all other cases
0285 
0286         AlkValue temp;
0287         mpz_class denom(_denom);
0288         // only process in case the denominators are different
0289         if (mpz_cmpabs(denom.get_mpz_t(), mpq_denref(d->m_val.get_mpq_t())) != 0) {
0290             mpz_class in_denom(mpq_denref(in.d->m_val.get_mpq_t()));
0291             mpz_class out_num, out_denom;
0292 
0293             if (sgn(in_denom) == -1) { // my denom is negative
0294                 in_num = in_num * (-in_denom);
0295                 in_num = 1;
0296             }
0297 
0298             mpz_class remainder;
0299             int denom_neg = 0;
0300 
0301             // if the denominator is less than zero, we are to interpret it as
0302             // the reciprocal of its magnitude.
0303             if (sgn(denom) < 0) {
0304                 mpz_class temp_a;
0305                 mpz_class temp_bc;
0306                 denom = -denom;
0307                 denom_neg = 1;
0308                 temp_a = ::abs(in_num);
0309                 temp_bc = in_denom * denom;
0310                 remainder = temp_a % temp_bc;
0311                 out_num = temp_a / temp_bc;
0312                 out_denom = denom;
0313             } else {
0314                 temp = AlkValue(denom, in_denom);
0315                 // the canonicalization required here is part of the ctor
0316                 // temp.d->m_val.canonicalize();
0317 
0318                 out_num = ::abs(in_num * temp.d->m_val.get_num());
0319                 remainder = out_num % temp.d->m_val.get_den();
0320                 out_num = out_num / temp.d->m_val.get_den();
0321                 out_denom = denom;
0322             }
0323 
0324             if (remainder != 0) {
0325                 switch (how) {
0326                 case RoundFloor:
0327                     if (sign < 0) {
0328                         out_num = out_num + 1;
0329                     }
0330                     break;
0331 
0332                 case RoundCeil:
0333                     if (sign > 0) {
0334                         out_num = out_num + 1;
0335                     }
0336                     break;
0337 
0338                 case RoundTruncate:
0339                     break;
0340 
0341                 case RoundPromote:
0342                     out_num = out_num + 1;
0343                     break;
0344 
0345                 case RoundHalfDown:
0346                     if (denom_neg) {
0347                         if ((2 * remainder) > (in_denom * denom)) {
0348                             out_num = out_num + 1;
0349                         }
0350                     } else if ((2 * remainder) > (temp.d->m_val.get_den())) {
0351                         out_num = out_num + 1;
0352                     }
0353                     break;
0354 
0355                 case RoundHalfUp:
0356                     if (denom_neg) {
0357                         if ((2 * remainder) >= (in_denom * denom)) {
0358                             out_num = out_num + 1;
0359                         }
0360                     } else if ((2 * remainder) >= temp.d->m_val.get_den()) {
0361                         out_num = out_num + 1;
0362                     }
0363                     break;
0364 
0365                 case RoundRound:
0366                     if (denom_neg) {
0367                         if ((remainder * 2) > (in_denom * denom)) {
0368                             out_num = out_num + 1;
0369                         } else if ((2 * remainder) == (in_denom * denom)) {
0370                             if ((out_num % 2) != 0) {
0371                                 out_num = out_num + 1;
0372                             }
0373                         }
0374                     } else {
0375                         if ((remainder * 2) > temp.d->m_val.get_den()) {
0376                             out_num = out_num + 1;
0377                         } else if ((2 * remainder) == temp.d->m_val.get_den()) {
0378                             if ((out_num % 2) != 0) {
0379                                 out_num = out_num + 1;
0380                             }
0381                         }
0382                     }
0383                     break;
0384 
0385                 case RoundNever:
0386                     qWarning("AlkValue: have remainder \"%s\"->convert(%s, %d)",
0387                              qPrintable(toString()), denom.get_str().c_str(), how);
0388                     break;
0389                 }
0390             }
0391 
0392             // construct the new output value
0393             out = AlkValue(out_num * sign, out_denom);
0394         } else {
0395             out = *this;
0396         }
0397     }
0398     return out;
0399 }
0400 
0401 AlkValue AlkValue::convertPrecision(int prec, const RoundingMethod how) const
0402 {
0403     return convertDenominator(precisionToDenominator(prec).get_si(), how);
0404 }
0405 
0406 mpz_class AlkValue::denominatorToPrecision(mpz_class denom)
0407 {
0408     mpz_class rc = 0;
0409     while (denom > 1) {
0410         ++rc;
0411         denom /= 10;
0412     }
0413     return rc;
0414 }
0415 
0416 mpz_class AlkValue::precisionToDenominator(mpz_class prec)
0417 {
0418     mpz_class denom = 1;
0419     while ((prec--) > 0) {
0420         denom *= 10;
0421     }
0422     return denom;
0423 }
0424 
0425 const AlkValue &AlkValue::canonicalize()
0426 {
0427     d->m_val.canonicalize();
0428     return *this;
0429 }
0430 
0431 AlkValue AlkValue::operator+(const AlkValue &right) const
0432 {
0433     AlkValue result;
0434     mpq_add(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
0435     result.d->m_val.canonicalize();
0436     return result;
0437 }
0438 
0439 AlkValue AlkValue::operator-(const AlkValue &right) const
0440 {
0441     AlkValue result;
0442     mpq_sub(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
0443     result.d->m_val.canonicalize();
0444     return result;
0445 }
0446 
0447 AlkValue AlkValue::operator*(const AlkValue &right) const
0448 {
0449     AlkValue result;
0450     mpq_mul(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
0451     result.d->m_val.canonicalize();
0452     return result;
0453 }
0454 
0455 AlkValue AlkValue::operator/(const AlkValue &right) const
0456 {
0457     AlkValue result;
0458     mpq_div(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
0459     result.d->m_val.canonicalize();
0460     return result;
0461 }
0462 
0463 AlkValue AlkValue::operator%(int operand) const
0464 {
0465     mpz_class num(mpq_numref(d->m_val.get_mpq_t()));
0466     AlkValue result;
0467     result.d->m_val = num % operand;
0468     return result;
0469 }
0470 
0471 AlkValue AlkValue::operator*(int factor) const
0472 {
0473     AlkValue result;
0474     mpq_class right(factor);
0475     mpq_mul(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.get_mpq_t());
0476     result.d->m_val.canonicalize();
0477     return result;
0478 }
0479 
0480 const AlkValue &AlkValue::operator=(const AlkValue &right)
0481 {
0482     d = right.d;
0483     return *this;
0484 }
0485 
0486 const AlkValue &AlkValue::operator=(int right)
0487 {
0488     d->m_val = right;
0489     d->m_val.canonicalize();
0490     return *this;
0491 }
0492 
0493 const AlkValue &AlkValue::operator=(double right)
0494 {
0495     d->m_val = right;
0496     d->m_val.canonicalize();
0497     return *this;
0498 }
0499 
0500 const AlkValue &AlkValue::operator=(const QString &right)
0501 {
0502     AlkValue other(right, QLatin1Char('.'));
0503     d->m_val = other.d->m_val;
0504     return *this;
0505 }
0506 
0507 AlkValue AlkValue::abs() const
0508 {
0509     AlkValue result;
0510     mpq_abs(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t());
0511     result.d->m_val.canonicalize();
0512     return result;
0513 }
0514 
0515 bool AlkValue::operator==(const AlkValue &val) const
0516 {
0517     if (d == val.d) {
0518         return true;
0519     }
0520     return mpq_equal(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t());
0521 }
0522 
0523 bool AlkValue::operator!=(const AlkValue &val) const
0524 {
0525     if (d == val.d) {
0526         return false;
0527     }
0528     return !mpq_equal(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t());
0529 }
0530 
0531 bool AlkValue::operator<(const AlkValue &val) const
0532 {
0533     return mpq_cmp(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()) < 0 ? true : false;
0534 }
0535 
0536 bool AlkValue::operator>(const AlkValue &val) const
0537 {
0538     return mpq_cmp(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()) > 0 ? true : false;
0539 }
0540 
0541 bool AlkValue::operator<=(const AlkValue &val) const
0542 {
0543     return mpq_cmp(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()) <= 0 ? true : false;
0544 }
0545 
0546 bool AlkValue::operator>=(const AlkValue &val) const
0547 {
0548     return mpq_cmp(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()) >= 0 ? true : false;
0549 }
0550 
0551 AlkValue AlkValue::operator-() const
0552 {
0553     AlkValue result;
0554     mpq_neg(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t());
0555     result.d->m_val.canonicalize();
0556     return result;
0557 }
0558 
0559 AlkValue &AlkValue::operator+=(const AlkValue &right)
0560 {
0561     mpq_add(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
0562     d->m_val.canonicalize();
0563     return *this;
0564 }
0565 
0566 AlkValue &AlkValue::operator-=(const AlkValue &right)
0567 {
0568     mpq_sub(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
0569     d->m_val.canonicalize();
0570     return *this;
0571 }
0572 
0573 AlkValue &AlkValue::operator*=(const AlkValue &right)
0574 {
0575     mpq_mul(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
0576     d->m_val.canonicalize();
0577     return *this;
0578 }
0579 
0580 AlkValue &AlkValue::operator/=(const AlkValue &right)
0581 {
0582     mpq_div(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
0583     d->m_val.canonicalize();
0584     return *this;
0585 }
0586 
0587 const mpq_class &AlkValue::valueRef() const
0588 {
0589     return d->m_val;
0590 }
0591 
0592 mpq_class &AlkValue::valueRef()
0593 {
0594     return d->m_val;
0595 }