// Multiple precision decimal numbers // NOTE: This is only for floating point printing and nothing else package 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 do 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); buf = buf[0:n]; if a.count == 0 { buf[0] = '0'; return string(buf[0:1]); } w := 0; if a.decimal_point <= 0 { buf[w] = '0'; w += 1; buf[w] = '.'; w += 1; w += digit_zero(buf[w : w-a.decimal_point]); w += copy(buf[w:], a.digits[0:a.count]); } else if a.decimal_point < a.count { w += copy(buf[w:], a.digits[0:a.decimal_point]); buf[w] = '.'; w += 1; w += copy(buf[w:], a.digits[a.decimal_point : a.count]); } else { w += copy(buf[w:], a.digits[0:a.count]); w += digit_zero(buf[w : w+a.decimal_point-a.count]); } return string(buf[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, i: u64) { buf: [64]byte; n := 0; for 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) { delta := int(k/4); r := a.count; // read index w := a.count+delta; // write index n: uint; for r -= 1; r >= 0; r -= 1 { n += (uint(a.digits[r]) - '0') << k; quo := n/10; rem := n - 10*quo; w -= 1; if w < len(a.digits) { 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 0 <= w && w < len(a.digits) { a.digits[w] = byte('0' + rem); } else if rem != 0 { a.trunc = true; } n = quo; } a.count += delta; a.count = min(a.count, len(a.digits)); a.decimal_point += delta; trim(a); } shift :: proc(a: ^Decimal, k: int) { uint_size :: 8*size_of(uint); max_shift :: uint_size-4; switch { 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 do 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; }