1 #pragma once
2 // rational_impl.hpp: definition of adaptive precision rational arithmetic type
3 //
4 // Copyright (C) 2017-2021 Stillwater Supercomputing, Inc.
5 //
6 // This file is part of the universal numbers project, which is released under an MIT Open Source license.
7 #include <cstdint>
8 #include <sstream>
9 #include <cassert>
10 #include <iostream>
11 #include <iomanip>
12 #include <vector>
13 #include <limits>
14 #include <regex>
15 #include <algorithm>
16
17 #include <universal/native/ieee754.hpp>
18 #include <universal/string/strmanip.hpp>
19 #include <universal/number/rational/exceptions.hpp>
20 #include <universal/number/decimal/decimal.hpp>
21
22 namespace sw::universal {
23
24 /////////////////////////////////////////////
25 // Forward references
26 class rational;
27 rational quotient(const rational&, const rational&);
28
29 /// <summary>
30 /// Adaptive precision rational number system type
31 /// </summary>
32 /// The rational is comprised of two adaptive rationals representing the numerator and denominator.
33 /// The digits of both are managed as a vector with the digit for 10^0 stored at index 0, 10^1 stored at index 1, etc.
34 class rational {
35 public:
rational()36 rational() { setzero(); }
37
38 rational(const rational&) = default;
39 rational(rational&&) = default;
40
41 rational& operator=(const rational&) = default;
42 rational& operator=(rational&&) = default;
43
44 // initializers for native types
rational(char initial_value)45 rational(char initial_value) { *this = initial_value; }
rational(short initial_value)46 rational(short initial_value) { *this = initial_value; }
rational(int initial_value)47 rational(int initial_value) { *this = initial_value; }
rational(long initial_value)48 rational(long initial_value) { *this = initial_value; }
rational(long long initial_value)49 rational(long long initial_value) { *this = initial_value; }
rational(unsigned char initial_value)50 rational(unsigned char initial_value) { *this = initial_value; }
rational(unsigned short initial_value)51 rational(unsigned short initial_value) { *this = initial_value; }
rational(unsigned int initial_value)52 rational(unsigned int initial_value) { *this = initial_value; }
rational(unsigned long initial_value)53 rational(unsigned long initial_value) { *this = initial_value; }
rational(unsigned long long initial_value)54 rational(unsigned long long initial_value) { *this = initial_value; }
rational(float initial_value)55 rational(float initial_value) { *this = initial_value; }
rational(double initial_value)56 rational(double initial_value) { *this = initial_value; }
57
58
59 // assignment operators for native types
operator =(const std::string & digits)60 rational& operator=(const std::string& digits) {
61 parse(digits);
62 return *this;
63 }
operator =(signed char rhs)64 rational& operator=(signed char rhs) { return from_signed<signed char>(rhs); }
operator =(short rhs)65 rational& operator=(short rhs) { return from_signed<short>(rhs); }
operator =(int rhs)66 rational& operator=(int rhs) { return from_signed<int>(rhs); }
operator =(long rhs)67 rational& operator=(long rhs) { return from_signed<long>(rhs); }
operator =(long long rhs)68 rational& operator=(long long rhs) { return from_signed<long long>(rhs); }
operator =(unsigned char rhs)69 rational& operator=(unsigned char rhs) { return from_unsigned<unsigned char>(rhs); }
operator =(unsigned short rhs)70 rational& operator=(unsigned short rhs) { return from_unsigned<unsigned short>(rhs); }
operator =(unsigned int rhs)71 rational& operator=(unsigned int rhs) { return from_unsigned<unsigned int>(rhs); }
operator =(unsigned long rhs)72 rational& operator=(unsigned long rhs) { return from_unsigned<unsigned long>(rhs); }
operator =(unsigned long long rhs)73 rational& operator=(unsigned long long rhs) { return from_unsigned<unsigned long long>(rhs); }
operator =(float rhs)74 rational& operator=(float rhs) { return from_ieee754<float>(rhs); }
operator =(double rhs)75 rational& operator=(double rhs) { return from_ieee754<double>(rhs); }
76
77 #if LONG_DOUBLE_SUPPORT
rational(long double initial_value)78 rational(long double initial_value) { *this = initial_value; }
operator =(long double rhs)79 rational& operator=(long double rhs) { return from_ieee754<long double>(rhs); }
80 #endif
81
82 // unitary operators
operator -() const83 rational operator-() const {
84 rational tmp(*this);
85 tmp.setsign(!tmp.sign());
86 return tmp;
87 }
operator ++(int)88 rational operator++(int) { // postfix
89 rational tmp(*this);
90 ++numerator;
91 return tmp;
92 }
operator ++()93 rational& operator++() { // prefix
94 ++numerator;
95 return *this;
96 }
operator --(int)97 rational operator--(int) { // postfix
98 rational tmp(*this);
99 --numerator;
100 return tmp;
101 }
operator --()102 rational& operator--() { // prefix
103 --numerator;
104 return *this;
105 }
106
107 // arithmetic operators
operator +=(const rational & rhs)108 rational& operator+=(const rational& rhs) {
109 decimal a = (negative ? -numerator : numerator);
110 decimal b = denominator;
111 decimal c = (rhs.negative ? -rhs.numerator : rhs.numerator);
112 decimal d = rhs.denominator;
113 if (denominator == rhs.denominator) {
114 decimal num = a + c;
115 negative = num.isneg();
116 numerator = (negative ? -num : num);
117 }
118 else {
119 decimal e = a * d + b * c;
120 decimal f = b * d;
121 negative = e.isneg();
122 numerator = (negative ? -e : e);
123 denominator = f;
124 }
125 normalize();
126 return *this;
127 }
operator -=(const rational & rhs)128 rational& operator-=(const rational& rhs) {
129 decimal a = (negative ? -numerator : numerator);
130 decimal b = denominator;
131 decimal c = (rhs.negative ? -rhs.numerator : rhs.numerator);
132 decimal d = rhs.denominator;
133 if (denominator == rhs.denominator) {
134 decimal num = a - c;
135 negative = num.isneg();
136 numerator = (negative ? -num : num);
137 }
138 else {
139 decimal e = a * d - b * c;
140 decimal f = b * d;
141 negative = e.isneg();
142 numerator = (negative ? -e : e);
143 denominator = f;
144 }
145 normalize();
146 return *this;
147 }
operator *=(const rational & rhs)148 rational& operator*=(const rational& rhs) {
149 numerator *= rhs.numerator;
150 denominator *= rhs.denominator;
151 negative = !((negative && rhs.negative) || (!negative && !rhs.negative));
152 normalize();
153 return *this;
154 }
operator /=(const rational & rhs)155 rational& operator/=(const rational& rhs) {
156 #if RATIONAL_THROW_ARITHMETIC_EXCEPTION
157 if (rhs.iszero()) {
158 throw rational_divide_by_zero();
159 }
160 #else
161 std::cerr << "rational_divide_by_zero\n";
162 #endif
163 negative = !((negative && rhs.negative) || (!negative && !rhs.negative));
164 numerator *= rhs.denominator;
165 denominator *= rhs.numerator;
166 normalize();
167 return *this;
168 }
169
170 // conversion operators
operator unsigned short() const171 explicit operator unsigned short() const { return to_unsigned<unsigned short>(); }
operator unsigned int() const172 explicit operator unsigned int() const { return to_unsigned<unsigned int>(); }
operator unsigned long() const173 explicit operator unsigned long() const { return to_unsigned<unsigned long>(); }
operator unsigned long long() const174 explicit operator unsigned long long() const { return to_unsigned<unsigned long long>(); }
operator short() const175 explicit operator short() const { return to_signed<short>(); }
operator int() const176 explicit operator int() const { return to_signed<int>(); }
operator long() const177 explicit operator long() const { return to_signed<long>(); }
operator long long() const178 explicit operator long long() const { return to_signed<long long>(); }
operator float() const179 explicit operator float() const { return to_ieee754<float>(); }
operator double() const180 explicit operator double() const { return to_ieee754<double>(); }
operator long double() const181 explicit operator long double() const { return to_ieee754<long double>(); }
182
183 // selectors
iszero() const184 inline bool iszero() const {
185 return numerator.iszero();
186 }
sign() const187 inline bool sign() const { return negative; }
isneg() const188 inline bool isneg() const { return negative; } // < 0
ispos() const189 inline bool ispos() const { return !negative; } // >= 0
top() const190 inline decimal top() const { return numerator; }
bottom() const191 inline decimal bottom() const { return denominator; }
192 // modifiers
setzero()193 inline void setzero() {
194 negative = false;
195 numerator = 0;
196 denominator = 1;
197 }
setsign(bool sign)198 inline void setsign(bool sign) { negative = sign; }
setneg()199 inline void setneg() { negative = true; }
setpos()200 inline void setpos() { negative = false; }
setnumerator(const decimal & num)201 inline void setnumerator(const decimal& num) { numerator = num; }
setdenominator(const decimal & denom)202 inline void setdenominator(const decimal& denom) { denominator = denom; }
setbits(uint64_t v)203 inline void setbits(uint64_t v) { *this = v; } // API to be consistent with the other number systems
204
205 // read a rational ASCII format and make a rational type out of it
parse(const std::string & _digits)206 bool parse(const std::string& _digits) {
207 bool bSuccess = false;
208 std::string digits(_digits);
209 trim(digits);
210 // check if the txt is an rational form:[+-]*[0123456789]+
211 std::regex rational_regex("[+-]*[0123456789]+");
212 if (std::regex_match(digits, rational_regex)) {
213 // found a rational representation
214 numerator.clear();
215 auto it = digits.begin();
216 if (*it == '-') {
217 setneg();
218 ++it;
219 }
220 else if (*it == '+') {
221 ++it;
222 }
223 for (; it != digits.end(); ++it) {
224 uint8_t v;
225 switch (*it) {
226 case '0':
227 v = 0;
228 break;
229 case '1':
230 v = 1;
231 break;
232 case '2':
233 v = 2;
234 break;
235 case '3':
236 v = 3;
237 break;
238 case '4':
239 v = 4;
240 break;
241 case '5':
242 v = 5;
243 break;
244 case '6':
245 v = 6;
246 break;
247 case '7':
248 v = 7;
249 break;
250 case '8':
251 v = 8;
252 break;
253 case '9':
254 v = 9;
255 break;
256 default:
257 v = 0;
258 }
259 numerator.push_back(v);
260 }
261 std::reverse(numerator.begin(), numerator.end());
262 bSuccess = true;
263 }
264 return bSuccess;
265 }
266
267 protected:
268 // HELPER methods
269
270 // remove greatest common divisor out of the numerator/denominator pair
normalize()271 inline void normalize() {
272 decimal a, b, r;
273 a = numerator; b = denominator; // precondition is numerator and denominator are positive
274 #if RATIONAL_THROW_ARITHMETIC_EXCEPTION
275 if (b.iszero()) {
276 throw rational_divide_by_zero();
277 }
278 #else
279 std::cerr << "rational_divide_by_zero\n";
280 #endif
281 while (a % b > 0) {
282 r = a % b;
283 a = b;
284 b = r;
285 }
286 numerator /= b;
287 denominator /= b;
288 }
289 // conversion functions
290 // convert to signed int: TODO, SFINEA
291 template<typename SignedInt>
to_signed() const292 inline SignedInt to_signed() const { return static_cast<SignedInt>(numerator / denominator); }
293 // convert to unsigned int: TODO, SFINEA
294 template<typename UnsignedInt>
to_unsigned() const295 inline UnsignedInt to_unsigned() const { return static_cast<UnsignedInt>(numerator / denominator); }
296 // convert to ieee-754: TODO, SFINEA
297 template<typename Ty>
to_ieee754() const298 inline Ty to_ieee754() const { return Ty(numerator) / Ty(denominator); }
299
300 // from signed int: TODO, SFINEA
301 template<typename UnsignedInt>
from_signed(UnsignedInt & rhs)302 rational& from_signed(UnsignedInt& rhs) {
303 if (rhs < 0) {
304 negative = true;
305 numerator = -rhs;
306 }
307 else {
308 negative = false;
309 numerator = rhs;
310 }
311 denominator = 1;
312 return *this;
313 }
314 // from signed int: TODO, SFINEA
315 template<typename UnsignedInt>
from_unsigned(UnsignedInt & rhs)316 rational& from_unsigned(UnsignedInt& rhs) {
317 negative = false;
318 numerator = rhs;
319 denominator = 1;
320 return *this;
321 }
322 // from ieee754: TODO, SFINEA
323 template<typename Real>
from_ieee754(Real & rhs)324 rational& from_ieee754(Real& rhs) {
325 std::cerr << "TBD" << rhs << std::endl;
326 return *this;
327 }
328
329 private:
330 // sign-magnitude number: indicate if number is positive or negative
331 bool negative;
332 decimal numerator; // will be managed as a positive number
333 decimal denominator; // will be managed as a positive number
334
335 friend std::ostream& operator<<(std::ostream& ostr, const rational& d);
336 friend std::istream& operator>>(std::istream& istr, rational& d);
337
338 // rational - rational logic operators
339 friend bool operator==(const rational& lhs, const rational& rhs);
340 friend bool operator!=(const rational& lhs, const rational& rhs);
341 friend bool operator<(const rational& lhs, const rational& rhs);
342 friend bool operator>(const rational& lhs, const rational& rhs);
343 friend bool operator<=(const rational& lhs, const rational& rhs);
344 friend bool operator>=(const rational& lhs, const rational& rhs);
345 };
346
347 ////////////////// helper functions
348
349
350 ////////////////// rational operators
351
352 /// stream operators
353
354 // generate an ASCII rational string
to_string(const rational & d)355 inline std::string to_string(const rational& d) {
356 std::stringstream str;
357 if (d.isneg()) str << '-';
358 str << "TBD";
359 return str.str();
360 }
361
362 // generate an ASCII rational format and send to ostream
operator <<(std::ostream & ostr,const rational & d)363 inline std::ostream& operator<<(std::ostream& ostr, const rational& d) {
364 // make certain that setw and left/right operators work properly
365 std::stringstream str;
366 if (d.isneg()) str << '-';
367 str << d.numerator << '/' << d.denominator;
368 return ostr << str.str();
369 }
370
371 // read an ASCII rational format from an istream
operator >>(std::istream & istr,rational & p)372 inline std::istream& operator>>(std::istream& istr, rational& p) {
373 std::string txt;
374 istr >> txt;
375 if (!p.parse(txt)) {
376 std::cerr << "unable to parse -" << txt << "- into a rational value\n";
377 }
378 return istr;
379 }
380
381 /// rational binary arithmetic operators
382
383 // binary addition of rational numbers
operator +(const rational & lhs,const rational & rhs)384 inline rational operator+(const rational& lhs, const rational& rhs) {
385 rational sum = lhs;
386 sum += rhs;
387 return sum;
388 }
389 // binary subtraction of rational numbers
operator -(const rational & lhs,const rational & rhs)390 inline rational operator-(const rational& lhs, const rational& rhs) {
391 rational diff = lhs;
392 diff -= rhs;
393 return diff;
394 }
395 // binary mulitplication of rational numbers
operator *(const rational & lhs,const rational & rhs)396 inline rational operator*(const rational& lhs, const rational& rhs) {
397 rational mul = lhs;
398 mul *= rhs;
399 return mul;
400 }
401 // binary division of rational numbers
operator /(const rational & lhs,const rational & rhs)402 inline rational operator/(const rational& lhs, const rational& rhs) {
403 rational ratio = lhs;
404 ratio /= rhs;
405 return ratio;
406 }
407
408 //////////////////////////////////////////////////////////////////////////////////
409 /// logic operators
410
411 /// rational - rational logic operators
412
413 // equality test
operator ==(const rational & lhs,const rational & rhs)414 bool operator==(const rational& lhs, const rational& rhs) {
415 return lhs.numerator == rhs.numerator && lhs.denominator == rhs.denominator;
416 }
417 // inequality test
operator !=(const rational & lhs,const rational & rhs)418 bool operator!=(const rational& lhs, const rational& rhs) {
419 return !operator==(lhs, rhs);
420 }
421 // less-than test
operator <(const rational & lhs,const rational & rhs)422 bool operator<(const rational& lhs, const rational& rhs) {
423 // a/b < c/d => ad / bd < cb / bd => ad < cb
424 decimal ad = lhs.numerator * rhs.denominator;
425 decimal bc = lhs.denominator * rhs.numerator;
426 return ad < bc;
427 }
428 // greater-than test
operator >(const rational & lhs,const rational & rhs)429 bool operator>(const rational& lhs, const rational& rhs) {
430 return operator<(rhs, lhs);
431 }
432 // less-or-equal test
operator <=(const rational & lhs,const rational & rhs)433 bool operator<=(const rational& lhs, const rational& rhs) {
434 return operator<(lhs, rhs) || operator==(lhs, rhs);
435 }
436 // greater-or-equal test
operator >=(const rational & lhs,const rational & rhs)437 bool operator>=(const rational& lhs, const rational& rhs) {
438 return !operator<(lhs, rhs);
439 }
440
441 // rational - long logic operators
operator ==(const rational & lhs,long rhs)442 inline bool operator==(const rational& lhs, long rhs) {
443 return lhs == rational(rhs);
444 }
operator !=(const rational & lhs,long rhs)445 inline bool operator!=(const rational& lhs, long rhs) {
446 return !operator==(lhs, rational(rhs));
447 }
operator <(const rational & lhs,long rhs)448 inline bool operator< (const rational& lhs, long rhs) {
449 return operator<(lhs, rational(rhs));
450 }
operator >(const rational & lhs,long rhs)451 inline bool operator> (const rational& lhs, long rhs) {
452 return operator< (rational(rhs), lhs);
453 }
operator <=(const rational & lhs,long rhs)454 inline bool operator<=(const rational& lhs, long rhs) {
455 return operator< (lhs, rational(rhs)) || operator==(lhs, rational(rhs));
456 }
operator >=(const rational & lhs,long rhs)457 inline bool operator>=(const rational& lhs, long rhs) {
458 return !operator<(lhs, rational(rhs));
459 }
460
461 // long - rational logic operators
operator ==(long lhs,const rational & rhs)462 inline bool operator==(long lhs, const rational& rhs) {
463 return rational(lhs) == rhs;
464 }
operator !=(long lhs,const rational & rhs)465 inline bool operator!=(long lhs, const rational& rhs) {
466 return !operator==(rational(lhs), rhs);
467 }
operator <(long lhs,const rational & rhs)468 inline bool operator< (long lhs, const rational& rhs) {
469 return operator<(rational(lhs), rhs);
470 }
operator >(long lhs,const rational & rhs)471 inline bool operator> (long lhs, const rational& rhs) {
472 return operator< (rational(lhs), rhs);
473 }
operator <=(long lhs,const rational & rhs)474 inline bool operator<=(long lhs, const rational& rhs) {
475 return operator< (rational(lhs), rhs) || operator==(rational(lhs), rhs);
476 }
operator >=(long lhs,const rational & rhs)477 inline bool operator>=(long lhs, const rational& rhs) {
478 return !operator<(rational(lhs), rhs);
479 }
480
481 ///////////////////////////////////////////////////////////////////////
482
483
484 // find largest multiplier of rhs being less or equal to lhs by subtraction; assumes 0*rhs <= lhs <= 9*rhs
findLargestMultiple_(const rational & lhs,const rational & rhs)485 rational findLargestMultiple_(const rational& lhs, const rational& rhs) {
486 rational multiplier;
487
488 return multiplier;
489 }
490
491
492 ///////////////////////
493 // decintdiv_t for rational to capture quotient and remainder during long division
494 struct rationalintdiv {
495 rational quot; // quotient
496 rational rem; // remainder
497 };
498
499 // divide integer rational a and b and return result argument
rational_divide(const rational & lhs,const rational & rhs)500 rationalintdiv rational_divide(const rational& lhs, const rational& rhs) {
501 if (rhs.iszero()) {
502 #if RATIONAL_THROW_ARITHMETIC_EXCEPTION
503 throw rational_divide_by_zero{};
504 #else
505 std::cerr << "rational_divide_by_zero\n";
506 #endif // RATIONAL_THROW_ARITHMETIC_EXCEPTION
507 }
508
509 // a/b / c/d => ad / bc
510 rationalintdiv divresult;
511 divresult.quot.setnumerator(lhs.top() * rhs.bottom());
512 divresult.quot.setdenominator(lhs.bottom() * rhs.top());
513 return divresult;
514 }
515
516 // return quotient of a rational integer division
quotient(const rational & _a,const rational & _b)517 rational quotient(const rational& _a, const rational& _b) {
518 return rational_divide(_a, _b).quot;
519 }
520 // return remainder of a rational integer division
remainder(const rational & _a,const rational & _b)521 rational remainder(const rational& _a, const rational& _b) {
522 return rational_divide(_a, _b).rem;
523 }
524
525 } // namespace sw::universal
526
527