python3 verify_bexprvalue.py
63 checks, a few seconds. 37/63 against the pre-fix binary — 26 checks discriminate.
The B source expression path was compared against the paths that read the same number on the same deck. It answered differently twice.
Enhancement-491 replaced a gmin-derived fudge factor with a fixed epsilon and
wrote the nudge as
arg2 = (arg1 >= 0.0) ? PTDIV_EPS : -PTDIV_EPS;meaning to keep the sign of the numerator. But a negative numerator was then divided by a negative epsilon, so the quotient came out positive either way:
| expression | before | after |
|---|---|---|
v(p)/0, v(p) = +3 |
+3e+32 | +3e+32 |
v(p)/0, v(p) = −3 |
+3e+32 | −3e+32 |
-1/0 |
+1e+32 | −1e+32 |
(0-2)/0 |
+2e+32 | −2e+32 |
Every case Enhancement-491 measured had a positive numerator, which is why its
own suite did not catch this. A divisor of exactly zero has no sign to recover,
so the epsilon is now unconditionally positive — zero approached from above
— and the quotient keeps the sign of the numerator, which is what
lim(x/eps), eps→0+ gives. The magnitude, and the gmin independence
Enhancement-491 established, are unchanged and pinned here.
Every B source line is rewritten by inp_modify_exp(), which re-emitted each
literal it found with "%18.10e".
B0 b0 0 v=1.2345678901234567 → reaches the parser as 1.2345678901e+00
a relative error of 1.9e-11, while the same literal written on an R, C
or V card, in a .param, or as an OSDI .model parameter kept all seventeen
digits. The B source was the only path in the simulator that could not carry a
double.
This is not only a printout: it reached the arithmetic. tan(1.5707963267948966)
returned −1.96e11 where libm gives 1.633123935319537e+16 — the argument
was wrong, not the tangent.
INPevaluate() re-reads the text this function emits, and accumulates the
mantissa by hand as mantis = 10 * mantis + digit, which loses a bit once the
accumulator passes 2^53. Handing it the eighteen digits of "%.17e" put
0.7853981633974483 back 1 ulp out, when the user's own sixteen-digit text
would have survived intact. So inp_num_text() emits the shortest text that
reads back as the same double, trying 15, 16 then 17 significant digits.
A few values still land 1–2 ulp out through INPevaluate's own
pow(10, expo) scaling. That residue is shared by every value path — a V
source shows it too — so it is a different, wider question than the one measured
here, and this enhancement does not reach into it. The suite therefore pins
parity and a 2 ulp bound, not bit-exactness: what must never come back is
the 11-digit rounding, which put the same values 1.0e5–1.6e5 ulp out.
mkb() in inpptree.c folds a constant / with a raw division and no zero
guard, bypassing PTdivide entirely. It was probed from B, E and G
sources in constant, parenthesised and mixed forms and never produced an
infinity, so the branch appears unreachable from user input and was left
alone rather than fixed past the evidence.
Also pinned: the SPICE suffixes (100p, 5MEG, 1k), a leading-dot .5,
exponent forms, pwl B sources (which inp_bsource_compat excludes from the
rewrite), current B sources, node references mixed with literals, and the value
surviving a transient.