// Exact arbitrary-precision decimal arithmetic for moonbean.
//
// Values are stored as `mantissa * 10^-scale`. The scale is preserved on
// purpose: Beancount infers tolerances from the number of fractional digits a
// user wrote (10.00 and 10 are different inputs), so a normalising decimal
// (such as one that strips trailing zeros) cannot be used here.
///|
let decimal_precision : Int32 = 28
///|
pub(all) struct Decimal {
mantissa : @bigint.BigInt
scale : Int32
}
///|
pub fn Decimal::new(mantissa : @bigint.BigInt, scale : Int32) -> Decimal {
{ mantissa, scale, }
}
///|
pub fn Decimal::zero() -> Decimal {
{ mantissa: @bigint.BigInt::from_int(0), scale: 0, }
}
///|
pub fn Decimal::zero_scaled(scale : Int32) -> Decimal {
{ mantissa: @bigint.BigInt::from_int(0), scale, }
}
///|
pub fn Decimal::one() -> Decimal {
{ mantissa: @bigint.BigInt::from_int(1), scale: 0, }
}
///|
pub fn Decimal::from_int(value : Int32) -> Decimal {
{ mantissa: @bigint.BigInt::from_int(value), scale: 0, }
}
///|
pub fn Decimal::from_int64(value : Int64) -> Decimal {
{ mantissa: @bigint.BigInt::from_int64(value), scale: 0, }
}
///|
fn pow10(n : Int32) -> @bigint.BigInt {
let mut result = @bigint.BigInt::from_int(1)
let ten = @bigint.BigInt::from_int(10)
let mut i = 0
while i < n {
result = result.mul(ten)
i = i + 1
}
result
}
///|
fn bigint_abs(value : @bigint.BigInt) -> @bigint.BigInt {
if value.compare_int(0) < 0 {
value.neg()
} else {
value
}
}
///|
fn bigint_sign(value : @bigint.BigInt) -> Int32 {
value.compare_int(0)
}
///|
fn digit_count(value : @bigint.BigInt) -> Int32 {
bigint_abs(value).to_string().length()
}
///|
fn bigint_gcd(a : @bigint.BigInt, b : @bigint.BigInt) -> @bigint.BigInt {
let mut x = bigint_abs(a)
let mut y = bigint_abs(b)
while !y.is_zero() {
let r = x.mod(y)
x = y
y = r
}
x
}
///|
fn bigint_mul_pow10(value : @bigint.BigInt, n : Int32) -> @bigint.BigInt {
if n <= 0 {
value
} else {
value.mul(pow10(n))
}
}
///|
fn bigint_div_pow10(value : @bigint.BigInt, n : Int32) -> @bigint.BigInt {
if n <= 0 {
value
} else {
value.div(pow10(n))
}
}
// Rounds |value| / 10^k to the nearest integer, ties to even, and restores the
// sign of `value`.
///|
fn bigint_div_pow10_half_even(
value : @bigint.BigInt,
n : Int32,
) -> @bigint.BigInt {
if n <= 0 {
value
} else {
let neg = bigint_sign(value) < 0
let abs_value = bigint_abs(value)
let den = pow10(n)
let quot = abs_value.div(den)
let rem = abs_value.mod(den)
let twice = rem.mul(@bigint.BigInt::from_int(2))
let up = if twice.compare(den) > 0 {
true
} else if twice.compare(den) == 0 {
quot.mod(@bigint.BigInt::from_int(2)).compare_int(0) != 0
} else {
false
}
let rounded = if up { quot.add(@bigint.BigInt::from_int(1)) } else { quot }
if neg {
rounded.neg()
} else {
rounded
}
}
}
///|
/// Parses a plain decimal literal: optional sign, digits, optional fraction.
/// Thousands separators (commas between digits) are accepted. Exponents are
/// intentionally rejected; Beancount numbers are written in plain notation.
pub fn Decimal::parse(input : String) -> Decimal? {
let text = input.trim()
if text.length() == 0 {
return None
}
let mut idx = 0
let mut negative = false
let first = text.get_char(0)
match first {
Some(c) =>
if c == '-' || c == '+' {
negative = c == '-'
idx = 1
}
None => return None
}
let digits = StringBuilder()
let mut scale = 0
let mut seen_digit = false
let mut seen_dot = false
let mut last_was_comma = false
let chars = text.char_length()
while idx < chars {
let c = match text.get_char(idx) {
Some(c) => c
None => return None
}
if c >= '0' && c <= '9' {
if seen_dot {
scale = scale + 1
}
digits.write_char(c)
seen_digit = true
last_was_comma = false
} else if c == '.' {
if seen_dot {
return None
}
seen_dot = true
last_was_comma = false
} else if c == ',' {
if !seen_digit || seen_dot || last_was_comma {
return None
}
last_was_comma = true
} else {
return None
}
idx = idx + 1
}
if !seen_digit || last_was_comma {
return None
}
let mut mantissa = @bigint.BigInt::from_string(digits.to_string())
if negative && !mantissa.is_zero() {
mantissa = mantissa.neg()
}
Some({ mantissa, scale, })
}
///|
pub fn Decimal::is_zero(self : Decimal) -> Bool {
self.mantissa.is_zero()
}
///|
pub fn Decimal::is_negative(self : Decimal) -> Bool {
bigint_sign(self.mantissa) < 0
}
///|
pub fn Decimal::is_positive(self : Decimal) -> Bool {
bigint_sign(self.mantissa) > 0
}
///|
pub fn Decimal::signum(self : Decimal) -> Int32 {
bigint_sign(self.mantissa)
}
///|
pub fn Decimal::neg(self : Decimal) -> Decimal {
{ mantissa: self.mantissa.neg(), scale: self.scale, }
}
///|
pub fn Decimal::abs(self : Decimal) -> Decimal {
{ mantissa: bigint_abs(self.mantissa), scale: self.scale, }
}
///|
pub fn Decimal::scale(self : Decimal) -> Int32 {
self.scale
}
///|
pub fn Decimal::mantissa(self : Decimal) -> @bigint.BigInt {
self.mantissa
}
///|
/// Multiplies the value by 10^k, preserving the represented value.
pub fn Decimal::shift_scale(self : Decimal, k : Int32) -> Decimal {
{ mantissa: bigint_mul_pow10(self.mantissa, k), scale: self.scale + k, }
}
///|
/// Rounds to the nearest value with the requested scale, ties to even.
pub fn Decimal::quantize(self : Decimal, target_scale : Int32) -> Decimal {
if target_scale == self.scale {
return self
}
if target_scale > self.scale {
return {
mantissa: bigint_mul_pow10(self.mantissa, target_scale - self.scale),
scale: target_scale,
}
}
let drop = self.scale - target_scale
{
mantissa: bigint_div_pow10_half_even(self.mantissa, drop),
scale: target_scale,
}
}
///|
/// Rounds to `digits` significant digits, ties to even.
pub fn Decimal::round_to_significant(self : Decimal, digits : Int32) -> Decimal {
if self.mantissa.is_zero() {
return self
}
let current = digit_count(self.mantissa)
if current <= digits {
return self
}
let drop = current - digits
let mut rounded = bigint_div_pow10_half_even(self.mantissa, drop)
let mut scale = self.scale - drop
if digit_count(rounded) > digits {
// Rounding carried into a new digit.
rounded = bigint_div_pow10(rounded, 1)
scale = scale - 1
}
{ mantissa: rounded, scale, }
}
///|
pub fn Decimal::round_to_integer(self : Decimal) -> Decimal {
self.quantize(0)
}
///|
fn align_pair(
a : Decimal,
b : Decimal,
) -> (@bigint.BigInt, @bigint.BigInt, Int32) {
let target = if a.scale > b.scale { a.scale } else { b.scale }
let left = bigint_mul_pow10(a.mantissa, target - a.scale)
let right = bigint_mul_pow10(b.mantissa, target - b.scale)
(left, right, target)
}
///|
pub fn Decimal::add(a : Decimal, b : Decimal) -> Decimal {
let (left, right, scale) = align_pair(a, b)
let raw = { mantissa: left.add(right), scale, }
raw.round_to_significant(decimal_precision)
}
///|
pub fn Decimal::sub(a : Decimal, b : Decimal) -> Decimal {
let (left, right, scale) = align_pair(a, b)
let raw = { mantissa: left.sub(right), scale, }
raw.round_to_significant(decimal_precision)
}
///|
pub fn Decimal::mul(a : Decimal, b : Decimal) -> Decimal {
let raw = { mantissa: a.mantissa.mul(b.mantissa), scale: a.scale + b.scale, }
raw.round_to_significant(decimal_precision)
}
///|
fn count_factor(
value : @bigint.BigInt,
factor : Int32,
) -> (Int32, @bigint.BigInt) {
let f = @bigint.BigInt::from_int(factor)
let mut remaining = value
let mut count = 0
while !remaining.is_zero() {
let q = remaining.div(f)
let r = remaining.mod(f)
if r.is_zero() {
remaining = q
count = count + 1
} else {
break
}
}
(count, remaining)
}
///|
/// Decimal division following Python's `decimal` semantics at precision 28:
/// exact results keep at least the "ideal" exponent, inexact results are
/// rounded to 28 significant digits with ROUND_HALF_EVEN.
pub fn Decimal::div(a : Decimal, b : Decimal) -> Decimal? {
if b.mantissa.is_zero() {
return None
}
if a.mantissa.is_zero() {
let ideal = a.scale - b.scale
let scale = if ideal > 0 { ideal } else { 0 }
return Some({ mantissa: @bigint.BigInt::from_int(0), scale, })
}
let neg = bigint_sign(a.mantissa) != bigint_sign(b.mantissa)
let num0 = bigint_abs(a.mantissa)
let den0 = bigint_abs(b.mantissa)
let ideal_scale = a.scale - b.scale
// Build a plain fraction num/den.
let (num1, den1) = if b.scale >= a.scale {
(bigint_mul_pow10(num0, b.scale - a.scale), den0)
} else {
(num0, bigint_mul_pow10(den0, a.scale - b.scale))
}
let g = bigint_gcd(num1, den1)
let num = num1.div(g)
let den = den1.div(g)
let (twos, rest2) = count_factor(den, 2)
let (fives, rest5) = count_factor(rest2, 5)
let terminating = rest5.compare_int(1) == 0
let result = if terminating {
let exact_scale = if twos > fives { twos } else { fives }
let exact_mantissa = bigint_mul_pow10(num, exact_scale).div(den)
let scale = if ideal_scale > exact_scale {
ideal_scale
} else {
exact_scale
}
let mantissa = bigint_mul_pow10(exact_mantissa, scale - exact_scale)
let mut d = { mantissa, scale, }
d = d.round_to_significant(decimal_precision)
d
} else {
// Adjusted exponent d such that 10^d <= num/den < 10^(d+1).
let mut exp = digit_count(num) - digit_count(den)
let lower_ok = if exp >= 0 {
num.compare(bigint_mul_pow10(den, exp)) >= 0
} else {
bigint_mul_pow10(num, -exp).compare(den) >= 0
}
if !lower_ok {
exp = exp - 1
}
let target_scale = decimal_precision - 1 - exp
let (scaled_num, scaled_den) = if target_scale >= 0 {
(bigint_mul_pow10(num, target_scale), den)
} else {
(num, bigint_mul_pow10(den, -target_scale))
}
let q = scaled_num.div(scaled_den)
let r = scaled_num.mod(scaled_den)
let twice = r.mul(@bigint.BigInt::from_int(2))
let up = if twice.compare(scaled_den) > 0 {
true
} else if twice.compare(scaled_den) == 0 {
q.mod(@bigint.BigInt::from_int(2)).compare_int(0) != 0
} else {
false
}
let mut mantissa = if up { q.add(@bigint.BigInt::from_int(1)) } else { q }
let mut scale = target_scale
if digit_count(mantissa) > decimal_precision {
mantissa = bigint_div_pow10(mantissa, 1)
scale = scale - 1
}
{ mantissa, scale, }
}
let signed = if neg { result.neg() } else { result }
Some(signed)
}
///|
pub fn Decimal::compare(a : Decimal, b : Decimal) -> Int32 {
let (left, right, _) = align_pair(a, b)
left.compare(right)
}
///|
pub fn Decimal::equal(a : Decimal, b : Decimal) -> Bool {
a.compare(b) == 0
}
///|
pub fn Decimal::less_than(a : Decimal, b : Decimal) -> Bool {
a.compare(b) < 0
}
///|
pub fn Decimal::less_equal(a : Decimal, b : Decimal) -> Bool {
a.compare(b) <= 0
}
///|
/// Returns 10^-scale, i.e. the value of the least significant digit.
pub fn Decimal::last_digit_place(self : Decimal) -> Decimal {
if self.scale > 0 {
{ mantissa: @bigint.BigInt::from_int(1), scale: self.scale, }
} else {
Decimal::one()
}
}
///|
/// True when the literal carries fractional digits (Beancount only infers a
/// tolerance from numbers with a fractional part).
pub fn Decimal::has_fractional_digits(self : Decimal) -> Bool {
self.scale > 0
}
///|
pub fn Decimal::to_string(self : Decimal) -> String {
let negative = bigint_sign(self.mantissa) < 0
let digits = bigint_abs(self.mantissa).to_string()
let sb = StringBuilder()
if negative && !self.is_zero() {
sb.write_char('-')
}
if self.scale <= 0 {
sb.write_string(digits)
if self.scale < 0 {
sb.write_string("0".repeat(-self.scale))
}
} else if digits.length() <= self.scale {
sb.write_string("0.")
sb.write_string("0".repeat(self.scale - digits.length()))
sb.write_string(digits)
} else {
let split = digits.length() - self.scale
let mut i = 0
for c in digits.iter() {
if i == split {
sb.write_char('.')
}
sb.write_char(c)
i = i + 1
}
}
sb.to_string()
}
///|
/// Human oriented formatting: trims trailing zeros but keeps at least
/// `min_scale` fractional digits.
pub fn Decimal::to_display(self : Decimal, min_scale~ : Int32 = 0) -> String {
let raw = self.to_string()
let dot = match raw.find(".") {
Some(i) => i
None => return raw
}
let mut end = raw.length()
while end > dot + 1 + min_scale && raw.get_char(end - 1) == Some('0') {
end = end - 1
}
if end == dot + 1 {
end = dot
}
raw.unsafe_substring(start=0, end~)
}