/* How to do decimal floating point? Like, so a calculator will get * exact answers on decimal math where possible. * * The standard approach is to use BCD. But I’m thinking there might * be a cheaper approach: float the decimal point at the right of the * number instead of the left, the generalized version of “do * financial arithmetic in integer cents”. * * This file compiles to 1204 bytes of AVR code. */ #include #include "decimal.h" typedef int32_t s32; typedef uint8_t u8; static decimal dec_normalize(decimal n) { while (0 < n.decimals && 0 != n.mantissa && 0 == n.mantissa % 10) { n.mantissa /= 10; n.decimals--; } return n; } static decimal dec_extend(decimal n, u8 decimals) { while (n.decimals < decimals) { n.mantissa *= 10; n.decimals++; } return n; } static decimal dec_error() { decimal result = { .is_error = 1 }; return result; } decimal dec_add(decimal a, decimal b) { if (a.is_error) return a; if (b.is_error) return b; u8 decimals = (a.decimals > b.decimals ? a.decimals : b.decimals); a = dec_extend(a, decimals); b = dec_extend(b, decimals); decimal result = { .mantissa = a.mantissa + b.mantissa, .is_error = 0, .decimals = decimals, }; return dec_normalize(result); } decimal dec_sub(decimal a, decimal b) { decimal bm = { .mantissa = -b.mantissa, .is_error = b.is_error, .decimals = b.decimals, }; return dec_add(a, bm); } decimal dec_mul(decimal a, decimal b) { if (a.is_error) return a; if (b.is_error) return b; decimal result = { .mantissa = a.mantissa * b.mantissa, .is_error = 0, .decimals = a.decimals + b.decimals, }; return dec_normalize(result); } decimal dec_div(decimal dividend, decimal divisor, u8 min_decimals) { if (dividend.is_error) return dividend; if (divisor.is_error) return divisor; if (0 == divisor.mantissa) return dec_error(); dividend = dec_extend(dividend, min_decimals + divisor.decimals); decimal result = { .mantissa = dividend.mantissa / divisor.mantissa, .is_error = 0, .decimals = dividend.decimals - divisor.decimals, }; return dec_normalize(result); } decimal dec_of_int(int i) { decimal result = { .mantissa = i, .is_error = 0, .decimals = 0, }; return dec_normalize(result); }