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 }