I agree with Frank B.
It turns out it is usually easier to construct the value using integer types, and convert it to double when needed for computation. One would use something like the following for inputting a new value:
Code:
uint64_t base = 0;
int decimals = -1;
bool negative = false;
if (sign change pressed) {
negative = !negative;
} else
if (decimal point pressed) {
if (decimals < 0) {
decimals = 0;
}
} else
if (digit d is pressed) {
uint64_t temp = (base * 10) + d;
if (temp <= UINT64_C(999999999999999999)) {
base = temp;
if (decimals >= 0) {
++decimals;
}
}
}
Essentially, 'base' contains the digits, 'negative' is true if it is negative, and 'decimals' is -1 if the number is an integer, and the position of the decimal point otherwise. (decimals == 0 means the decimal point is to the right of the value, decimals == 1 means the decimal point is just before the least significant digit, and so on.)
Note that you cannot really just check if the pin corresponding to each button is in the pressed state, because you'll put the above in a loop, and it gets executed several times each second. Instead, you need to track the state of each button, and only run the above when the button state changes from
not-pressed to
pressed.
When we need to use the value, we convert it to a double:
Code:
static double positive_power_of_ten(unsigned int n)
{
double result = 1.0;
double factor = 10.0;
// result = Product(10**(2**k[i]), i), for k[] such that sum(2**k[i], i) == n.
while (n) {
if (n & 1) {
result *= factor;
}
n >>= 1;
factor *= 10;
}
return result;
}
double triplet_to_double(uint64_t base, int decimals, bool negative)
{
double result = (double)base;
// Optional precision check:
if ((uint64_t)result != base) {
// Warning: double type does not have sufficient precision to represent this value; rounding will occur.
}
if (decimals > 0) {
result /= positive_power_of_ten(decimals);
}
// If you had an additional power of ten exponent parameter, exp10, you'd apply it here:
// if (exp10 > 0) {
// result *= positive_power_of_ten(exp10);
// } else
// if (exp10 < 0) {
// result /= positive_power_of_ten(-exp10);
// }
if (negative) {
result = -result;
}
return result;
}
Normally, you do not need the inverse operation, because calculator results are not editable. If you do need it, the key is again in powers of ten; you want
decimals to be the largest possible value for which
round(abs(double)*positive_power_of_ten(decimals)) <= positive_power_of_ten(display_digits) , with
display_digits being the number of digits you can display. The left side is then the
base. Finally, to get rid of unnecessary trailing fractional zeroes (to turn say 1.25000 into 1.25), as long as
(base > 0 && decimals > 0 && base % 10 == 0), divide base by ten and decrement
decimals by one. That's about it.
In more general terms, it might be interesting to implement a class for radix-10 floating point values. These have an integer mantissa
m, and an integer power of ten
p, so that they represent positive value
v =
m × 10
p; with a separate negative flag. The uint32_t type would allow 9 digits of precision in the mantissa; and uint64_t type 18 digits. Most interestingly, an array of uint32_t limbs (radix-10
9) would allow 9
N digits of precision, allowing the internal use of uint64_t for temporaries. Trigonometric functions could be implemented via
CORDIC or series expansion. Base-10 logarithm could exploit log
10(v) =
p + log
10(
m), and natural logarithm use the
iterative approach. Exponential function would use the
traditional sum. Multiplying
n and
m-digit values yields an
m+
n digit result, which then needs to be divided by a suitable power of ten. Division uses
long division.