-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdecimal.go
148 lines (129 loc) · 3.49 KB
/
decimal.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// This file is part of rpn, a simple and useful CLI RPN calculator.
// For further information, check https://github.com/marcopaganini/rpn
//
// (C) Sep/2024 by Marco Paganini <paganini AT paganini DOT net>
package main
import (
"bytes"
"fmt"
"strings"
"github.com/ericlagergren/decimal"
)
// big returns a new *decimal.Big
func big() *decimal.Big {
return decimal.WithContext(decimal.Context128)
}
// bigUint returns a new *decimal.Big from an uint64.
func bigUint(n uint64) *decimal.Big {
return big().SetUint64(n)
}
// bigFloat returns a new *decimal.Big from a string
// Using a float64 here will introduce rounding errors.
func bigFloat(s string) *decimal.Big {
r, _ := big().SetString(s)
return r
}
// commafWithDigits idea comes from the humanize library, but was modified to
// work with decimal numbers.
func commafWithDigits(n *decimal.Big, decimals int) string {
// Make a copy so we won't modify the original value (passed by pointer).
v := big().Copy(n)
buf := &bytes.Buffer{}
if v.Sign() < 0 {
buf.Write([]byte{'-'})
// Make v positive
v.SetSignbit(false)
}
comma := []byte{','}
f := fmt.Sprintf("%%.%df", decimals)
parts := strings.Split(fmt.Sprintf(f, v), ".")
pos := 0
if len(parts[0])%3 != 0 {
pos += len(parts[0]) % 3
buf.WriteString(parts[0][:pos])
buf.Write(comma)
}
for ; pos < len(parts[0]); pos += 3 {
buf.WriteString(parts[0][pos : pos+3])
buf.Write(comma)
}
buf.Truncate(buf.Len() - 1)
if len(parts) > 1 {
buf.Write([]byte{'.'})
buf.WriteString(parts[1])
}
return stripTrailingDigits(buf.String(), decimals)
}
func stripTrailingDigits(s string, digits int) string {
// Remove insignificant zeroes after period (if any).
if strings.Contains(s, ".") {
s = strings.TrimRight(s, "0")
s = strings.TrimRight(s, ".")
}
// Trim string to .<digits>
if i := strings.Index(s, "."); i >= 0 {
if digits <= 0 {
return s[:i]
}
i++
if i+digits >= len(s) {
return s
}
return s[:i+digits]
}
return s
}
// formatNumber formats the number using base and decimals. For bases different
// than 10, non-integer floating numbers are truncated.
func formatNumber(ctx decimal.Context, n *decimal.Big, base, decimals int) string {
// Print NaN without suffix numbers.
if n.IsNaN(0) {
return strings.TrimRight(fmt.Sprint(n), "0123456789")
}
if n.IsInf(0) {
return fmt.Sprint(n)
}
// clean = double as ascii, without non-significant decimal zeroes.
f := fmt.Sprintf("%%.%df", decimals)
clean := stripTrailingDigits(fmt.Sprintf(f, n), decimals)
var (
n64 uint64
suffix string
)
buf := &bytes.Buffer{}
if base != 10 {
// For negative numbers, prefix them with a minus sign and
// force them to be positive.
if n.Signbit() {
buf.Write([]byte{'-'})
n.SetSignbit(false)
}
// Truncate floating point numbers to their integer representation.
if !n.IsInt() {
suffix = fmt.Sprintf(" (truncated from %s)", clean)
ctx.Floor(n, n)
}
// Non-base 10 uses uint64s.
var ok bool
n64, ok = n.Uint64()
if !ok {
return "Invalid number: non decimal base only supports uint64 numbers."
}
}
switch {
case base == 2:
buf.WriteString(fmt.Sprintf("0b%b%s", n64, suffix))
case base == 8:
buf.WriteString(fmt.Sprintf("0%o%s", n64, suffix))
case base == 16:
buf.WriteString(fmt.Sprintf("0x%x%s", n64, suffix))
default:
h := commafWithDigits(n, decimals)
// Only print humanized format when it differs from original value.
if h != clean {
suffix = " (" + h + ")"
}
buf.WriteString(clean + suffix)
}
return buf.String()
}