fix: a readout keeps its metric prefix when the stream declares no unit

The SI prefix ladder in format_measure() was gated on a non-empty unit, so two
decimals of a small unitless value printed `0.00` -- the reading gone, not merely
coarse. That is a whole class of channels rather than a corner: a CONFIG need not
declare a unit, and a math channel never has one. Below 1e-12 the same loss
happened even with a unit, so that range falls back to three significant digits.

With no unit the prefix goes tight against the number (`585.24m`), since the space
exists to separate the number from its unit and a spaced `m` alone reads as metres.

The mock declared `unit="V"` for every data signal, which is why no probe could see
this; it now takes `--unit ''`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-10-05 17:28:51 +02:00
1 parent 3d7e214295
commit b505706d92
6 files changed
+66 -25

No files matched your search

+6
View File
@@ -22,6 +22,12 @@ owns a <b>Reset</b> button. Drag a marker to move it. The mode dropdown offers f
• A value is the <i>nearest stored sample's</i>, never a number between two samples. Zoom in past
the sample spacing and the line between two samples is something the plot drew, not data —
so a reading off it would be the drawing. The hover crosshair reads the same way.
• Every reading carries a <i>metric prefix</i> so the digits sit in <tt>1..999</tt>:
<tt>10.52 mV</tt>, not <tt>0.01 V</tt>. Two decimals is all an instrument landing on an
<tt>f32</tt> sample can claim, so without the prefix a small value would read <tt>0.00</tt>
and the reading would be gone. A stream that declares no unit gets the prefix anyway, written
tight against the number (<tt>585.24m</tt>) — the space separates the number from its unit,
and a spaced <tt>m</tt> with nothing after it would read as metres.
• A marker past the newest sample reads <tt>--</tt>. With only one marker over data the row
reads <tt>partial</tt>: a difference between a number and nothing is not a number.
• In <tt>mix</tt> each marker also places a crossing annotation per trace, which is what lets
+5 -1
View File
@@ -27,7 +27,11 @@ set, with <b>Save</b> and <b>Delete</b>.
• A window with too few crossings reads <tt>invalid</tt> / <tt>--</tt> rather than a plausible
invented number. Frequency over a window holding one edge is the usual case.
• <tt>frequency</tt> is reported in Hz and <tt>period</tt>/<tt>pulse duration</tt> in seconds;
every other type carries the channel's own unit.
every other type carries the channel's own unit. A value is written with a <i>metric prefix</i>
so its digits sit in <tt>1..999</tt> — <tt>636.62 mV</tt>, not <tt>0.64 V</tt> — including when
the stream declares no unit at all, which is always the case for a math channel: there the
prefix goes tight against the number (<tt>636.62m</tt>), since two decimals of an unprefixed
small value is <tt>0.00</tt>.
• Cost is bounded by reading the summary pyramid the producer already maintains, not the raw
window. The values are still exact: a bucket's sum is a sum.
+23 -18
View File
@@ -922,6 +922,13 @@ const char* badge_markup(BadgePhase phase) {
/// sample is an `f32` of a value the CONFIG may scale arbitrarily, so more digits would be
/// noise dressed as precision. Trailing zeros go, so `1.50 V` reads `1.5 V` -- "at most
/// two decimals", not exactly two.
///
/// The prefix is applied **whether or not a unit is declared**, and that is the point: two
/// decimals of an unprefixed small value is `0.00`, i.e. the reading is simply gone. A signal
/// whose CONFIG declares no unit is ordinary (and a *math* channel never has one), so the pane
/// showed `0.00` for every value under 5 mV of an entire class of channels. A bare prefix is
/// written tight against the number -- `1.34m`, not `1.34 m` -- because the space is there to
/// separate the number from its unit, and with no unit to separate, a spaced `m` reads as metres.
std::string format_measure(double v, const std::string& unit) {
if (!std::isfinite(v)) return "--";
static const struct {
@@ -934,22 +941,22 @@ std::string format_measure(double v, const std::string& unit) {
const double mag = std::fabs(v);
const char* prefix = "";
double scale = 1.0;
// No unit, no prefix: `10.52 m` is a prefix with nothing to prefix, which reads as
// metres. A bare number stays a bare number and simply keeps its decimals.
if (!unit.empty()) {
for (const auto& p : kPrefixes) {
if (mag >= p.scale) {
prefix = p.prefix;
scale = p.scale;
break;
}
}
if (mag > 0.0 && mag < 1.0e-12) { // below the ladder: keep it in plain digits
prefix = "";
scale = 1.0;
for (const auto& p : kPrefixes) {
if (mag >= p.scale) {
prefix = p.prefix;
scale = p.scale;
break;
}
}
char buf[64];
if (mag > 0.0 && mag < 1.0e-12) {
// Below the ladder, where scaling cannot help and `%.2f` would print `0.00` again. Three
// significant digits in whatever form fits, so the reading survives as a reading.
std::snprintf(buf, sizeof(buf), "%.3g", v);
std::string out = buf;
if (!unit.empty()) out += " " + unit;
return out;
}
std::snprintf(buf, sizeof(buf), "%.2f", v / scale);
std::string num = buf;
if (num.find('.') != std::string::npos) { // at most two decimals, not exactly two
@@ -958,11 +965,9 @@ std::string format_measure(double v, const std::string& unit) {
}
if (num == "-0") num = "0";
std::string out = num;
if (prefix[0] != '\0') out += std::string(" ") + prefix;
if (!unit.empty()) {
if (prefix[0] == '\0') out += " ";
out += unit;
}
if (!unit.empty()) out += " "; // the space separates the number from its unit, nothing else
out += prefix;
out += unit;
return out;
}