code wiki / (root) / nx_solve_steps_lib.nx

nx_solve_steps_lib.nx source

↩ module page · 218 lines · 9921 B

1// nx_solve_steps_lib.nx -- A SOLVE, WORKED STEP BY STEP, EACH STEP NAMING THE RULE IT APPLIED. 2// 3// WHY THIS EXISTS (measured 2026-09-03). nx_capsearch over 7,305 sources with corpus_complete=1 shows the 4// estate ALREADY HAS a solver: nx_calc_solve ships nx_calc_solve_linear and nx_calc_solve_quadratic and 5// returns roots. What it does not produce is the WORKING. The rival the operator named states its own 6// product as "get step-by-step algebra answers instantly", so the gap against it was never the arithmetic 7// -- it was the explanation. This organ is that join, and it is deliberately a SEPARATE lib rather than a 8// change to the solver, because "what is the answer" and "how would a person get there" are two questions 9// and one organ should do one thing. 10// 11// EVERY STEP CARRIES ITS RULE. A worked solution that shows lines without naming the operation is a 12// magic trick; a learner cannot tell a legal move from a wrong one. So each row is RULE then LaTeX, and 13// the LaTeX goes through nx_mathml at render time, so the page shows real typeset mathematics with no 14// third-party typesetter anywhere in the path. 15// 16// ★EXACT WHERE EXACT IS POSSIBLE, AND HONEST WHERE IT IS NOT. Rational answers are reduced by gcd and 17// printed as fractions, never as truncated decimals. When a quadratic root is irrational this says so and 18// prints the EXACT radical form rather than a rounded decimal pretending to be the answer. A solver that 19// silently rounds is teaching that mathematics is approximately true. 20// 21// license_tier: ORIGINAL 22import "syscalls.nx" 23import "nx_isqrt.nx" 24 25const SS_OK: i64 = 0 26const SS_ERR_SHORT: i64 = 0 - 1 27const SS_ERR_NOT_LINEAR: i64 = 0 - 2 // a == 0 in a*x + b = c is not an equation in x 28const SS_ERR_NOT_QUAD: i64 = 0 - 3 // a == 0 in a*x^2 + bx + c is the linear case, refuse by name 29const SS_MAXSTEPS: i64 = 16 30 31func ss_slen(s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { i = i + 1 } return i } 32 33func ss_put(b: *u8, off: i64, cap: i64, s: *u8) -> i64 { 34 if off < 0 { return off } 35 let n: i64 = ss_slen(s) 36 if off + n >= cap { return SS_ERR_SHORT } 37 var i: i64 = 0 38 while i < n { b[off + i] = s[i]; i = i + 1 } 39 return off + n 40} 41 42func ss_num(b: *u8, off: i64, cap: i64, v: i64) -> i64 { 43 if off < 0 { return off } 44 var o: i64 = off 45 var m: i64 = v 46 if m < 0 { o = ss_put(b, o, cap, "-" as *u8); m = 0 - m } 47 let t: *u8 = sys_mmap(32) 48 var k: i64 = 0 49 if m == 0 { t[0] = 48 as u8; k = 1 } 50 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 51 var i: i64 = 0 52 while i < k { 53 if o < 0 { i = k } else { 54 if o + 1 >= cap { o = SS_ERR_SHORT } else { b[o] = t[k - 1 - i]; o = o + 1; i = i + 1 } 55 } 56 } 57 sys_munmap(t, 32) 58 return o 59} 60 61func ss_gcd(a: i64, b: i64) -> i64 { 62 var x: i64 = a 63 var y: i64 = b 64 if x < 0 { x = 0 - x } 65 if y < 0 { y = 0 - y } 66 while y != 0 { let t: i64 = x % y; x = y; y = t } 67 if x == 0 { return 1 } 68 return x 69} 70 71// Emit a rational as reduced LaTeX: an integer when it divides, a \frac otherwise, with the sign carried 72// on the numerator so a reader never meets a negative denominator. 73func ss_rational(b: *u8, off: i64, cap: i64, num: i64, den: i64) -> i64 { 74 var n: i64 = num 75 var d: i64 = den 76 if d < 0 { n = 0 - n; d = 0 - d } 77 let g: i64 = ss_gcd(n, d) 78 n = n / g 79 d = d / g 80 if d == 1 { return ss_num(b, off, cap, n) } 81 var o: i64 = ss_put(b, off, cap, "\\frac{" as *u8) 82 o = ss_num(b, o, cap, n) 83 o = ss_put(b, o, cap, "}{" as *u8) 84 o = ss_num(b, o, cap, d) 85 return ss_put(b, o, cap, "}" as *u8) 86} 87 88// One row of the worked solution: RULE, a tab, the LaTeX, a newline. Chosen so a caller splits on the tab 89// and hands column two straight to the typesetter with no parsing of mathematics in the page layer. 90func ss_row(b: *u8, off: i64, cap: i64, rule: *u8) -> i64 { 91 var o: i64 = ss_put(b, off, cap, rule) 92 return ss_put(b, o, cap, "\t" as *u8) 93} 94 95func ss_end(b: *u8, off: i64, cap: i64) -> i64 { return ss_put(b, off, cap, "\n" as *u8) } 96 97// Write a*x + b (with the sign of b folded in) into the buffer. 98func ss_linear_lhs(b: *u8, off: i64, cap: i64, a: i64, bb: i64) -> i64 { 99 var o: i64 = off 100 if a != 1 { if a == 0 - 1 { o = ss_put(b, o, cap, "-" as *u8) } else { o = ss_num(b, o, cap, a) } } 101 o = ss_put(b, o, cap, "x" as *u8) 102 if bb > 0 { o = ss_put(b, o, cap, " + " as *u8); o = ss_num(b, o, cap, bb) } 103 if bb < 0 { o = ss_put(b, o, cap, " - " as *u8); o = ss_num(b, o, cap, 0 - bb) } 104 return o 105} 106 107// SOLVE a*x + b = c, SHOWING THE WORK. Returns bytes written, or a negative SS_ERR_*. 108func ss_steps_linear(a: i64, b0: i64, c: i64, out: *u8, cap: i64) -> i64 { 109 // A refusal by name, because a*x + b = c with a == 0 is not an equation in x at all: it is either a 110 // contradiction or an identity, and answering it with a number would be a lie about what was asked. 111 if a == 0 { return SS_ERR_NOT_LINEAR } 112 var o: i64 = 0 113 114 o = ss_row(out, o, cap, "the equation as given" as *u8) 115 o = ss_linear_lhs(out, o, cap, a, b0) 116 o = ss_put(out, o, cap, " = " as *u8) 117 o = ss_num(out, o, cap, c) 118 o = ss_end(out, o, cap) 119 120 o = ss_row(out, o, cap, "subtract the constant from both sides, which keeps the equality true" as *u8) 121 if a != 1 { if a == 0 - 1 { o = ss_put(out, o, cap, "-" as *u8) } else { o = ss_num(out, o, cap, a) } } 122 o = ss_put(out, o, cap, "x = " as *u8) 123 o = ss_num(out, o, cap, c - b0) 124 o = ss_end(out, o, cap) 125 126 o = ss_row(out, o, cap, "divide both sides by the coefficient of x" as *u8) 127 o = ss_put(out, o, cap, "x = " as *u8) 128 o = ss_put(out, o, cap, "\\frac{" as *u8) 129 o = ss_num(out, o, cap, c - b0) 130 o = ss_put(out, o, cap, "}{" as *u8) 131 o = ss_num(out, o, cap, a) 132 o = ss_put(out, o, cap, "}" as *u8) 133 o = ss_end(out, o, cap) 134 135 o = ss_row(out, o, cap, "reduce the fraction to lowest terms, exactly and with no rounding" as *u8) 136 o = ss_put(out, o, cap, "x = " as *u8) 137 o = ss_rational(out, o, cap, c - b0, a) 138 o = ss_end(out, o, cap) 139 return o 140} 141 142// SOLVE a*x^2 + b*x + c = 0, SHOWING THE WORK, and telling the truth about irrational roots. 143func ss_steps_quadratic(a: i64, b: i64, c: i64, out: *u8, cap: i64) -> i64 { 144 if a == 0 { return SS_ERR_NOT_QUAD } 145 var o: i64 = 0 146 147 o = ss_row(out, o, cap, "the equation as given" as *u8) 148 if a != 1 { o = ss_num(out, o, cap, a) } 149 o = ss_put(out, o, cap, "x^{2}" as *u8) 150 if b > 0 { o = ss_put(out, o, cap, " + " as *u8); if b != 1 { o = ss_num(out, o, cap, b) } o = ss_put(out, o, cap, "x" as *u8) } 151 if b < 0 { o = ss_put(out, o, cap, " - " as *u8); if b != 0 - 1 { o = ss_num(out, o, cap, 0 - b) } o = ss_put(out, o, cap, "x" as *u8) } 152 if c > 0 { o = ss_put(out, o, cap, " + " as *u8); o = ss_num(out, o, cap, c) } 153 if c < 0 { o = ss_put(out, o, cap, " - " as *u8); o = ss_num(out, o, cap, 0 - c) } 154 o = ss_put(out, o, cap, " = 0" as *u8) 155 o = ss_end(out, o, cap) 156 157 o = ss_row(out, o, cap, "the quadratic formula, which solves every equation of this shape" as *u8) 158 o = ss_put(out, o, cap, "x = \\frac{-b \\pm \\sqrt{b^{2} - 4ac}}{2a}" as *u8) 159 o = ss_end(out, o, cap) 160 161 let disc: i64 = b * b - 4 * a * c 162 o = ss_row(out, o, cap, "compute the discriminant, which decides how many real roots there are" as *u8) 163 o = ss_put(out, o, cap, "b^{2} - 4ac = " as *u8) 164 o = ss_num(out, o, cap, disc) 165 o = ss_end(out, o, cap) 166 167 if disc < 0 { 168 // NOT AN ERROR, AND NOT AN EMPTY ANSWER: a negative discriminant is a real fact about the 169 // equation, and saying so is the correct result rather than a failure to produce roots. 170 o = ss_row(out, o, cap, "the discriminant is negative, so there are NO real roots" as *u8) 171 o = ss_put(out, o, cap, "x \\notin \\mathbb{R}" as *u8) 172 o = ss_end(out, o, cap) 173 return o 174 } 175 176 let r: i64 = nx_isqrt(disc) 177 if r * r == disc { 178 o = ss_row(out, o, cap, "the discriminant is a perfect square, so both roots are EXACT rationals" as *u8) 179 o = ss_put(out, o, cap, "\\sqrt{" as *u8) 180 o = ss_num(out, o, cap, disc) 181 o = ss_put(out, o, cap, "} = " as *u8) 182 o = ss_num(out, o, cap, r) 183 o = ss_end(out, o, cap) 184 185 o = ss_row(out, o, cap, "first root, reduced to lowest terms" as *u8) 186 o = ss_put(out, o, cap, "x = " as *u8) 187 o = ss_rational(out, o, cap, 0 - b + r, 2 * a) 188 o = ss_end(out, o, cap) 189 190 o = ss_row(out, o, cap, "second root, reduced to lowest terms" as *u8) 191 o = ss_put(out, o, cap, "x = " as *u8) 192 o = ss_rational(out, o, cap, 0 - b - r, 2 * a) 193 o = ss_end(out, o, cap) 194 return o 195 } 196 197 // ★THE HONEST BRANCH. The roots are irrational, so the EXACT surd is the answer. A decimal here would 198 // be a rounded number wearing the clothes of an exact one, which is precisely what an estate with no 199 // float in its path should refuse to teach. 200 o = ss_row(out, o, cap, "the discriminant is not a perfect square, so the roots are IRRATIONAL and the exact form is the answer" as *u8) 201 o = ss_put(out, o, cap, "x = \\frac{" as *u8) 202 o = ss_num(out, o, cap, 0 - b) 203 o = ss_put(out, o, cap, " \\pm \\sqrt{" as *u8) 204 o = ss_num(out, o, cap, disc) 205 o = ss_put(out, o, cap, "}}{" as *u8) 206 o = ss_num(out, o, cap, 2 * a) 207 o = ss_put(out, o, cap, "}" as *u8) 208 o = ss_end(out, o, cap) 209 return o 210} 211 212func ss_err_name(e: i64) -> *u8 { 213 if e == SS_OK { return "OK" as *u8 } 214 if e == SS_ERR_SHORT { return "OUTPUT-BUFFER-TOO-SMALL" as *u8 } 215 if e == SS_ERR_NOT_LINEAR { return "NOT-AN-EQUATION-IN-X-the-coefficient-of-x-is-zero" as *u8 } 216 if e == SS_ERR_NOT_QUAD { return "NOT-QUADRATIC-the-leading-coefficient-is-zero-solve-it-as-linear" as *u8 } 217 return "UNKNOWN-ERROR" as *u8 218}