uncertainty
overview
I added a measurement uncertainty engine to C47 on my DM42n. Enter the variables of a formula along with their standard uncertainties, and the calculator returns the result together with the uncertainty of that result, plus a budget showing which input is responsible for the error.
This is compliant with ISO/IEC Guide 98-3, the standard for stating measurement uncertainty. This is something you’d normally need a spreadsheet for, and rarely find in a basic calculator.
I got my inspiration here from DB48X, another firmware for the same hardware, which has an uncertain number type that propagates a standard deviation through operations. It lacks the rest of the ISO/IEC 98-3 stuff like sensitivity coefficients, a ranked budget, an expanded uncertainty with its coverage factor, effective degrees of freedom.
Oh, and I tried to make this run on the DM42 also, which is a capacity feat.
let’s pack it in
The DM42 has 704 KB of internal flash for the program. The firmware ships in numbered packages, which are different subsets of the features chosen to fit:
| package | flash used | free |
|---|---|---|
| 1 | 720,840 / 720,896 | 56 |
| 2 | 720,336 / 720,896 | 560 |
| 3 | 719,184 / 720,896 | 1,712 |
| 4 | 698,000 / 720,896 | 22,896 |
So the feature is compiled out of the tight packages above, and the source keeps a calibrated table of what each option costs. Mine is 6,504 bytes of flash, measured by building the same tree twice with the option on and off.
Flash turned out to be the easy part. The DM42 build then refused to link:
Error: .bss reaches the DMCP system data block at 0x10002000. Reduce static data.
Static data on the DM42 stops at 0x10002000, about 8 KB, and the firmware was already using 8,104 of it. My budget structure is 2.2 KB. There was no room for it, and none for 200 bytes either.
The fix was to stop calling it static data. The calculator has its own 64 KB arena where registers and matrices live, which is where an object that size belongs. Allocating from the arena on first use took the static cost from 2,228 bytes to 40, and it linked.
That move introduced a bug, which someone else caught. A factory reset wipes the arena and rebuilds the free list, and the code that does it nulls the two pointers the statistics engine keeps into the arena. It knew nothing about mine. After a reset my pointer still aimed into freed memory. One line to fix, and a good reminder that moving into a shared arena means inheriting its invariants. Sanitizers cannot see it either, since it is all inside one allocation they consider valid.
the step size
The C47 uses a fifteen point stencil, exact for polynomials to degree fourteen, and then takes its step as h = x × 10⁻¹⁶. A central difference trades truncation error against rounding error and the balance sits near ε^(1/(p+1)); function values come back at 34 digits, so the optimum for that stencil is around 10⁻², not 10⁻¹⁶. At 10⁻¹⁶ the result is rounding noise and all fourteen orders are thrown away.
The firmware’s own checked-in test expectation for the first derivative of x³ at 5, which is 75:
74.99999999999999999893939393939394
20 digits out of 34. The second derivative gets 7.5, since dividing by h² amplifies the same noise.
So I did not reuse it. A two point central difference at h = max(|x|,1) × 10⁻¹¹ measures about 22 digits using two evaluations instead of fifteen. Six decades better for an eighth of the work. The sophistication was in the wrong place: the step decides whether the stencil matters at all.
Reading it also turned up four defects, the worst being that f’ and f“ returned a silently wrong answer for any program taking its input as a named variable. Fixed upstream now.
how to use the mode
Seven commands, in their own UNCT menu.
| U.EDIT | the input table, one row per variable: u(x) and degrees of freedom |
| U.CORR | correlations, if the inputs are not independent |
| U.P | level of confidence, 95 or 0.95, both work |
| U.CALC | y in Y, u_c(y) in X |
| U.EXP | k in Y, U in X |
| U.BUDG | the ranked budget |
| U.Y | which variable is the measurand, for a formula written with = |
A run is short. Pick the formula with EQN, then:
U.EDIT fill in u for each variable
99 U.P 99 % confidence
U.CALC 50.000838 mm, u_c = 32 nm
U.BUDG lS 25 nm, dtheta 16.68, d 9.7, dalpha 2.9
The combined number tells you how wrong you are; the budget tells you which measurement to go improve.
does it work
The GUM contains its own worked examples in Annex H, with published answers. That is an unusually good acceptance test, because it was written by the people who defined the method.
The engine reproduces H.1, the calibration of an end gauge, giving 50.000838 mm with a combined uncertainty of 32 nm. It reproduces H.2, simultaneous resistance and reactance, which is the correlated example and exercises the covariance terms. It reproduces H.3, a thermometer calibration, both with and without the correlation between the fitted intercept and slope.
The GUM notes that including second order terms raises H.1’s uncertainty from 32 nm to 34 nm. This engine is first order by construction, so 32 nm is its correct answer, and getting 34 would mean the sensitivities were wrong. Encoding the number the approximation should produce, rather than the most precise number in the document, is the difference between a test that checks the method and a test that checks nothing.
what it costs
| flash | 6,504 bytes |
| static memory | 40 bytes |
| arena, on first use | about 2.2 KB |
| evaluations per budget | 2 per variable, plus one |
home | about | github | mastodon
epoch
1785237504