math.decimal #
Description
math.decimal provides arbitrary-precision fixed-point decimal arithmetic. It stores values as an integer coefficient plus a decimal scale, so addition, subtraction, and multiplication stay exact.
Like Go's shopspring/decimal, constructors keep the supplied scale internally, while str() omits trailing fractional zeros: from_string('5.00') has coefficient 500, scale 2, and string 5. Use str_fixed(places) for half-up rounding and exactly that many places, for example amount.str_fixed(2)! returns 5.00. Negative places return an error. + and - keep the larger scale, * adds the scales, and round and div store exactly the requested scale. Comparisons use numeric value regardless of scale. normalize() removes trailing coefficient zeros and reduces the stored scale without changing the value or its normal string representation. V uses nonnegative scales and retains its existing exponent parsing limits.
round and div round to a caller-chosen number of decimal places with a RoundingMode: half_up (ties away from zero), half_even (banker's rounding), half_down, up, down, ceiling or floor. div_prec is div with half_up. The / operator uses decimal.default_division_precision (16 fractional places), independently of either operand's stored scale, and panics on a zero divisor; use div where the divisor can be zero. Use div_prec with more places when small values would otherwise round to zero.
rem returns the remainder of a division truncated towards zero, so it has the sign of the dividend, like % on V integers. floor_div and floor_mod round the quotient towards negative infinity, so the remainder has the sign of the divisor, like math.floor_mod. div_euclid and mod_euclid give a remainder that is never negative. All of them return an error on a zero divisor.
sign returns -1, 0 or 1, and decimal.min and decimal.max return the smaller and the larger of two values. from_minor_units and to_minor_units convert between a decimal and an integer count of 10^-precision units: 19.99 is 1999 units at precision 2. to_minor_units returns an error rather than drop digits, so round first where needed.
from_string accepts an optional base-10 exponent, for example 1.5e-2 is 0.015. An exponent can have one leading + or - sign and any number of leading zeros. Exponents must be between -100 and 100 inclusive. Larger magnitudes return an exponent out of range error rather than changing the value or allocating large powers of ten.
Decimal implements the json2 custom encoder and decoders, like big.Integer. json2.encode writes a value as a JSON number in plain notation with its stored scale, so 10.50 stays 10.50. Consumers that read JSON numbers as binary floating point, such as JavaScript's JSON.parse, can round such values. json2.decode accepts a JSON number within the exponent range of from_string, or a JSON string with the syntax of a JSON number such as "10.50", and keeps its scale; other strings, booleans and arrays return an error. With json5: true, a hexadecimal number such as 0x1F decodes with scale 0, while Infinity and NaN return an error. Outside strict mode a nested null decodes to zero; use a ?decimal.Decimal field to tell a missing value apart from zero. In a sum type that also has a string variant, json2 gives a JSON string to whichever of the two is declared first: with decimal.Decimal | string every string is parsed as a decimal and a non-numeric string is an error, so declare string | decimal.Decimal to keep strings as strings. As for big.Integer, json2 writes a sum type variant as a tagged object of its fields, and decodes a JSON object into those fields without validation: an object with a negative scale_value decodes, and str() and json2.encode then panic, so check scale() >= 0 on untrusted input.
Example
import math.decimal
price := decimal.from_string('19.99')!
fee := decimal.from_string('1.50')!
total := price + fee
assert total.str() == '21.49'
amount := decimal.from_string('5.00')!
assert amount.scale() == 2
assert amount.str() == '5'
assert amount.str_fixed(2)! == '5.00'
assert amount.normalize().scale() == 0
assert amount == decimal.from_int(5)
share := total.div_prec(decimal.from_int(3), 2)!
assert share.str() == '7.16'
vat := price * decimal.from_string('0.21')!
assert vat.str() == '4.1979'
assert vat.round(2, .half_up)!.str() == '4.2'
tie := decimal.from_string('0.125')!
assert tie.round(2, .half_even)!.str() == '0.12'
assert tie.round(2, .half_up)!.str() == '0.13'
angle := decimal.from_int(-30)
assert angle.rem(decimal.from_int(360))!.str() == '-30'
assert angle.floor_mod(decimal.from_int(360))!.str() == '330'
assert price.to_minor_units(2)! == 1999
Constants #
const default_division_precision = 16
default_division_precision is the number of digits after the decimal point used by the / operator.
const zero = Decimal{
coefficient_value: big.zero_int
scale_value: 0
}
zero is the canonical zero value for Decimal.
fn from_i64 #
fn from_i64(value i64) Decimal
from_i64 creates a decimal from an i64.
fn from_int #
fn from_int(value int) Decimal
from_int creates a decimal from an int.
fn from_minor_units #
fn from_minor_units(units i64, precision int) !Decimal
from_minor_units creates a decimal from an amount in units of 10^-precision, so from_minor_units(1999, 2) is 19.99. The stored scale is precision.
fn from_string #
fn from_string(input string) !Decimal
from_string parses a decimal string with an optional base-10 exponent. Exponents from -100 to 100 are supported; larger magnitudes return an error. The digits after the decimal point set the scale, so 5.00 keeps scale 2; an exponent shifts it, so 1.50e1 stores coefficient 150 and scale 1.
fn from_u64 #
fn from_u64(value u64) Decimal
from_u64 creates a decimal from a u64.
fn max #
fn max(a Decimal, b Decimal) Decimal
max returns the larger of a and b, or a when they are equal.
fn min #
fn min(a Decimal, b Decimal) Decimal
min returns the smaller of a and b, or a when they are equal.
fn new #
fn new(coefficient big.Integer, scale int) Decimal
new creates a decimal from coefficient * 10^-scale and keeps scale, so new(500, 2) stores coefficient 500 and scale 2.
fn RoundingMode.from #
fn RoundingMode.from[W](input W) !RoundingMode
from returns the RoundingMode value of an integer or of a field name, or an error if there is none.
enum RoundingMode #
enum RoundingMode {
half_up // round to nearest; ties away from zero
half_even // round to nearest; ties to the even neighbour (banker's rounding)
half_down // round to nearest; ties towards zero
up // away from zero
down // towards zero (truncate)
ceiling // towards positive infinity
floor // towards negative infinity
}
RoundingMode selects how round and div treat the digits they discard.
struct Decimal #
struct Decimal {
coefficient_value big.Integer
scale_value int
}
Decimal represents an arbitrary-precision fixed-point decimal value. Its numeric value is coefficient * 10^-scale.
fn (Decimal) coefficient #
fn (d Decimal) coefficient() big.Integer
coefficient returns the integer coefficient stored by d.
fn (Decimal) scale #
fn (d Decimal) scale() int
scale returns the number of decimal places stored by d.
fn (Decimal) normalize #
fn (d Decimal) normalize() Decimal
normalize returns d with trailing coefficient zeros removed and its scale reduced accordingly. Its numeric value and string representation do not change.
fn (Decimal) is_zero #
fn (d Decimal) is_zero() bool
is_zero returns true when d == 0.
fn (Decimal) abs #
fn (d Decimal) abs() Decimal
abs returns the absolute value of d.
fn (Decimal) neg #
fn (d Decimal) neg() Decimal
neg returns the negated value of d.
fn (Decimal) sign #
fn (d Decimal) sign() int
sign returns -1, 0 or 1 when d is negative, zero or positive.
fn (Decimal) rem #
fn (dividend Decimal) rem(divisor Decimal) !Decimal
rem returns the remainder of dividing dividend by divisor with the quotient truncated towards zero. Like % on V integers, the result takes the sign of dividend: -7.5 rem 2 is -1.5. Its scale is the larger of the two scales.
fn (Decimal) floor_div #
fn (dividend Decimal) floor_div(divisor Decimal) !Decimal
floor_div returns the quotient of dividend and divisor rounded towards negative infinity. With floor_mod it satisfies dividend == dividend.floor_div(divisor) * divisor + dividend.floor_mod(divisor).
fn (Decimal) floor_mod #
fn (dividend Decimal) floor_mod(divisor Decimal) !Decimal
floor_mod returns the remainder of dividing dividend by divisor with the quotient rounded towards negative infinity. Like math.floor_mod, the result takes the sign of divisor: -7.5 floor_mod 2 is 0.5.
fn (Decimal) div_euclid #
fn (dividend Decimal) div_euclid(divisor Decimal) !Decimal
div_euclid returns the Euclidean quotient of dividend and divisor. With mod_euclid it satisfies dividend == dividend.div_euclid(divisor) * divisor + dividend.mod_euclid(divisor).
fn (Decimal) mod_euclid #
fn (dividend Decimal) mod_euclid(divisor Decimal) !Decimal
mod_euclid returns the Euclidean remainder of dividing dividend by divisor. Like big.Integer.mod_euclid, the result is never negative and is smaller than |divisor|: -7.5 mod_euclid -2 is 0.5.
fn (Decimal) to_minor_units #
fn (d Decimal) to_minor_units(precision int) !i64
to_minor_units returns d as an amount in units of 10^-precision, so 19.99 with precision 2 is 1999. It returns an error when d has more than precision digits after the decimal point, not counting trailing zeros (round it first), or when the amount does not fit in an i64.
fn (Decimal) str #
fn (d Decimal) str() string
str returns d in plain decimal notation without trailing fractional zeros. It does not change the stored coefficient or scale.
fn (Decimal) str_fixed #
fn (d Decimal) str_fixed(precision int) !string
str_fixed returns d rounded half up (ties away from zero) with exactly precision digits after the decimal point. Negative precision returns an error.
fn (Decimal) + #
fn (left Decimal) + (right Decimal) Decimal
- returns the exact sum of
leftandright, with the larger of their scales.
fn (Decimal) - #
fn (left Decimal) - (right Decimal) Decimal
- returns the exact difference of
leftandright, with the larger of their scales.
fn (Decimal) * #
fn (left Decimal) * (right Decimal) Decimal
- returns the exact product of
leftandright, with the sum of their scales.
fn (Decimal) / #
fn (dividend Decimal) / (divisor Decimal) Decimal
/ divides dividend by divisor, rounding half up to default_division_precision digits after the decimal point regardless of the operands' scales. It panics on a zero divisor; use div where it can be zero.
fn (Decimal) div_prec #
fn (dividend Decimal) div_prec(divisor Decimal, precision int) !Decimal
div_prec divides dividend by divisor and rounds half up to exactly precision digits after the decimal point.
fn (Decimal) div #
fn (dividend Decimal) div(divisor Decimal, precision int, mode RoundingMode) !Decimal
div divides dividend by divisor and rounds the quotient to exactly precision digits after the decimal point using mode.
fn (Decimal) round #
fn (d Decimal) round(precision int, mode RoundingMode) !Decimal
round returns d rounded to exactly precision digits after the decimal point using mode. A value with fewer digits is padded in its stored coefficient, so 1.5 rounded to 3 places has coefficient 1500 and scale 3.
fn (Decimal) == #
fn (left Decimal) == (right Decimal) bool
== returns true when left and right represent the same value, whatever their scales, so 5.0 == 5.00.
fn (Decimal) < #
fn (left Decimal) < (right Decimal) bool
< returns true when left is smaller than right.
fn (Decimal) from_json_number #
fn (mut d Decimal) from_json_number(raw_number string) !
from_json_number implements a custom decoder for json2. It parses raw_number like from_string, so the digits after the decimal point set the scale. A JSON5 hexadecimal integer such as 0x1F or -0x1F has scale 0.
fn (Decimal) from_json_string #
fn (mut d Decimal) from_json_string(raw_string string) !
from_json_string implements a custom decoder for json2, for values sent as JSON strings such as "19.99". raw_string must have the syntax of a JSON number: no surrounding whitespace, no leading + sign, and digits on both sides of a decimal point.
fn (Decimal) to_json #
fn (d Decimal) to_json() string
to_json implements a custom encoder for json2. It writes d as a JSON number in plain decimal notation and keeps the stored scale, so 10.50 encodes as 10.50 and decodes back to the same coefficient and scale.