// Multiple precision decimal numbers // NOTE: This is only for floating point printing and nothing else package strconv_decimal Decimal :: struct { digits: [384]byte, // big-endian digits count: int, decimal_point: int, neg, trunc: bool, } decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string { digit_zero :: proc(buf: []byte) -> int { for _, i in buf { buf[i] = '0' } return len(buf) } n := 10 + a.count + abs(a.decimal_point) // TODO(bill): make this work with a buffer that's not big enough assert(len(buf) >= n) b := buf[0:n] if a.count == 0 { b[0] = '0' return string(b[0:1]) } w := 0 if a.decimal_point <= 0 { b[w] = '0'; w += 1 b[w] = '.'; w += 1 w += digit_zero(b[w : w-a.decimal_point]) w += copy(b[w:], a.digits[0:a.count]) } else if a.decimal_point < a.count { w += copy(b[w:], a.digits[0:a.decimal_point]) b[w] = '.'; w += 1 w += copy(b[w:], a.digits[a.decimal_point : a.count]) } else { w += copy(b[w:], a.digits[0:a.count]) w += digit_zero(b[w : w+a.decimal_point-a.count]) } return string(b[0:w]) } // trim trailing zeros trim :: proc(a: ^Decimal) { for a.count > 0 && a.digits[a.count-1] == '0' { a.count -= 1 } if a.count == 0 { a.decimal_point = 0 } } assign :: proc(a: ^Decimal, idx: u64) { buf: [64]byte n := 0 for i := idx; i > 0; { j := i/10 i -= 10*j buf[n] = byte('0'+i) n += 1 i = j } a.count = 0 for n -= 1; n >= 0; n -= 1 { a.digits[a.count] = buf[n] a.count += 1 } a.decimal_point = a.count trim(a) } shift_right :: proc(a: ^Decimal, k: uint) { r := 0 // read index w := 0 // write index n: uint for ; n>>k == 0; r += 1 { if r >= a.count { if n == 0 { // Just in case a.count = 0 return } for n>>k == 0 { n = n * 10 r += 1 } break } c := uint(a.digits[r]) n = n*10 + c - '0' } a.decimal_point -= r-1 mask: uint = (1<>k n &= mask a.digits[w] = byte('0' + dig) w += 1 n = n*10 + c - '0' } for n > 0 { dig := n>>k n &= mask if w < len(a.digits) { a.digits[w] = byte('0' + dig) w += 1 } else if dig > 0 { a.trunc = true } n *= 10 } a.count = w trim(a) } shift_left :: proc(a: ^Decimal, k: uint) { // NOTE(bill): used to determine buffer size required for the decimal from the binary shift // 'k' means `1<= 0; r -= 1 { n += (uint(a.digits[r]) - '0') << k quo := n/10 rem := n - 10*quo w -= 1 if w < d { a.digits[w] = byte('0' + rem) } else if rem != 0 { a.trunc = true } n = quo } for n > 0 { quo := n/10 rem := n - 10*quo w -= 1 if w < d { a.digits[w] = byte('0' + rem) } else if rem != 0 { a.trunc = true } n = quo } // NOTE(bill): Remove unused buffer size assert(w >= 0) capacity -= w a.count = min(a.count+capacity, d) a.decimal_point += capacity trim(a) } shift :: proc(a: ^Decimal, i: int) { uint_size :: 8*size_of(uint) max_shift :: uint_size-4 switch k := i; { case a.count == 0: // no need to update case k > 0: for k > max_shift { shift_left(a, max_shift) k -= max_shift } shift_left(a, uint(k)) case k < 0: for k < -max_shift { shift_right(a, max_shift) k += max_shift } shift_right(a, uint(-k)) } } can_round_up :: proc(a: ^Decimal, nd: int) -> bool { if nd < 0 || nd >= a.count { return false } if a.digits[nd] == '5' && nd+1 == a.count { if a.trunc { return true } return nd > 0 && (a.digits[nd-1]-'0')%2 != 0 } return a.digits[nd] >= '5' } round :: proc(a: ^Decimal, nd: int) { if nd < 0 || nd >= a.count { return } if can_round_up(a, nd) { round_up(a, nd) } else { round_down(a, nd) } } round_up :: proc(a: ^Decimal, nd: int) { if nd < 0 || nd >= a.count { return } for i := nd-1; i >= 0; i -= 1 { if c := a.digits[i]; c < '9' { a.digits[i] += 1 a.count = i+1 return } } // Number is just 9s a.digits[0] = '1' a.count = 1 a.decimal_point += 1 } round_down :: proc(a: ^Decimal, nd: int) { if nd < 0 || nd >= a.count { return } a.count = nd trim(a) } // Extract integer part, rounded appropriately. There are no guarantees about overflow. rounded_integer :: proc(a: ^Decimal) -> u64 { if a.decimal_point > 20 { return 0xffff_ffff_ffff_ffff } i: int = 0 n: u64 = 0 m := min(a.decimal_point, a.count) for ; i < m; i += 1 { n = n*10 + u64(a.digits[i]-'0') } for ; i < a.decimal_point; i += 1 { n *= 10 } if can_round_up(a, a.decimal_point) { n += 1 } return n }