code wiki / (root) / nx_damages_lib.nx

nx_damages_lib.nx source

↩ module page · 58 lines · 2772 B

1// nx_damages_lib.nx -- PREJUDGMENT INTEREST / money damages over time. Integer-exact, composes nx_sol. 2// 3// A money judgment is rarely just the principal: statutes award prejudgment interest from the date of loss 4// (accrual) to the date of judgment. Getting it right means an EXACT day count (not an approximation) and the 5// correct convention -- simple vs annual compounding, at a declared statutory rate. This organ REUSES the 6// exact proleptic-Gregorian day engine from nx_sol (composition, not a re-implementation) for the day count, 7// then does the interest in exact integer minor units. 8// 9// FAIL-CLOSED: a judgment date before the accrual date, a negative principal, or a negative rate returns 10// DMG_BAD -- you never compute interest on an impossible interval. The year basis is a DECLARED constant 11// (statutes differ: 365 vs 360); it is not buried (Rule 11). 12// 13// SCALE ENVELOPE (declared): principal * annual_bp * days fits i64 for principal up to ~9e10 minor units at 14// bp<=1e4, days<=1e4. license_tier: ORIGINAL No hw writes (Rule 26). LIB. 15 16import "nx_sol_lib.nx" 17 18const DMG_YEAR_DAYS: i64 = 365 // declared day-count basis (statute-configurable; 360 is the alternative) 19const DMG_BP_FULL: i64 = 10000 20const DMG_BAD: i64 = 0 - 2000000002 21 22// exact day count between two civil dates, via nx_sol's day-number engine. FAIL-CLOSED if end precedes start. 23func dmg_days_between(y1: i64, m1: i64, d1: i64, y2: i64, m2: i64, d2: i64) -> i64 { 24 let start: i64 = sol_days_from_civil(y1, m1, d1) 25 let end: i64 = sol_days_from_civil(y2, m2, d2) 26 if end < start { return DMG_BAD } 27 return end - start 28} 29 30// SIMPLE prejudgment interest: principal * rate * days / year. Truncated down. Fail-closed on bad input. 31func dmg_simple_interest(principal: i64, annual_bp: i64, days: i64) -> i64 { 32 if principal < 0 { return DMG_BAD } 33 if annual_bp < 0 { return DMG_BAD } 34 if days < 0 { return DMG_BAD } 35 return principal * annual_bp * days / (DMG_BP_FULL * DMG_YEAR_DAYS) 36} 37 38// ANNUALLY COMPOUNDED interest over whole years: returns the interest (balance minus principal). 39func dmg_compound_interest(principal: i64, annual_bp: i64, years: i64) -> i64 { 40 if principal < 0 { return DMG_BAD } 41 if annual_bp < 0 { return DMG_BAD } 42 if years < 0 { return DMG_BAD } 43 var bal: i64 = principal 44 var i: i64 = 0 45 while i < years { 46 bal = bal + bal * annual_bp / DMG_BP_FULL 47 i = i + 1 48 } 49 return bal - principal 50} 51 52// total award = principal + interest, propagating the fail-closed sentinel. 53func dmg_total_award(principal: i64, interest: i64) -> i64 { 54 if principal < 0 { return DMG_BAD } 55 if interest == DMG_BAD { return DMG_BAD } 56 if interest < 0 { return DMG_BAD } 57 return principal + interest 58}