juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

selftest_calc.c (16819B)


      1 /* selftest_calc.c - expresiones de punta a punta: texto -> tokens -> RPN -> Num -> formato. */
      2 #include <stdio.h>
      3 #include <string.h>
      4 #include "selftest.h"
      5 #include "eval.h"
      6 #include "fmt.h"
      7 #include "dmath.h"
      8 #include "str.h"
      9 
     10 typedef struct { const char *in; int ang; const char *exact, *dec; } Case;
     11 
     12 /* exact: lo que muestra en modo exacto; dec: con S⇔D (o "" si es igual) */
     13 static const Case CASES[] = {
     14     { "1+2", ANG_DEG, "3", "" },
     15     { "0.1+0.2", ANG_DEG, "3/10", "0.3" },
     16     { "\\f{1}{3}+\\f{1}{6}", ANG_DEG, "1/2", "0.5" },
     17     { "1:3", ANG_DEG, "1/3", "0.3333333333" },
     18     { "2:3*3", ANG_DEG, "2", "" },
     19     { "-2^{2}", ANG_DEG, "-4", "" },
     20     { "(-2)^{2}", ANG_DEG, "4", "" },
     21     { "2^{3}^{2}", ANG_DEG, "64", "" },
     22     { "1:2\\pi", ANG_DEG, "0.1591549431", "" },
     23     { "\\r{8}", ANG_DEG, "2√2", "2.828427125" },
     24     { "\\r{12}+\\r{3}", ANG_DEG, "3√3", "5.196152423" },
     25     { "\\f{1}{\\r{2}}", ANG_DEG, "√2/2", "0.7071067812" },
     26     { "\\f{1+\\r{5}}{2}", ANG_DEG, "(1+√5)/2", "1.618033989" },
     27     { "\\r{2}*\\r{3}", ANG_DEG, "√6", "2.449489743" },
     28     { "\\r{-4}", ANG_DEG, "Error", "" },
     29     { "sin(30)", ANG_DEG, "1/2", "0.5" },
     30     { "sin(30", ANG_DEG, "1/2", "0.5" },
     31     { "cos(45)", ANG_DEG, "√2/2", "0.7071067812" },
     32     { "tan(60)", ANG_DEG, "√3", "1.732050808" },
     33     { "tan(90)", ANG_DEG, "Error", "" },
     34     { "sin(18)", ANG_DEG, "(-1+√5)/4", "0.3090169944" },
     35     { "sin(\\f{\\pi}{6})", ANG_RAD, "1/2", "0.5" },
     36     { "cos(\\pi)", ANG_RAD, "-1", "" },
     37     { "sin(1)", ANG_RAD, "0.8414709848", "" },
     38     { "asin(\\f{1}{2})", ANG_DEG, "30", "" },
     39     { "asin(\\f{1}{2})", ANG_RAD, "π/6", "0.5235987756" },
     40     { "acos(-1)", ANG_RAD, "π", "3.141592654" },
     41     { "atan(1)", ANG_GRA, "50", "" },
     42     { "2\\pi", ANG_DEG, "2π", "6.283185307" },
     43     { "\\pi:4*3", ANG_DEG, "3π/4", "2.35619449" },
     44     { "log(1000)", ANG_DEG, "3", "" },
     45     { "\\l{2}{8}", ANG_DEG, "3", "" },
     46     { "\\l{2}{\\f{1}{8}}", ANG_DEG, "-3", "" },
     47     { "ln(1)", ANG_DEG, "0", "" },
     48     { "ln(0)", ANG_DEG, "Error", "" },
     49     { "e^{1}", ANG_DEG, "2.718281828", "" },
     50     { "5!", ANG_DEG, "120", "" },
     51     { "10\\C3", ANG_DEG, "120", "" },
     52     { "10\\P3", ANG_DEG, "720", "" },
     53     { "50%", ANG_DEG, "1/2", "0.5" },
     54     { "2\\inv", ANG_DEG, "1/2", "0.5" },
     55     { "\\n{3}{-27}", ANG_DEG, "-3", "" },
     56     { "\\n{3}{2}", ANG_DEG, "1.25992105", "" },
     57     { "8^{\\f{1}{3}}", ANG_DEG, "2", "" },
     58     { "4^{1.5}", ANG_DEG, "8", "" },
     59     { "2^{0.5}", ANG_DEG, "√2", "1.414213562" },
     60     { "\\a{-3}", ANG_DEG, "3", "" },
     61     { "\\m{1}{1}{2}", ANG_DEG, "3/2", "1.5" },
     62     { "1.5\\E3", ANG_DEG, "1500", "" },
     63     { "1\\E-3", ANG_DEG, "1/1000", "1E-3" },
     64     { "123456789*987654321", ANG_DEG, "1.219326311E17", "" },
     65     { "2^{100}", ANG_DEG, "1.2676506E30", "" },
     66     { "1:7^{20}", ANG_DEG, "1.253254289E-17", "" },
     67     { "gcd(12,18)", ANG_DEG, "6", "" },
     68     { "lcm(4,6)", ANG_DEG, "12", "" },
     69     { "int(-2.5)", ANG_DEG, "-2", "" },
     70     { "intg(-2.5)", ANG_DEG, "-3", "" },
     71     { "2(3+4)", ANG_DEG, "14", "" },
     72     { "(1+2)(3+4)", ANG_DEG, "21", "" },
     73     { "2sin(30)", ANG_DEG, "1", "" },
     74     { "1+", ANG_DEG, "Syntax", "" },
     75     { "\\f{}{2}", ANG_DEG, "Syntax", "" },
     76     { "(1+2", ANG_DEG, "3", "" },
     77     { "1:0", ANG_DEG, "Error", "" },
     78     { "sinh(1)", ANG_DEG, "1.175201194", "" },
     79     { "\\vA+1", ANG_DEG, "1", "" },
     80     { "e", ANG_DEG, "2.718281828", "" },
     81     { "3\\pi-\\pi", ANG_DEG, "2π", "6.283185307" },
     82     { "\\f{2}{4}", ANG_DEG, "1/2", "0.5" },
     83     { "\\f{\\r{3}}{2}*\\f{\\r{3}}{2}", ANG_DEG, "3/4", "0.75" },
     84     { "\\f{1}{1+\\r{2}}", ANG_DEG, "-1+√2", "0.4142135624" },
     85     /* analisis */
     86     { "\\i{0}{1}{x^{2}}", ANG_DEG, "0.3333333333", "" },
     87     { "\\i{0}{\\pi}{sin(x)}", ANG_RAD, "2", "" },
     88     { "\\i{0}{1}{\\f{4}{1+x^{2}}}", ANG_DEG, "3.141592654", "" },
     89     { "\\i{1}{\\e}{\\f{1}{x}}", ANG_DEG, "1", "" },
     90     { "\\i{0}{2}{\\i{0}{1}{x}}", ANG_DEG, "1", "" },
     91     { "\\i{0}{10}{\\e^{-x}}", ANG_DEG, "0.9999546001", "" },
     92     { "\\d{x^{3}}{2}", ANG_DEG, "12", "" },
     93     { "\\d{sin(x)}{0}", ANG_RAD, "1", "" },
     94     { "\\d{ln(x)}{4}", ANG_DEG, "0.25", "" },
     95     { "\\s{1}{100}{x}", ANG_DEG, "5050", "" },
     96     { "\\s{1}{4}{\\f{1}{x}}", ANG_DEG, "25/12", "2.083333333" },
     97     { "\\p{1}{5}{x}", ANG_DEG, "120", "" },
     98     { "\\s{1}{10}{x}+\\vX", ANG_DEG, "55", "" },
     99     /* distribuciones (valores de referencia de tablas) */
    100     { "normpd(0,1,0)", ANG_DEG, "0.3989422804", "" },
    101     { "normcd(-1,1,1,0)", ANG_DEG, "0.6826894921", "" },
    102     { "normcd(6,1\\E99,1,0)", ANG_DEG, "9.86587645E-10", "" },
    103     { "invnorm(0.975,1,0)", ANG_DEG, "1.959963985", "" },
    104     { "invnorm(0.05,2,10)", ANG_DEG, "6.710292746", "" },
    105     { "binompd(3,10,0.5)", ANG_DEG, "0.1171875", "" },
    106     { "binomcd(3,10,0.5)", ANG_DEG, "0.171875", "" },
    107     { "binompd(500,1000,0.5)", ANG_DEG, "0.02522501818", "" },
    108     { "poisspd(2,3)", ANG_DEG, "0.2240418077", "" },
    109     { "poisscd(2,3)", ANG_DEG, "0.4231900811", "" },
    110     /* sexagesimal */
    111     { "12\\deg30\\deg", ANG_DEG, "25/2", "12.5" },
    112     { "1\\deg30\\deg36\\deg", ANG_DEG, "151/100", "1.51" },
    113     { "sin(30\\deg)", ANG_DEG, "1/2", "0.5" },
    114 };
    115 
    116 static int run(const char *in, int ang, bool want_dec, char *out, int n)
    117 {
    118     static Expr e;
    119     static Num vars[NVARS];
    120     if (!expr_from_text(&e, in)) return snprintf(out, n, "Texto?");
    121     EvalCtx cx = { 0 };
    122     cx.ang = ang;
    123     cx.vars = vars;
    124     cx.ans = num_int(0);
    125     cx.preans = num_int(0);
    126     Num r;
    127     int pos, err = calc_eval(&e, &cx, &r, &pos);
    128     if (err == CE_SYNTAX) return snprintf(out, n, "Syntax");
    129     if (err) return snprintf(out, n, "Error");
    130     Fmt f;
    131     fmt_default(&f);
    132     return fmt_plain(&r, &f, want_dec, out, n);
    133 }
    134 
    135 void selftest_calc(void)
    136 {
    137     char got[128];
    138     for (size_t i = 0; i < sizeof CASES / sizeof CASES[0]; i++) {
    139         const Case *c = &CASES[i];
    140         run(c->in, c->ang, false, got, sizeof got);
    141         st_check(!strcmp(got, c->exact), "%-28s = %s (esperaba %s)", c->in, got, c->exact);
    142         if (c->dec[0]) {
    143             run(c->in, c->ang, true, got, sizeof got);
    144             st_check(!strcmp(got, c->dec), "%-28s ≈ %s (esperaba %s)", c->in, got, c->dec);
    145         }
    146     }
    147 }
    148 
    149 /* ./calc --eval "texto": muestra exacto y decimal */
    150 int eval_cli(const char *in)
    151 {
    152     char a[128], b[128];
    153     run(in, ANG_DEG, false, a, sizeof a);
    154     run(in, ANG_DEG, true, b, sizeof b);
    155     printf("%s\n", a);
    156     if (strcmp(a, b)) printf("%s\n", b);
    157     return 0;
    158 }
    159 
    160 /* edicion: se tipea la secuencia (con '<' = BKSP, '[' = izquierda, ']' = derecha) */
    161 static void typed(const char *keys, const char *want, int want_cur)
    162 {
    163     static Expr e;
    164     char got[256];
    165     ex_clear(&e);
    166     for (const char *k = keys; *k; k++) {
    167         if (*k == '<') ex_backspace(&e);
    168         else if (*k == '[') ex_left(&e);
    169         else if (*k == ']') ex_right(&e);
    170         else if (*k == '*') ex_insert(&e, T_MUL);
    171         else ex_insert(&e, (unsigned char)*k);
    172     }
    173     expr_to_text(&e, got, sizeof got);
    174     st_check(!strcmp(got, want) && (want_cur < 0 || e.cur == want_cur), "tipear %-14s -> %s cur %d (esperaba %s cur %d)",
    175              keys, got, e.cur, want, want_cur);
    176 }
    177 
    178 void selftest_edit(void)
    179 {
    180     typed("1/2+1", "\\F{1}{2}+1", -1);
    181     typed("/", "\\f{}{}", 1);
    182     typed("/3]4", "\\f{3}{4}", 4);
    183     typed("12/34", "\\F{12}{34}", 6);
    184     typed("sqrt(2)+1", "\\r{2}+1", -1);
    185     typed("sqrt(2+3)", "\\r{2+3}", -1);
    186     typed("2^3+1", "2^{3}+1", -1);
    187     typed("2^-3", "2^{-3}", -1);
    188     typed("pi", "\\pi", -1);
    189     typed("2pi", "2\\pi", -1);
    190     typed("ans+1", "\\ans+1", -1);
    191     typed("sin(30", "sin(30", -1);
    192     typed("asin(1", "asin(1", -1);
    193     typed("1/(2+3)+1", "\\F{1}{2+3}+1", -1);
    194     typed("(1+2)/3", "\\F{1+2}{3}", -1);
    195     typed("2^(1+2)*3", "2^{1+2}*3", -1);
    196     typed("(1)(2)/3", "(1)\\F{2}{3}", -1);
    197     typed("sin(30)/2", "\\F{sin(30)}{2}", -1);
    198     typed("2^3/4", "\\F{2^{3}}{4}", -1);
    199     typed("1/2<<", "\\F{1}{}", 2);
    200     typed("1/2<<<<", "", 0);
    201     typed("abs(-3)", "\\a{-3}", -1);
    202     typed("cbrt(8)", "\\n{3}{8}", -1);
    203     typed("exp(2)", "\\e\\x{2}", -1);
    204     typed("Ax2", "\\vAx2", -1);
    205     typed("1/2*3", "\\F{1}{2}*3", -1);
    206     typed("12[[/", "\\f{}{}12", 1);
    207     typed("integ(0,1,x^2)", "\\i{0}{1}{x^{2}}", -1);
    208     typed("deriv(x^3,2)", "\\d{x^{3}}{2}", -1);
    209     typed("sum(1,10,x)+1", "\\s{1}{10}{x}+1", -1);
    210     typed("logb(2,8)", "\\l{2}{8}", -1);
    211 }
    212 
    213 #include "calc.h"
    214 
    215 static void keys(Calc *c, const char *k)
    216 {
    217     for (; *k; k++) {
    218         int key = (unsigned char)*k;
    219         if (key == '~') key = K_OK;
    220         else if (key == '<') key = K_BACK;
    221         else if (key == '\t') key = K_TAB;
    222         else if (key == 'v') key = K_DOWN;       /* (ninguna prueba tipea la letra v) */
    223         else if (key == '&') key = '<';          /* el < de verdad (∠ en complejos) */
    224         else if (key == 1) key = K_UP;
    225         else if (key == 2) key = K_RIGHT;
    226         calc_key(c, key);
    227     }
    228 }
    229 
    230 void selftest_app(void)
    231 {
    232     static Calc a, b;
    233     static char buf[32000];
    234     calc_init(&a);
    235     keys(&a, "1/3+1/6~");
    236     st_check(a.shown && !a.err && a.res.exact && a.res.x.a == 1 && a.res.x.d == 2, "app: 1/3+1/6 = 1/2");
    237     keys(&a, "*2~");
    238     st_check(!a.err && a.res.exact && a.res.x.a == 1 && a.res.x.d == 1, "app: Ans*2 = 1");
    239     keys(&a, "sqrt(8)~>A");
    240     keys(&a, "A^2~");
    241     st_check(!a.err && a.res.exact && a.res.x.a == 8 && a.res.x.d == 1, "app: STO A, A^2 = 8");
    242     keys(&a, "0.1+0.2~\t");
    243     st_check(a.want_dec, "app: S<->D");
    244     a.ang = ANG_RAD;
    245     a.fmt.mode = FMT_FIX; a.fmt.n = 3;
    246     int n = persist_save(&a, buf, sizeof buf);
    247     calc_init(&b);
    248     persist_load(&b, buf, n);
    249     st_check(b.nh == a.nh && b.ang == ANG_RAD && b.fmt.mode == FMT_FIX && b.fmt.n == 3, "guardar/cargar: ajustes e historial (%d de %d)", b.nh, a.nh);
    250     st_check(b.vars[VAR_A].exact && b.vars[VAR_A].x.b == 2 && b.vars[VAR_A].x.r == 2, "guardar/cargar: A = 2√2 exacto");
    251     char t1[256], t2[256];
    252     expr_to_text(&a.hist[0].in, t1, sizeof t1);
    253     expr_to_text(&b.hist[0].in, t2, sizeof t2);
    254     st_check(!strcmp(t1, t2), "guardar/cargar: historial %s = %s", t1, t2);
    255     /* °′″ del resultado */
    256     calc_init(&a);
    257     keys(&a, "1.51~'");
    258     static Expr d;
    259     calc_result_expr(&a, &a.res, false, &d);
    260     char ds[64] = "";
    261     fmt_plain(&a.res, &a.fmt, false, ds, sizeof ds);
    262     {
    263         Num tmpn; (void)tmpn;
    264         char dd[64];
    265         int p = 0;
    266         for (int i = 0; i < d.n; i++) {
    267             int t = d.t[i];
    268             p = str_put(dd, sizeof dd, p, t == T_DEGS ? "°" : t == T_MIN ? "'" : t == T_SEC ? "\"" : (char[]){ (char)t, 0 });
    269         }
    270         st_check(a.dms && !strcmp(dd, "1°30'36\""), "°′″: 1.51 = %s (esperaba 1°30'36\")", dd);
    271     }
    272 }
    273 
    274 /* modos: ecuaciones, estadistica y programador, manejados con teclas como en la app */
    275 void selftest_modes(void)
    276 {
    277     static Calc c;
    278     char b[128];
    279     calc_init(&c);
    280     /* sistema 3x3: x+y+z=6, 2y+5z=-4, 2x+5y-z=27 -> 5, 3, -2 */
    281     keys(&c, "@2v~");
    282     keys(&c, "1~1~1~6~0~2~5~-4~2~5~-1~27~~");
    283     st_check(c.eq.nsol == 3, "sistema 3x3 resuelto");
    284     fmt_plain(&c.eq.sol[0].re, &c.fmt, false, b, sizeof b);
    285     st_check(!strcmp(b, "5"), "sistema 3x3: x = %s (esperaba 5)", b);
    286     fmt_plain(&c.eq.sol[2].re, &c.fmt, false, b, sizeof b);
    287     st_check(!strcmp(b, "-2"), "sistema 3x3: z = %s (esperaba -2)", b);
    288     /* x² - 2 = 0 -> ±√2 exacto */
    289     calc_init(&c);
    290     keys(&c, "@2vv~1~0~-2~~");
    291     fmt_plain(&c.eq.sol[0].re, &c.fmt, false, b, sizeof b);
    292     st_check(!strcmp(b, "√2"), "x²-2: x1 = %s (esperaba √2)", b);
    293     /* x³ - 1 = 0 -> 1 y complejas -1/2 ± √3/2 i */
    294     calc_init(&c);
    295     keys(&c, "@2vvv~1~0~0~-1~~");
    296     fmt_plain(&c.eq.sol[0].re, &c.fmt, false, b, sizeof b);
    297     st_check(!strcmp(b, "1"), "x³-1: raiz real %s", b);
    298     fmt_plain(&c.eq.sol[1].im, &c.fmt, false, b, sizeof b);
    299     st_check(!strcmp(b, "√3/2") || !strcmp(b, "-√3/2"), "x³-1: parte imaginaria %s", b);
    300     /* x³ - 2x - 5 = 0: raiz real 2.094551482 (sin raices racionales) */
    301     calc_init(&c);
    302     keys(&c, "@2vvv~1~0~-2~-5~~");
    303     fmt_plain(&c.eq.sol[0].re, &c.fmt, false, b, sizeof b);
    304     st_check(!strcmp(b, "2.094551482"), "x³-2x-5: %s (esperaba 2.094551482)", b);
    305     /* SOLVE: cos(X) = X en radianes */
    306     calc_init(&c);
    307     c.ang = ANG_RAD;
    308     keys(&c, "@2vvvv~cos(x)=x~");
    309     fmt_plain(&c.eq.solve_x, &c.fmt, false, b, sizeof b);
    310     st_check(!strcmp(b, "0.7390851332"), "SOLVE cos x = x: %s", b);
    311     /* estadistica lineal: (1,2) (2,4) (3,6.1) */
    312     calc_init(&c);
    313     keys(&c, "@6v~1~2~2~4~3~6.1~~");
    314     st_check(c.st.phase == 1 && c.st.rvalid, "estadística: regresión lineal");
    315     fmt_plain(&c.st.rb, &c.fmt, false, b, sizeof b);
    316     st_check(!strcmp(b, "41/20"), "estadística: b = %s (esperaba 41/20)", b);
    317     fmt_plain(&c.st.ra, &c.fmt, false, b, sizeof b);
    318     st_check(!strcmp(b, "-1/15"), "estadística: a = %s (esperaba -1/15)", b);
    319     /* ŷ(4) en Calcular */
    320     keys(&c, "<@1yhat(4)~");
    321     fmt_plain(&c.res, &c.fmt, false, b, sizeof b);
    322     st_check(!strcmp(b, "122/15"), "ŷ(4) = %s (esperaba 122/15)", b);
    323     /* programador: FF + 1 en hex, -1 en 32 bits, AND */
    324     calc_init(&c);
    325     keys(&c, "@5\tFF+1~");
    326     st_check(!c.err && c.res.v.m && dec_cmp(c.res.v, dec_int(256)) == 0, "programador: FF+1 = 256");
    327     keys(&c, "<\t\t\t");
    328     keys(&c, "255 and 15~");
    329     st_check(!c.err && dec_cmp(c.res.v, dec_int(15)) == 0, "programador: 255 and 15 = 15");
    330     keys(&c, "7:2~");
    331     st_check(!c.err && dec_cmp(c.res.v, dec_int(3)) == 0, "programador: 7÷2 = 3");
    332     keys(&c, "2147483647+1~");
    333     st_check(!c.err && dec_cmp(c.res.v, dec_int(-2147483648ll)) == 0, "programador: desborde de 32 bits");
    334 
    335     /* complejos */
    336     static const struct { const char *in, *want; } CX[] = {
    337         { "(3+4i)(1-2i)~", "11-2i" }, { "sqrt(-4)~", "2i" }, { "(1+i)/(1-i)~", "i" },
    338         { "2&60~", "1+√3i" }, { "|3+4i|~", "5" }, { "arg(1+i)~", "45" }, { "(1+i)^8~", "16" },
    339         { "i^2~", "-1" }, { "conjg(2-3i)~", "2+3i" }, { "1/i~", "-i" }, { "(2+i)^-1~", "2/5-1/5i" },
    340         { "sqrt(2i)~", "1+i" }, { "imag(5-7i)~", "-7" },
    341     };
    342     calc_init(&c);
    343     keys(&c, "@7");
    344     for (size_t i = 0; i < sizeof CX / sizeof CX[0]; i++) {
    345         keys(&c, "<");
    346         keys(&c, CX[i].in);
    347         static Expr r;
    348         char t[96];
    349         calc_res_expr(&c, &r);
    350         /* texto lineal del resultado (√ y fracciones) */
    351         Num dummy; (void)dummy;
    352         int p = 0;
    353         t[0] = 0;
    354         for (int k = 0; k < r.n; k++) {
    355             int tk = r.t[k];
    356             const char *s2 = tk == T_SQRT ? "√" : tk == T_FRAC ? "" : tk == T_SEP ? "/" : tk == T_END ? "" :
    357                              tk == T_ANGLE ? "∠" : 0;
    358             char ch[2] = { (char)tk, 0 };
    359             p = str_put(t, sizeof t, p, s2 ? s2 : ch);
    360         }
    361         st_check(!c.err && !strcmp(t, CX[i].want), "complejos: %s = %s (esperaba %s)", CX[i].in, c.err ? "error" : t, CX[i].want);
    362     }
    363     keys(&c, "<(1+i)^8~&");
    364     {
    365         static Expr r;
    366         calc_res_expr(&c, &r);
    367         st_check(c.polar && r.n >= 3 && r.t[2] == T_ANGLE, "complejos: polar 16∠0");
    368     }
    369 
    370     /* matrices: MatA = [2 1; 1 3], VctA = (1,2,3), VctB = (4,5,6) */
    371     calc_init(&c);
    372     keys(&c, "@8=~~2~1~1~3~<");
    373     keys(&c, "=vvvv~\x01\x02v~1~2~3~<");
    374     keys(&c, "=v~\x01\x02v~4~5~6~<");
    375     st_check(c.mt.m[0].r == 2 && c.mt.v[0].c == 3 && c.mt.v[1].c == 3, "matrices: definidas MatA 2x2, VctA y VctB de 3");
    376     static const struct { const char *in, *want; } MX[] = {
    377         { "det(A)~", "5" }, { "A^-1~", "3/5 -1/5 / -1/5 2/5" }, { "A*A~", "5 5 / 5 10" }, { "trn(A)~", "2 1 / 1 3" },
    378         { "A^3~", "15 20 / 20 35" }, { "dot(`A,`B)~", "32" }, { "cross(`A,`B)~", "-3 6 -3" }, { "|`A|~", "√14" },
    379         { "A*identity(2)-A~", "0 0 / 0 0" }, { "2A+A~", "6 3 / 3 9" },
    380     };
    381     for (size_t i = 0; i < sizeof MX / sizeof MX[0]; i++) {
    382         keys(&c, "<");
    383         keys(&c, MX[i].in);
    384         char t[160] = "";
    385         int p = 0;
    386         const MVal *m = &c.mt.res;
    387         for (int r = 0; r < m->r && !c.err; r++) {
    388             if (r) p = str_put(t, sizeof t, p, " / ");
    389             for (int j = 0; j < m->c; j++) {
    390                 char v[48];
    391                 fmt_plain(mv_get(m, r, j), &c.fmt, false, v, sizeof v);
    392                 if (j) p = str_put(t, sizeof t, p, " ");
    393                 p = str_put(t, sizeof t, p, v);
    394             }
    395         }
    396         st_check(!c.err && !strcmp(t, MX[i].want), "matrices: %s = %s (esperaba %s)", MX[i].in, c.err ? "error" : t, MX[i].want);
    397     }
    398     {
    399         static Calc b2;
    400         static char buf2[20000];
    401         int nb = persist_save(&c, buf2, sizeof buf2);
    402         calc_init(&b2);
    403         persist_load(&b2, buf2, nb);
    404         st_check(b2.mt.m[0].r == 2 && b2.mt.v[1].c == 3 && dec_cmp(mv_get(&b2.mt.v[1], 0, 2)->v, dec_int(6)) == 0,
    405                  "matrices: guardar y cargar");
    406     }
    407 }