v / vlib / math / decimal / decimal.v
256 lines · 228 sloc · 7.03 KB · 3d627e2c328f993afe228964a195691c9302e642
Raw
1// Copyright (c) 2019-2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module decimal
5
6import math.big
7import strings
8
9// default_division_precision is used by the `/` operator when the quotient
10// needs a rounded decimal expansion.
11pub const default_division_precision = 16
12
13// zero is the canonical zero value for Decimal.
14pub const zero = Decimal{
15 coefficient_value: big.zero_int
16 scale_value: 0
17}
18
19// Decimal represents an arbitrary-precision fixed-point decimal value.
20// Its numeric value is `coefficient * 10^-scale`.
21pub struct Decimal {
22 coefficient_value big.Integer
23 scale_value int
24}
25
26// new creates a decimal from `coefficient * 10^-scale`.
27pub fn new(coefficient big.Integer, scale int) Decimal {
28 if scale < 0 {
29 panic('math.decimal: scale cannot be negative')
30 }
31 return normalize(coefficient, scale)
32}
33
34// from_int creates a decimal from an int.
35pub fn from_int(value int) Decimal {
36 return Decimal{
37 coefficient_value: big.integer_from_int(value)
38 scale_value: 0
39 }
40}
41
42// from_i64 creates a decimal from an i64.
43pub fn from_i64(value i64) Decimal {
44 return Decimal{
45 coefficient_value: big.integer_from_i64(value)
46 scale_value: 0
47 }
48}
49
50// from_u64 creates a decimal from a u64.
51pub fn from_u64(value u64) Decimal {
52 return Decimal{
53 coefficient_value: big.integer_from_u64(value)
54 scale_value: 0
55 }
56}
57
58// from_string parses a decimal string without exponent notation.
59pub fn from_string(input string) !Decimal {
60 value := input.trim_space()
61 if value.len == 0 {
62 return error('math.decimal: empty input')
63 }
64 mut negative := false
65 mut seen_dot := false
66 mut digit_count := 0
67 mut scale := 0
68 mut digits := strings.new_builder(value.len)
69 for index, ch in value {
70 if index == 0 && (ch == `+` || ch == `-`) {
71 negative = ch == `-`
72 continue
73 }
74 if ch >= `0` && ch <= `9` {
75 digits.write_u8(ch)
76 digit_count++
77 if seen_dot {
78 scale++
79 }
80 continue
81 }
82 if ch == `.` && !seen_dot {
83 seen_dot = true
84 continue
85 }
86 return error('math.decimal: invalid decimal value `${input}`')
87 }
88 if digit_count == 0 {
89 return error('math.decimal: invalid decimal value `${input}`')
90 }
91 mut coefficient_string := digits.str()
92 if negative {
93 coefficient_string = '-' + coefficient_string
94 }
95 coefficient := big.integer_from_string(coefficient_string)!
96 return new(coefficient, scale)
97}
98
99// coefficient returns the integer coefficient stored by `d`.
100pub fn (d Decimal) coefficient() big.Integer {
101 return d.coefficient_value
102}
103
104// scale returns the number of decimal places stored by `d`.
105pub fn (d Decimal) scale() int {
106 return d.scale_value
107}
108
109// is_zero returns true when `d == 0`.
110pub fn (d Decimal) is_zero() bool {
111 return d.coefficient_value.signum == 0
112}
113
114// abs returns the absolute value of `d`.
115pub fn (d Decimal) abs() Decimal {
116 return Decimal{
117 coefficient_value: d.coefficient_value.abs()
118 scale_value: d.scale_value
119 }
120}
121
122// neg returns the negated value of `d`.
123pub fn (d Decimal) neg() Decimal {
124 return Decimal{
125 coefficient_value: d.coefficient_value.neg()
126 scale_value: d.scale_value
127 }
128}
129
130// str returns the canonical decimal representation of `d`.
131pub fn (d Decimal) str() string {
132 if d.coefficient_value.signum == 0 {
133 return '0'
134 }
135 if d.scale_value == 0 {
136 return d.coefficient_value.str()
137 }
138 mut prefix := ''
139 if d.coefficient_value.signum < 0 {
140 prefix = '-'
141 }
142 absolute := d.coefficient_value.abs().str()
143 if d.scale_value >= absolute.len {
144 padding := strings.repeat(`0`, d.scale_value - absolute.len)
145 return '${prefix}0.${padding}${absolute}'
146 }
147 split := absolute.len - d.scale_value
148 return '${prefix}${absolute[..split]}.${absolute[split..]}'
149}
150
151// + returns the exact sum of `left` and `right`.
152pub fn (left Decimal) + (right Decimal) Decimal {
153 scale := max_scale(left.scale_value, right.scale_value)
154 return new(left.rescaled_coefficient(scale) + right.rescaled_coefficient(scale), scale)
155}
156
157// - returns the exact difference of `left` and `right`.
158pub fn (left Decimal) - (right Decimal) Decimal {
159 scale := max_scale(left.scale_value, right.scale_value)
160 return new(left.rescaled_coefficient(scale) - right.rescaled_coefficient(scale), scale)
161}
162
163// * returns the exact product of `left` and `right`.
164pub fn (left Decimal) * (right Decimal) Decimal {
165 return new(left.coefficient_value * right.coefficient_value, left.scale_value +
166 right.scale_value)
167}
168
169// / divides `dividend` by `divisor` using `default_division_precision`.
170pub fn (dividend Decimal) / (divisor Decimal) Decimal {
171 return dividend.div_prec(divisor, default_division_precision) or { panic(err) }
172}
173
174// div_prec divides `dividend` by `divisor` and rounds half up to `precision`
175// digits after the decimal point.
176pub fn (dividend Decimal) div_prec(divisor Decimal, precision int) !Decimal {
177 if precision < 0 {
178 return error('math.decimal: precision cannot be negative')
179 }
180 if divisor.coefficient_value.signum == 0 {
181 return error('math.decimal: cannot divide by zero')
182 }
183 mut quotient, remainder := scaled_div_mod(dividend, divisor, precision)
184 if remainder.signum != 0 {
185 twice_remainder := remainder * big.two_int
186 if !(twice_remainder < scaled_divisor(dividend, divisor)) {
187 quotient += big.one_int
188 }
189 }
190 if dividend.coefficient_value.signum * divisor.coefficient_value.signum < 0 {
191 quotient = quotient.neg()
192 }
193 return new(quotient, precision)
194}
195
196// == returns true when `left` and `right` represent the same value.
197pub fn (left Decimal) == (right Decimal) bool {
198 return left.scale_value == right.scale_value
199 && left.coefficient_value == right.coefficient_value
200}
201
202// < returns true when `left` is smaller than `right`.
203pub fn (left Decimal) < (right Decimal) bool {
204 scale := max_scale(left.scale_value, right.scale_value)
205 return left.rescaled_coefficient(scale) < right.rescaled_coefficient(scale)
206}
207
208fn normalize(coefficient big.Integer, scale int) Decimal {
209 if coefficient.signum == 0 {
210 return zero
211 }
212 mut normalized_coefficient := coefficient
213 mut normalized_scale := scale
214 for normalized_scale > 0 {
215 quotient, remainder := normalized_coefficient.div_mod(big.c10)
216 if remainder.signum != 0 {
217 break
218 }
219 normalized_coefficient = quotient
220 normalized_scale--
221 }
222 return Decimal{
223 coefficient_value: normalized_coefficient
224 scale_value: normalized_scale
225 }
226}
227
228fn (d Decimal) rescaled_coefficient(scale int) big.Integer {
229 if scale <= d.scale_value || d.coefficient_value.signum == 0 {
230 return d.coefficient_value
231 }
232 return d.coefficient_value * pow10(scale - d.scale_value)
233}
234
235fn pow10(exponent int) big.Integer {
236 if exponent <= 0 {
237 return big.one_int
238 }
239 return big.c10.pow(u32(exponent))
240}
241
242fn scaled_divisor(dividend Decimal, divisor Decimal) big.Integer {
243 return divisor.coefficient_value.abs() * pow10(dividend.scale_value)
244}
245
246fn scaled_div_mod(dividend Decimal, divisor Decimal, precision int) (big.Integer, big.Integer) {
247 numerator := dividend.coefficient_value.abs() * pow10(divisor.scale_value + precision)
248 return numerator.div_mod(scaled_divisor(dividend, divisor))
249}
250
251fn max_scale(a int, b int) int {
252 if a > b {
253 return a
254 }
255 return b
256}
257