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