/** * @pwngh/money * * Copyright (c) Preston Neal * * This source code is licensed under the MIT license found in the * LICENSE.md file in the root directory of this source tree. * * @license MIT */ /** * Minor-unit integer money, as a single-file amalgamation. Zero imports, erasable syntax * only, so this one file composes into any environment unchanged: run directly under * Node >= 22.18 type stripping, vendor into a repo (economy-lab keeps zero runtime * dependencies by copying this file, not depending on it), publish as-is to npm, or feed * to any bundler. Reimplementations in other languages (UdonSharp) do not consume this * code; they consume `vectors`, the embedded conformance suite that pins every carrier * of these semantics to identical behavior. Drift across copies is a test failure, not * a discipline problem. * * Extract the vectors for a non-TypeScript runner: * * node -e 'import("./money.js").then(m => console.log(JSON.stringify(m.vectors)))' > vectors.json * * Amounts are `bigint` minor units checked into i64 range, so totals stay exact past * Number precision and line up with Postgres `bigint` and C# `long`. Arithmetic throws * on broken premises (overflow, currency mismatch, invalid weights, bad rates); `parse` and * `decode` are the untrusted boundaries and return null for every "no". No Intl, no locale tables: * formatting is manual grouping, identical on every runtime. */ /** A money value. Structurally beneath economy-lab's branded Amount: lab's toAmount adds the brand, arithmetic here is shared. */ export interface Amount { readonly currency: string; readonly minor: bigint; } export declare const I64_MIN: bigint; export declare const I64_MAX: bigint; /** The ISO 4217 List One amendment this exponent table is current through. */ export declare const ISO_4217_AMENDMENT = 180; /** Minor-unit exponent for a currency code: minor = whole * 10^exponent. */ export declare function exponent(currency: string): number; /** Builds a range-checked Amount from a currency and a minor-unit count. */ export declare function amount(currency: string, minor: bigint): Amount; export declare function isZero(a: Amount): boolean; export declare function isNegative(a: Amount): boolean; /** Adds two amounts of the same currency. Throws on mismatch or i64 overflow. */ export declare function add(a: Amount, b: Amount): Amount; /** Subtracts b from a in the same currency. Throws on mismatch or i64 overflow. */ export declare function sub(a: Amount, b: Amount): Amount; export declare function neg(a: Amount): Amount; export declare function abs(a: Amount): Amount; /** Scales an amount by an integer factor. Throws on i64 overflow. */ export declare function mul(a: Amount, k: bigint): Amount; /** Compares two amounts of the same currency. Throws on mismatch. */ export declare function compare(a: Amount, b: Amount): -1 | 0 | 1; /** * Rounding modes for division. halfEven is banker's rounding, ties to the even * quotient, carrying no directional bias under repetition; halfUp is ties away from * zero, the statutory rounding of tax and VAT rules. */ export type Rounding = 'floor' | 'ceil' | 'trunc' | 'halfEven' | 'halfUp'; /** * Integer division with the rounding mode named at the call site, because BigInt, C#, * and SQL disagree on negative quotients (-7/2 is -4 floored, -3 truncated) and a mode * left implicit is a mode chosen by the host language. Operands are unbounded bigints; * the result is checked into i64. Zero divisor and unknown mode are broken premises * and throw. */ export declare function divRound(num: bigint, den: bigint, mode: Rounding): bigint; /** * value * num / den through an arbitrary-precision intermediate, then divRound: the * rate, fee, and conversion primitive. The intermediate deliberately exceeds i64 — * vectors near I64_MAX exist to force other carriers into 128-bit arithmetic — and * only the result is range-checked. */ export declare function mulDiv(value: bigint, num: bigint, den: bigint, mode: Rounding): bigint; /** * A conversion rate as an exact rational: num / den units of the target per whole unit * of the source. Never a float. */ export interface Rate { readonly num: bigint; readonly den: bigint; } /** * Converts an amount across currencies through a rational rate with the * 10^(expTo − expFrom) rescale built in, because the cross-exponent step is where * hand-rolled conversions rot: USD cents to JPY yen is not a bare multiply. The * rounding mode is named at the call site; a non-positive denominator or negative * numerator is a broken premise and throws. The result is a range-checked Amount. */ export declare function convert(a: Amount, to: string, r: Rate, mode: Rounding): Amount; /** * Canonical wire form, `CUR:minor`, exactly one encoding per value so equality is byte * equality and reconciliation compares strings. Encoding a malformed currency or an * out-of-range minor is a broken premise and throws: the caller built that Amount. */ export declare function encode(a: Amount): string; /** * The untrusted boundary for wire text: strict grammar, canonical integers only (no * leading zeros, no -0), i64 range enforced, null for every "no" — so decode∘encode * and encode∘decode are both identities on the valid domain. */ export declare function decode(text: string): Amount | null; /** * Formats minor units as a plain decimal string with 3-digit grouping. Locale-free by * design: the same bytes on Node, browsers, and the UdonSharp reimplementation. The * separator options are pinned by vectors because consumers build their decimal wire * from them: economy-edge's and economy-lab's `USD:12.34` is `format` with group ''. */ export declare function format(minor: bigint, exp: number, options?: { group?: string; decimal?: string; }): string; /** * Parses a decimal string to minor units. The untrusted boundary: every rejection is * null, never a throw. Accepts an optional leading minus, digits either ungrouped or * correctly comma-grouped, and at most `exp` fraction digits (shorter pads, longer is * rejected rather than silently truncated). Out-of-i64-range values are rejected. */ export declare function parse(text: string, exp: number): bigint | null; /** * Splits minor units across integer weights with no lost unit: shares sum to `minor` * exactly. Floor division first, then the remainder goes one unit at a time to the * largest fractional parts (ties to the lower index), so the result is deterministic. * Negative amounts allocate as the negated allocation of their absolute value. */ export declare function allocate(minor: bigint, weights: readonly bigint[]): bigint[]; /** * Splits minor units by basis points, floor per share, remainder to the caller: the * economy-lab Recipient contract, where the platform keeps the remaining fee. Unlike * `allocate`, the remainder is a return value, not redistributed. */ export declare function splitBps(minor: bigint, bps: readonly number[]): { shares: bigint[]; remainder: bigint; }; /** * The conformance suite, embedded so the file is its own single source of truth. * JSON-safe (bigints carried as strings) so non-TypeScript runners consume the same * vectors byte for byte. 'throws' marks calls whose premise is invalid. */ export type Vector = readonly ['exp', string, number] | readonly ['parse', string, number, string | null] | readonly ['format', string, number, string, (readonly [string, string])?] | readonly ['add', string, string, string | 'throws'] | readonly ['mul', string, string, string | 'throws'] | readonly ['div', string, string, Rounding, string | 'throws'] | readonly ['muldiv', string, string, string, Rounding, string | 'throws'] | readonly ['conv', string, string, string, string, string, Rounding, string | 'throws'] | readonly ['enc', string, string, string | 'throws'] | readonly ['dec', string, string | null] | readonly ['alloc', string, readonly string[], readonly string[] | 'throws'] | readonly ['bps', string, readonly number[], readonly string[], string]; export declare const vectors: readonly Vector[]; /** * Runs every vector against this implementation and returns the failures, empty when * conformant. Pure and dependency-free so any consumer, in any runtime, can assert * `selfTest().length === 0` without a test framework. */ export declare function selfTest(): string[]; /** * Seeded property prover beside the example vectors: selfTest pins points, prove checks * laws over 500 sampled inputs per pass — parse∘format identity across all exponents, * wire round-trip, allocation and splitBps conservation, and for every rounding mode * both the remainder bound |num − result·den| < |den| and the floor ≤ mode ≤ ceil * ordering. The LCG is fixed-seed, so any failure is a reproducible counterexample, * never a flake. */ export declare function prove(): string[];