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.