Describe the feature
math.decimal (which closed #25010 and #21754) gets addition, subtraction, multiplication and comparison exactly right. For money work a few pieces are still missing. Checked on V 0.5.2 and on master at c64467d9, where the module is unchanged:
import math.decimal
one := decimal.from_int(1)
vat := decimal.from_string('19.99')! * decimal.from_string('0.21')! // 4.1979
println(vat.div_prec(one, 2)!) // 4.2, not 4.20
println(decimal.from_string('5.00')!) // 5
println(decimal.from_string('0.125')!.div_prec(one, 2)!) // 0.13; half-even gives 0.12
decimal.from_string('1e3') or { println(err) } // invalid decimal value
println(one / decimal.zero) // panicsI'd like to add the following, as separate small PRs:
-
Rounding modes.
pub enum RoundingMode { half_up half_even half_down up down ceiling floor }, wherehalf_upmeans half away from zero (whatdiv_precdoes today).pub fn (d Decimal) round(places int, mode RoundingMode) !Decimalpub fn (d Decimal) div(divisor Decimal, places int, mode RoundingMode) !Decimal.div_precstays as thehalf_upshorthand.
-
Minor units and small operations.
pub fn from_minor_units(units i64, places int) !Decimalpub fn (d Decimal) to_minor_units(places int) !i64, which returns an error when the value has more places thanplacesor does not fit in i64.rem(result takes the dividend's sign; error on a zero divisor),sign() int,min,max.
-
Exponent notation in
from_string. Accept1e3,1.5E-2and similar, with the exponent capped so that input like1e999999999cannot force a huge allocation. -
Keep the scale (behaviour change, needs agreement first).
from_string('5.00')keeps scale 2 and prints5.00. As in Java's BigDecimal:+and-take the larger scale;*adds the scales;roundanddivproduce exactlyplacesdigits.==and<keep comparing by value, so5.0 == 5.00.- A new
normalize()strips trailing zeros on request.
This changes
str()for values that have trailing zeros today. Since the module is new, now seems the cheapest time to change it.
The / operator would still panic on a zero divisor. V cannot handle an operator's error with or {}, so the docs would point fallible code to div.
Use Case
Invoices, VAT and tax calculations, and storing amounts as PostgreSQL numeric or as integer cents. These need values rounded to a fixed number of places under a chosen rule, printed with those places, and converted exactly to and from integer minor units.
Proposed Solution
Items 1–3 only add API and are backward compatible. Item 4 changes output, so I would hold it until it's agreed here. Is the scale-preserving design acceptable, or would you prefer to keep normalization and add a str_fixed(places) formatter instead?
Acknowledgements
- I may be able to implement this feature request
- This feature might incur a breaking change (item 4 only)
Feature request:
.rem()with an integer division towards zero instead of minus infinity.Rationale:
Usage:
RFC:
math.decimal, and may better fit inmath.big. Note that base-3 (ternary) computing will soon come back (after the historic experiments in USSR that were abandoned in the 1970's for reason of costs in the chip industry) with native hardware implementations in ALU/FPU in some newer Chinese processors in development (that allow better density and lower power consumption with ternary transistors and memory storages, that could help bypass a limit for extending the "Moore's Law", even if there's a hardware binary conversion interface provided for compatiblity with binary devices and existing standard networking protocols).