Alkimia API

alkvalue.cpp
1/*
2 SPDX-FileCopyrightText: 2010-2021 Thomas Baumgart tbaumgart @kde.org
3
4 This file is part of libalkimia.
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7*/
8
9#include "alkimia/alkvalue.h"
10
11#include <QSharedData>
12#include <iostream>
13
14#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
15#include <QRegularExpression>
16#else
17#include <QRegExp>
18#endif
19
20class AlkValue::Private : public QSharedData
21{
22public:
23 Private()
24 {
25 }
26
27 Private(const Private &other) : QSharedData(other)
28 , m_val(other.m_val)
29 {
30 }
31
32 mpq_class m_val;
33};
34
35/**
36 * Helper function to convert an mpq_class object into
37 * its internal QString representation. Mainly used for
38 * debugging.
39 */
40static QString mpqToString(const mpq_class &val)
41{
42 char *p = 0;
43 // use the gmp provided conversion routine
44 gmp_asprintf(&p, "%Qd", val.get_mpq_t());
45
46 // convert it into a QString
47 QString result = QString::fromLatin1(p);
48
49 // and free up the resources allocated by gmp_asprintf
50 void (*freefunc)(void *, size_t);
51 mp_get_memory_functions(NULL, NULL, &freefunc);
52 (*freefunc)(p, std::strlen(p) + 1);
53
54 if (!result.contains(QLatin1Char('/'))) {
55 result += QString::fromLatin1("/1");
56 }
57
58 // done
59 return result;
60}
61
62#if 0
63/**
64 * Helper function to convert an mpz_class object into
65 * its internal QString representation. Mainly used for
66 * debugging.
67 */
68static QString mpzToString(const mpz_class &val)
69{
70 char *p = 0;
71 // use the gmp provided conversion routine
72 gmp_asprintf(&p, "%Zd", val.get_mpz_t());
73
74 // convert it into a QString
76
77 // and free up the resources allocated by gmp_asprintf
78 __gmp_freefunc_t freefunc;
79 mp_get_memory_functions(NULL, NULL, &freefunc);
80 (*freefunc)(p, std::strlen(p) + 1);
81
82 // done
83 return result;
84}
85
86#endif
87
88QSharedDataPointer<AlkValue::Private> &AlkValue::sharedZero()
89{
90 static QSharedDataPointer<AlkValue::Private> sharedZeroPointer(new AlkValue::Private);
91 return sharedZeroPointer;
92}
93
94AlkValue::AlkValue()
95 : d(sharedZero())
96{
97}
98
99AlkValue::AlkValue(const AlkValue &val)
100 : d(val.d)
101{
102}
103
104AlkValue::AlkValue(const int num, const unsigned int denom)
105 : d(new Private)
106{
107 d->m_val = mpq_class(num, denom);
108 d->m_val.canonicalize();
109}
110
111AlkValue::AlkValue(const mpz_class &num, const mpz_class &denom)
112 : d(new Private)
113{
114 mpz_set(d->m_val.get_num_mpz_t(), num.get_mpz_t());
115 mpz_set(d->m_val.get_den_mpz_t(), denom.get_mpz_t());
116 d->m_val.canonicalize();
117}
118
119AlkValue::AlkValue(const mpq_class &val)
120 : d(new Private)
121{
122 d->m_val = val;
123 d->m_val.canonicalize();
124}
125
126AlkValue::AlkValue(const double &dAmount, const unsigned int denom)
127 : d(new Private)
128{
129 d->m_val = dAmount;
130 d->m_val.canonicalize();
131 if (denom != 0) {
132 *this = convertDenominator(denom);
133 }
134}
135
136AlkValue::AlkValue(const QString &str, const QChar &decimalSymbol)
137 : d(new Private)
138{
139 // empty strings are easy
140 if (str.isEmpty()) {
141 return;
142 }
143
144 // take care of mixed prices of the form "5 8/16" as well
145 // as own internal string representation
146#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
147 static QRegularExpression regExp(QLatin1String("^((\\d+)\\s+|-)?(\\d+/\\d+)"));
148 // +-#2-+ +---#3----+
149 // +-----#1-----+
150 auto match = regExp.match(str);
151 if (match.hasMatch()) {
152 d->m_val = qPrintable(str.mid(match.capturedStart(3)));
153 d->m_val.canonicalize();
154 const QString &part1 = match.captured(1);
155#else
156 QRegExp regExp(QLatin1String("^((\\d+)\\s+|-)?(\\d+/\\d+)"));
157 // +-#2-+ +---#3----+
158 // +-----#1-----+
159 //
160 if (regExp.indexIn(str) > -1) {
161 d->m_val = qPrintable(str.mid(regExp.pos(3)));
162 d->m_val.canonicalize();
163 const QString &part1 = regExp.cap(1);
164#endif
165 if (!part1.isEmpty()) {
166 if (part1 == QLatin1String("-")) {
167 mpq_neg(d->m_val.get_mpq_t(), d->m_val.get_mpq_t());
168 } else {
169 mpq_class summand(qPrintable(part1));
170 mpq_add(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), summand.get_mpq_t());
171 d->m_val.canonicalize();
172 }
173 }
174 return;
175 }
176
177 // qDebug("we got '%s' to convert", qPrintable(str));
178 // everything else gets down here
179 const QString negChars = QString::fromLatin1("\\-\\(\\)");
180 const QString validChars = QString::fromLatin1("\\d\\%1%2").arg(decimalSymbol, negChars);
181#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
182 QRegExp invCharSet(QString::fromLatin1("[^%1]").arg(validChars));
183 QRegExp negCharSet(QString::fromLatin1("[%1]").arg(negChars));
184#else
185 QRegularExpression invCharSet(QString::fromLatin1("[^%1]").arg(validChars));
186 QRegularExpression negCharSet(QString::fromLatin1("[%1]").arg(negChars));
187#endif
188
189 QString res(str);
190 // get rid of any character that is not allowed.
191 res.remove(invCharSet);
192
193 // qDebug("we reduced it to '%s'", qPrintable(res));
194 // check if number is negative
195 bool isNegative = false;
196 if (res.indexOf(negCharSet) != -1) {
197 isNegative = true;
198 res.remove(negCharSet);
199 }
200
201 // qDebug("and modified it to '%s'", qPrintable(res));
202 // if someone uses the decimal symbol more than once, we get
203 // rid of them except the right most one
204 int pos;
205 while (res.count(decimalSymbol) > 1) {
206 pos = res.indexOf(decimalSymbol);
207 res.remove(pos, 1);
208 }
209
210 // take care of any fractional part
211 pos = res.indexOf(decimalSymbol);
212 int len = res.length();
213 QString fraction = QString::fromLatin1("/1");
214 if ((pos != -1) && (pos < len)) {
215 fraction += QString(len - pos - 1, QLatin1Char('0'));
216 res.remove(pos, 1);
217 }
218
219 // check if the resulting numerator contains any leading zeros ...
220 int cnt = 0;
221 len = res.length() - 1;
222 while (res.size() > cnt && res[cnt] == QLatin1Char('0') && cnt < len) {
223 ++cnt;
224 }
225
226 // ... and remove them
227 if (cnt) {
228 res.remove(0, cnt);
229 }
230
231 // in case the numerator is empty, we convert it to "0"
232 if (res.isEmpty()) {
233 res = QLatin1Char('0');
234 }
235 res += fraction;
236
237 // looks like we now have a pretty normalized string that we
238 // can convert right away
239 // qDebug("and try to convert '%s'", qPrintable(res));
240 try {
241 d->m_val = mpq_class(qPrintable(res));
242 } catch (const std::invalid_argument &) {
243 qWarning("Invalid argument '%s' to mpq_class() in AlkValue. Arguments to ctor: '%s', '%c'", qPrintable(
244 res), qPrintable(str), decimalSymbol.toLatin1());
245 d->m_val = mpq_class(0);
246 }
247 d->m_val.canonicalize();
248
249 // now we make sure that we use the right sign
250 if (isNegative) {
251 d->m_val = -d->m_val;
252 }
253}
254
255AlkValue::~AlkValue()
256{
257}
258
259QString AlkValue::toString() const
260{
261 return mpqToString(d->m_val);
262}
263
264double AlkValue::toDouble() const
265{
266 return d->m_val.get_d();
267}
268
269AlkValue AlkValue::convertDenominator(const int _denom, const AlkValue::RoundingMethod how) const
270{
271 mpz_class denom(_denom);
272 return convertDenominator(denom, how);
273}
274
275AlkValue AlkValue::convertDenominator(const mpz_class _denom, const AlkValue::RoundingMethod how) const
276{
277 AlkValue in(*this);
278 mpz_class in_num(mpq_numref(in.d->m_val.get_mpq_t()));
279
280 AlkValue out; // initialize to zero
281
282 int sign = sgn(in_num);
283 if (sign != 0) {
284 // sign is either -1 for negative numbers or +1 in all other cases
285
286 AlkValue temp;
287 mpz_class denom(_denom);
288 // only process in case the denominators are different
289 if (mpz_cmpabs(denom.get_mpz_t(), mpq_denref(d->m_val.get_mpq_t())) != 0) {
290 mpz_class in_denom(mpq_denref(in.d->m_val.get_mpq_t()));
291 mpz_class out_num, out_denom;
292
293 if (sgn(in_denom) == -1) { // my denom is negative
294 in_num = in_num * (-in_denom);
295 in_num = 1;
296 }
297
298 mpz_class remainder;
299 int denom_neg = 0;
300
301 // if the denominator is less than zero, we are to interpret it as
302 // the reciprocal of its magnitude.
303 if (sgn(denom) < 0) {
304 mpz_class temp_a;
305 mpz_class temp_bc;
306 denom = -denom;
307 denom_neg = 1;
308 temp_a = ::abs(in_num);
309 temp_bc = in_denom * denom;
310 remainder = temp_a % temp_bc;
311 out_num = temp_a / temp_bc;
312 out_denom = denom;
313 } else {
314 temp = AlkValue(denom, in_denom);
315 // the canonicalization required here is part of the ctor
316 // temp.d->m_val.canonicalize();
317
318 out_num = ::abs(in_num * temp.d->m_val.get_num());
319 remainder = out_num % temp.d->m_val.get_den();
320 out_num = out_num / temp.d->m_val.get_den();
321 out_denom = denom;
322 }
323
324 if (remainder != 0) {
325 switch (how) {
326 case RoundFloor:
327 if (sign < 0) {
328 out_num = out_num + 1;
329 }
330 break;
331
332 case RoundCeil:
333 if (sign > 0) {
334 out_num = out_num + 1;
335 }
336 break;
337
338 case RoundTruncate:
339 break;
340
341 case RoundPromote:
342 out_num = out_num + 1;
343 break;
344
345 case RoundHalfDown:
346 if (denom_neg) {
347 if ((2 * remainder) > (in_denom * denom)) {
348 out_num = out_num + 1;
349 }
350 } else if ((2 * remainder) > (temp.d->m_val.get_den())) {
351 out_num = out_num + 1;
352 }
353 break;
354
355 case RoundHalfUp:
356 if (denom_neg) {
357 if ((2 * remainder) >= (in_denom * denom)) {
358 out_num = out_num + 1;
359 }
360 } else if ((2 * remainder) >= temp.d->m_val.get_den()) {
361 out_num = out_num + 1;
362 }
363 break;
364
365 case RoundRound:
366 if (denom_neg) {
367 if ((remainder * 2) > (in_denom * denom)) {
368 out_num = out_num + 1;
369 } else if ((2 * remainder) == (in_denom * denom)) {
370 if ((out_num % 2) != 0) {
371 out_num = out_num + 1;
372 }
373 }
374 } else {
375 if ((remainder * 2) > temp.d->m_val.get_den()) {
376 out_num = out_num + 1;
377 } else if ((2 * remainder) == temp.d->m_val.get_den()) {
378 if ((out_num % 2) != 0) {
379 out_num = out_num + 1;
380 }
381 }
382 }
383 break;
384
385 case RoundNever:
386 qWarning("AlkValue: have remainder \"%s\"->convert(%s, %d)",
387 qPrintable(toString()), denom.get_str().c_str(), how);
388 break;
389 }
390 }
391
392 // construct the new output value
393 out = AlkValue(out_num * sign, out_denom);
394 } else {
395 out = *this;
396 }
397 }
398 return out;
399}
400
401AlkValue AlkValue::convertPrecision(const int prec, const RoundingMethod how) const
402{
403 return convertDenominator(precisionToDenominator(prec).get_si(), how);
404}
405
406mpz_class AlkValue::denominatorToPrecision(mpz_class denom)
407{
408 mpz_class rc = 0;
409 while (denom > 1) {
410 ++rc;
411 denom /= 10;
412 }
413 return rc;
414}
415
416mpz_class AlkValue::precisionToDenominator(mpz_class prec)
417{
418 mpz_class denom = 1;
419 while ((prec--) > 0) {
420 denom *= 10;
421 }
422 return denom;
423}
424
425const AlkValue &AlkValue::canonicalize()
426{
427 d->m_val.canonicalize();
428 return *this;
429}
430
431AlkValue AlkValue::operator+(const AlkValue &right) const
432{
433 AlkValue result;
434 mpq_add(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
435 result.d->m_val.canonicalize();
436 return result;
437}
438
439AlkValue AlkValue::operator-(const AlkValue &right) const
440{
441 AlkValue result;
442 mpq_sub(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
443 result.d->m_val.canonicalize();
444 return result;
445}
446
447AlkValue AlkValue::operator*(const AlkValue &right) const
448{
449 AlkValue result;
450 mpq_mul(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
451 result.d->m_val.canonicalize();
452 return result;
453}
454
455AlkValue AlkValue::operator/(const AlkValue &right) const
456{
457 AlkValue result;
458 mpq_div(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
459 result.d->m_val.canonicalize();
460 return result;
461}
462
463AlkValue AlkValue::operator%(int operand) const
464{
465 mpz_class num(mpq_numref(d->m_val.get_mpq_t()));
466 AlkValue result;
467 result.d->m_val = num % operand;
468 return result;
469}
470
471AlkValue AlkValue::operator*(int factor) const
472{
473 AlkValue result;
474 mpq_class right(factor);
475 mpq_mul(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.get_mpq_t());
476 result.d->m_val.canonicalize();
477 return result;
478}
479
480const AlkValue &AlkValue::operator=(const AlkValue &right)
481{
482 d = right.d;
483 return *this;
484}
485
486const AlkValue &AlkValue::operator=(int right)
487{
488 d->m_val = right;
489 d->m_val.canonicalize();
490 return *this;
491}
492
493const AlkValue &AlkValue::operator=(double right)
494{
495 d->m_val = right;
496 d->m_val.canonicalize();
497 return *this;
498}
499
500const AlkValue &AlkValue::operator=(const QString &right)
501{
502 AlkValue other(right, QLatin1Char('.'));
503 d->m_val = other.d->m_val;
504 return *this;
505}
506
507AlkValue AlkValue::abs() const
508{
509 AlkValue result;
510 mpq_abs(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t());
511 result.d->m_val.canonicalize();
512 return result;
513}
514
515bool AlkValue::operator==(const AlkValue &val) const
516{
517 if (d == val.d) {
518 return true;
519 }
520 return mpq_equal(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t());
521}
522
523bool AlkValue::operator!=(const AlkValue &val) const
524{
525 if (d == val.d) {
526 return false;
527 }
528 return !mpq_equal(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t());
529}
530
531bool AlkValue::operator<(const AlkValue &val) const
532{
533 return mpq_cmp(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()) < 0 ? true : false;
534}
535
536bool AlkValue::operator>(const AlkValue &val) const
537{
538 return mpq_cmp(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()) > 0 ? true : false;
539}
540
541bool AlkValue::operator<=(const AlkValue &val) const
542{
543 return mpq_cmp(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()) <= 0 ? true : false;
544}
545
546bool AlkValue::operator>=(const AlkValue &val) const
547{
548 return mpq_cmp(d->m_val.get_mpq_t(), val.d->m_val.get_mpq_t()) >= 0 ? true : false;
549}
550
551AlkValue AlkValue::operator-() const
552{
553 AlkValue result;
554 mpq_neg(result.d->m_val.get_mpq_t(), d->m_val.get_mpq_t());
555 result.d->m_val.canonicalize();
556 return result;
557}
558
559AlkValue &AlkValue::operator+=(const AlkValue &right)
560{
561 mpq_add(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
562 d->m_val.canonicalize();
563 return *this;
564}
565
566AlkValue &AlkValue::operator-=(const AlkValue &right)
567{
568 mpq_sub(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
569 d->m_val.canonicalize();
570 return *this;
571}
572
573AlkValue &AlkValue::operator*=(const AlkValue &right)
574{
575 mpq_mul(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
576 d->m_val.canonicalize();
577 return *this;
578}
579
580AlkValue &AlkValue::operator/=(const AlkValue &right)
581{
582 mpq_div(d->m_val.get_mpq_t(), d->m_val.get_mpq_t(), right.d->m_val.get_mpq_t());
583 d->m_val.canonicalize();
584 return *this;
585}
586
587const mpq_class &AlkValue::valueRef() const
588{
589 return d->m_val;
590}
591
592mpq_class &AlkValue::valueRef()
593{
594 return d->m_val;
595}
char * toString(const EngineQuery &query)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
char toLatin1() const const
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QTextStream & right(QTextStream &stream)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:01:13 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.