code wiki / (root) / nx_money.nx

nx_money.nx source

↩ module page · 91 lines · 3726 B

1// nx_money.nx -- R1 of THE NISHI FINANCIAL ECOSYSTEM engine: the NO-FLOAT money substrate. 2// Money is exact i64 MINOR UNITS (cents). Rates are fixed-point scaled by MNY_RATE_SCALE (1e9). 3// All arithmetic is integer -- floats are WRONG for money (0.1 + 0.2 != 0.3), so this is correctness, 4// not doctrine. Provides exact add/sub/neg, rounded division (floor / half-up / banker's half-even), 5// scaled-rate application (interest), APR->periodic, percent constructor, and [-]D.CC formatting. 6// Foundation for the ledger (R2), debt/amortization (R4), audit (R5), optimizer (R7). 7// PRECONDITION on mny_apply_rate: |amount * rate_scaled| < 2^63 (true for all realistic personal-finance 8// amounts; 128-bit-safe widening for extreme values is the R1b hardening rung). license_tier: ORIGINAL 9import "nx_syscalls.nx" 10 11const MNY_RATE_SCALE: i64 = 1000000000 12const RND_FLOOR: i64 = 0 13const RND_HALF_UP: i64 = 1 14const RND_HALF_EVEN: i64 = 2 15 16func mny_add(a: i64, b: i64) -> i64 { return a + b } 17func mny_sub(a: i64, b: i64) -> i64 { return a - b } 18func mny_neg(a: i64) -> i64 { return 0 - a } 19 20// Floor division toward -infinity (den > 0). 21func mny_div_floor(num: i64, den: i64) -> i64 { 22 var q: i64 = num / den 23 let r: i64 = num - q * den 24 if r < 0 { q = q - 1 } 25 return q 26} 27 28// Rounded integer division num/den (den > 0) under mode. half-up rounds .5 away from zero; 29// half-even rounds .5 to the even neighbour (banker's). Sign-symmetric via magnitude. 30func mny_div_round(num: i64, den: i64, mode: i64) -> i64 { 31 if mode == RND_FLOOR { return mny_div_floor(num, den) } 32 var neg: i64 = 0 33 var x: i64 = num 34 if x < 0 { neg = 1; x = 0 - x } 35 let q: i64 = x / den 36 let r: i64 = x - q * den 37 var inc: i64 = 0 38 let twice: i64 = 2 * r 39 if mode == RND_HALF_UP { if twice >= den { inc = 1 } } 40 if mode == RND_HALF_EVEN { 41 if twice > den { inc = 1 } 42 else { if twice == den { if (q % 2) == 1 { inc = 1 } } } 43 } 44 var qq: i64 = q + inc 45 if neg == 1 { qq = 0 - qq } 46 return qq 47} 48 49// Apply a fixed-point rate (scaled by MNY_RATE_SCALE) to a money amount, exactly rounded. 50// interest = round(amount * rate / SCALE). 51func mny_apply_rate(amount: i64, rate_scaled: i64, mode: i64) -> i64 { 52 return mny_div_round(amount * rate_scaled, MNY_RATE_SCALE, mode) 53} 54 55// Convert an annual fixed-point rate to a monthly one (banker's rounding). 56func mny_rate_monthly(apr_scaled: i64) -> i64 { 57 return mny_div_round(apr_scaled, 12, RND_HALF_EVEN) 58} 59 60// Build a scaled rate from a percent expressed as the fraction pct_num/pct_den 61// (e.g. 6.5% = mny_rate_from_pct(65, 10)). rate = (pct_num / pct_den) / 100 * SCALE. 62func mny_rate_from_pct(pct_num: i64, pct_den: i64) -> i64 { 63 return mny_div_round(pct_num * MNY_RATE_SCALE, pct_den * 100, RND_HALF_EVEN) 64} 65 66// Write a base-10 integer (>= 0) to out at pos; returns new pos. 67func mny_wdec(out: *u8, pos: i64, v: i64) -> i64 { 68 if v == 0 { out[pos] = 0x30 as u8; return pos + 1 } 69 let tmp: *u8 = sys_mmap(24) 70 var m: i64 = v 71 var k: i64 = 0 72 while m > 0 { tmp[k] = (0x30 + (m % 10)) as u8; m = m / 10; k = k + 1 } 73 var p: i64 = pos 74 var i: i64 = k - 1 75 while i >= 0 { out[p] = tmp[i]; p = p + 1; i = i - 1 } 76 return p 77} 78 79// Format money (cents) as "[-]D.CC" into out; returns byte length. 80func mny_format(amount: i64, out: *u8) -> i64 { 81 var p: i64 = 0 82 var x: i64 = amount 83 if x < 0 { out[p] = 0x2d as u8; p = p + 1; x = 0 - x } 84 let dollars: i64 = x / 100 85 let cents: i64 = x - dollars * 100 86 p = mny_wdec(out, p, dollars) 87 out[p] = 0x2e as u8; p = p + 1 88 out[p] = (0x30 + (cents / 10)) as u8; p = p + 1 89 out[p] = (0x30 + (cents % 10)) as u8; p = p + 1 90 return p 91}