Skip to content

Commit 355c894

Browse files
authored
Merge pull request #45 from saxbophone/josh/42-long-double-conversion
long double conversion
2 parents 2f75c8b + 75b424f commit 355c894

File tree

2 files changed

+126
-13
lines changed

2 files changed

+126
-13
lines changed

arby/include/arby/arby.hpp

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
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;

tests/casting.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <cmath>
2+
13
#include <limits>
24
#include <stdexcept>
35

@@ -18,3 +20,55 @@ TEST_CASE("Casting arby::Uint with value higher than UINT_MAX to uintmax_t throw
1820

1921
CHECK_THROWS_AS((uintmax_t)value, std::range_error);
2022
}
23+
24+
TEST_CASE("Casting arby::Uint to long double", "[casting]") {
25+
auto value = GENERATE(take(1000, random((uintmax_t)0, std::numeric_limits<uintmax_t>::max())));
26+
27+
CHECK((long double)arby::Uint(value) == (long double)value);
28+
}
29+
30+
TEST_CASE("arby::Uint::from_float() with negative value throws std::domain_error") {
31+
auto value = GENERATE(
32+
take(1000,
33+
random(
34+
std::numeric_limits<long double>::lowest(),
35+
-std::numeric_limits<long double>::denorm_min()
36+
)
37+
)
38+
);
39+
40+
CHECK_THROWS_AS(arby::Uint::from_float(value), std::domain_error);
41+
}
42+
43+
TEST_CASE("arby::Uint::from_float() with non-finite value throws std::domain_error") {
44+
// need IEC 559 float to be sure that qNaN, sNan, ±Inf all exist
45+
if (std::numeric_limits<long double>::is_iec559) {
46+
auto value = GENERATE(
47+
-std::numeric_limits<long double>::infinity(),
48+
+std::numeric_limits<long double>::infinity(),
49+
std::numeric_limits<long double>::quiet_NaN(),
50+
std::numeric_limits<long double>::signaling_NaN()
51+
);
52+
53+
CHECK_THROWS_AS(arby::Uint::from_float(value), std::domain_error);
54+
} else {
55+
WARN("Can't test for non-finite values because long double isn't IEC 559!");
56+
}
57+
}
58+
59+
TEST_CASE("arby::Uint::from_float() with positive value") {
60+
auto power = GENERATE(0.125, 0.25, 0.5, 1, 2, 4, 8);
61+
auto value = GENERATE_COPY(
62+
take(100,
63+
random(
64+
0.0L,
65+
std::pow((long double)std::numeric_limits<uintmax_t>::max(), power)
66+
)
67+
)
68+
);
69+
70+
arby::Uint object = arby::Uint::from_float(value);
71+
72+
// value should be correct
73+
CHECK((long double)object == Approx(std::trunc(value)));
74+
}

0 commit comments

Comments
 (0)