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