mirror of
https://github.com/odin-lang/Odin.git
synced 2026-01-04 20:17:48 +00:00
269 lines
4.7 KiB
Odin
269 lines
4.7 KiB
Odin
// 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) - 1
|
|
|
|
for ; r < a.count; r += 1 {
|
|
c := uint(a.digits[r])
|
|
dig := n>>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<<k` == `2^k` which equates to roundup(k*log10(2)) digits required
|
|
log10_2 :: 0.301029995663981195213738894724493026768189881462108541310
|
|
capacity := int(f64(k)*log10_2 + 1)
|
|
|
|
r := a.count // read index
|
|
w := a.count+capacity // write index
|
|
|
|
d := len(a.digits)
|
|
|
|
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 < 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
|
|
}
|
|
|