File indexing completed on 2024-04-28 16:13:27

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