File indexing completed on 2025-02-16 13:48:47
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 }