1717#ifndef COM_SAXBOPHONE_ARBY_ARBY_HPP
1818#define COM_SAXBOPHONE_ARBY_ARBY_HPP
1919
20+ #include < cmath>
2021#include < cstddef>
2122#include < cstdint>
2223
@@ -115,13 +116,24 @@ namespace com::saxbophone::arby {
115116 * @brief Arbitrary-precision unsigned integer type
116117 * @note `std::numeric_limits<Uint>` is specialised such that most of the
117118 * members of that type are implemented to describe the traits of this type.
118- * @note Exceptions include any members which describe a finite number of digits
119- * or a maximmum value, neither of which apply to this type as it is unbounded.
119+ * @note Exceptions include any members of std::numeric_limits<> which
120+ * describe a finite number of digits or a maximmum value, neither of which
121+ * apply to this type as it is unbounded.
122+ * @exception std::logic_error may be thrown from most methods when the
123+ * result of an operation leaves a Uint object with leading zero digits in
124+ * its internal representation. Such cases are the result of bugs in this
125+ * code and should be reported as such.
120126 */
121127 class Uint {
122128 private:
123129 using StorageType = GetStorageType<int >::StorageType;
124130 using OverflowType = GetStorageType<int >::OverflowType;
131+ // traps with an exception if there are leading zeroes in the digits array
132+ constexprvector void _trap_leading_zero () const {
133+ if (_digits.size () > 0 and _digits.front () == 0 ) {
134+ throw std::logic_error (" leading zeroes in internal representation" );
135+ }
136+ }
125137 public:
126138 /* *
127139 * @brief The number base used internally to store the value
@@ -156,9 +168,9 @@ namespace com::saxbophone::arby {
156168 /* *
157169 * @brief Default constructor, initialises to numeric value `0`
158170 */
159- constexprvector Uint () : Uint( 0 ) {}
171+ constexprvector Uint () {} // uses default ctor of vector to init _digits to zero-size
160172 /* *
161- * @brief Value -constructor, initialises with the given value
173+ * @brief Integer -constructor, initialises with the given integer value
162174 * @param value value to initialise with
163175 */
164176 constexprvector Uint (uintmax_t value) : _digits(fit(value, Uint::BASE)) {
@@ -171,6 +183,34 @@ namespace com::saxbophone::arby {
171183 power /= Uint::BASE;
172184 }
173185 }
186+ _trap_leading_zero ();
187+ }
188+ /* *
189+ * @brief Constructor-like static method, creates Uint from floating point value
190+ * @returns Uint with the value of the given float, with the fractional part truncated off
191+ * @param value Positive floating point value to initialise with
192+ * @throws std::domain_error when `value < 0` or when `value` is not a
193+ * finite number.
194+ */
195+ static Uint from_float (long double value) {
196+ // prevent initialising from negative values
197+ if (value < 0 ) {
198+ throw std::domain_error (" Uint cannot be negative" );
199+ }
200+ // prevent initialising from ±inf or NaN
201+ if (not std::isfinite (value)) {
202+ throw std::domain_error (" Uint cannot be Infinite or NaN" );
203+ }
204+ Uint output;
205+ while (value > 0 ) {
206+ StorageType digit = (StorageType)std::fmod (value, Uint::BASE);
207+ output._digits .insert (output._digits .begin (), digit);
208+ value /= Uint::BASE;
209+ // truncate the fractional part of the floating-point value
210+ value = std::trunc (value);
211+ }
212+ output._trap_leading_zero ();
213+ return output;
174214 }
175215 /* *
176216 * @brief String-constructor, initialises from string decimal value
@@ -179,6 +219,19 @@ namespace com::saxbophone::arby {
179219 * @warning Unimplemented
180220 */
181221 Uint (std::string digits);
222+ private:
223+ // private helper method to abstract the common part of the casting op
224+ template <typename T>
225+ constexprvector T _cast_to () const {
226+ T accumulator = 0 ;
227+ // read digits out in big-endian order, shifting as we go
228+ for (auto digit : _digits) {
229+ accumulator *= Uint::BASE;
230+ accumulator += digit;
231+ }
232+ return accumulator;
233+ }
234+ public:
182235 /* *
183236 * @returns Value of this Uint object cast to uintmax_t
184237 * @throws std::range_error when Uint value is out of range for
@@ -189,15 +242,13 @@ namespace com::saxbophone::arby {
189242 if (*this > std::numeric_limits<uintmax_t >::max ()) {
190243 throw std::range_error (" value too large for uintmax_t" );
191244 }
192- uintmax_t accumulator = 0 ;
193- uintmax_t current_radix = 1 ;
194- // digits are stored in big-endian order, but we read them out in little-endian
195- // TODO: loops like this make my head hurt. Let's read it out in big-endian order instead
196- for (auto digit = _digits.rbegin (); digit != _digits.rend (); ++digit) {
197- accumulator += *digit * current_radix;
198- current_radix *= Uint::BASE;
199- }
200- return accumulator;
245+ return this ->_cast_to <uintmax_t >();
246+ }
247+ /* *
248+ * @returns Value of this Uint object cast to long long double
249+ */
250+ explicit constexprvector operator long double () const {
251+ return this ->_cast_to <long double >();
201252 }
202253 /* *
203254 * @brief custom ostream operator that allows this class to be printed
@@ -266,6 +317,7 @@ namespace com::saxbophone::arby {
266317 _digits.erase (_digits.begin ());
267318 }
268319 }
320+ _trap_leading_zero ();
269321 return *this ; // return new value by reference
270322 }
271323 /* *
@@ -308,6 +360,7 @@ namespace com::saxbophone::arby {
308360 _digits.insert (_digits.begin (), carry);
309361 }
310362 }
363+ _trap_leading_zero ();
311364 return *this ; // return the result by reference
312365 }
313366 /* *
@@ -324,6 +377,7 @@ namespace com::saxbophone::arby {
324377 * @details Subtracts other value from this Uint and assigns the result to self
325378 * @param rhs value to subtract from this Uint
326379 * @returns resulting object after subtraction-assignment
380+ * @throws std::underflow_error when rhs is bigger than this
327381 */
328382 constexprvector Uint& operator -=(Uint rhs) {
329383 // TODO: detect underflow early?
@@ -361,6 +415,7 @@ namespace com::saxbophone::arby {
361415 * @brief Subtraction operator for Uint
362416 * @param lhs,rhs operands for the subtraction
363417 * @returns result of lhs - rhs
418+ * @throws std::underflow_error when rhs is bigger than lhs
364419 */
365420 friend constexprvector Uint operator -(Uint lhs, const Uint& rhs) {
366421 lhs -= rhs; // reuse compound assignment
@@ -446,6 +501,7 @@ namespace com::saxbophone::arby {
446501 * @brief division and modulo all-in-one, equivalent to C/C++ div() and Python divmod()
447502 * @param lhs,rhs operands for the division/modulo operation
448503 * @returns tuple of {quotient, remainder}
504+ * @throws std::domain_error when rhs is zero
449505 */
450506 static constexprvector std::tuple<Uint, Uint> divmod (const Uint& lhs, const Uint& rhs) {
451507 // division by zero is undefined
@@ -486,6 +542,7 @@ namespace com::saxbophone::arby {
486542 * @note This implements floor-division, returning the quotient only
487543 * @param rhs value to divide this Uint by
488544 * @returns resulting object after division-assignment
545+ * @throws std::domain_error when rhs is zero
489546 */
490547 constexprvector Uint& operator /=(const Uint& rhs) {
491548 Uint quotient = *this / rhs; // uses friend /operator
@@ -510,6 +567,7 @@ namespace com::saxbophone::arby {
510567 * @note This returns the modulo/remainder of the division operation
511568 * @param rhs value to modulo-divide this Uint by
512569 * @returns resulting object after modulo-assignment
570+ * @throws std::domain_error when rhs is zero
513571 */
514572 constexprvector Uint& operator %=(const Uint& rhs) {
515573 Uint remainder = *this % rhs; // uses friend %operator
@@ -522,6 +580,7 @@ namespace com::saxbophone::arby {
522580 * @note This implements modulo-division, returning the remainder only
523581 * @param lhs,rhs operands for the division
524582 * @returns remainder of lhs / rhs
583+ * @throws std::domain_error when rhs is zero
525584 */
526585 friend constexprvector Uint operator %(Uint lhs, const Uint& rhs) {
527586 Uint remainder;
0 commit comments