use scientific notation for very large/very small values

This commit is contained in:
Emil Lerch 2026-07-25 11:26:29 -07:00
parent ae2b10a5cc
commit 3262be27ba
Signed by: lobo
GPG key ID: A7B62D657EF764F8
2 changed files with 77 additions and 0 deletions

View file

@ -688,6 +688,9 @@ IEEE 754 float interpretation:
- Significand: another color (e.g. green)
- Display below the bit grid shows:
- Decimal value (exact)
- Value and ULP use scientific notation for very small/large
magnitudes (e.g. a subnormal ULP shows as `1.4e-45`), fixed-point
otherwise, so each row stays on one line
- Formula: (-1)^s * 2^(e-bias) * 1.mantissa
- Classification: normal, denormal, zero, infinity, NaN
- ULP: delta to next representable value

View file

@ -55,6 +55,24 @@ pub fn formatFloat(buf: []u8, value: f64) FormattedValue {
return .{ .display = buf[0..raw_len], .raw = buf[0..raw_len] };
}
/// Format a float for compact single-line display (used by the float view).
/// Uses shortest round-trip fixed-point for normal magnitudes, and scientific
/// notation for very small or very large magnitudes so rows stay readable
/// (e.g. a subnormal ULP prints as "1.4e-45" instead of 45 decimal digits).
/// Non-finite values render as "inf", "-inf", or "nan".
pub fn formatCompactFloat(buf: []u8, value: f64) []const u8 {
if (std.math.isNan(value)) return "nan";
if (std.math.isPositiveInf(value)) return "inf";
if (std.math.isNegativeInf(value)) return "-inf";
const abs = @abs(value);
const use_scientific = abs != 0 and (abs < 1e-4 or abs >= 1e16);
if (use_scientific) {
return std.fmt.bufPrint(buf, "{e}", .{value}) catch return "ERR";
}
return std.fmt.bufPrint(buf, "{d}", .{value}) catch return "ERR";
}
/// Format an integer for programmer mode hex display.
/// Display: "FF FF FF FF" (space per byte), byte order per `endian`.
/// Raw: "0xFFFFFFFF" (no separators, canonical MSB-first value regardless of
@ -569,3 +587,59 @@ test "formatAscii: 0x7F is non-printable" {
const result = formatAscii(&buf, 0x7F, .bits8, .big);
try testing.expectEqualStrings(".", result.raw);
}
fn hasChar(s: []const u8, c: u8) bool {
return std.mem.indexOfScalar(u8, s, c) != null;
}
test "formatCompactFloat: normal magnitudes use fixed-point" {
var buf: [64]u8 = undefined;
try testing.expect(!hasChar(formatCompactFloat(&buf, 1.0), 'e'));
try testing.expect(!hasChar(formatCompactFloat(&buf, 3.14), 'e'));
try testing.expect(!hasChar(formatCompactFloat(&buf, 0.5), 'e'));
try testing.expect(!hasChar(formatCompactFloat(&buf, -2.0), 'e'));
// Just above the small-magnitude threshold stays fixed-point
try testing.expect(!hasChar(formatCompactFloat(&buf, 0.001), 'e'));
}
test "formatCompactFloat: 3.14 renders exactly" {
var buf: [64]u8 = undefined;
try testing.expectEqualStrings("3.14", formatCompactFloat(&buf, 3.14));
}
test "formatCompactFloat: preserves round-trip precision for normal magnitude" {
var buf: [64]u8 = undefined;
// 0.1 stored as f32 then widened: normal magnitude, so fixed-point and full
const v: f64 = @floatCast(@as(f32, 0.1));
const s = formatCompactFloat(&buf, v);
try testing.expect(!hasChar(s, 'e'));
try testing.expect(std.mem.startsWith(u8, s, "0.100000001"));
}
test "formatCompactFloat: very small magnitudes use scientific" {
var buf: [64]u8 = undefined;
// smallest f32 subnormal ~ 1.4e-45
try testing.expect(hasChar(formatCompactFloat(&buf, std.math.ldexp(@as(f64, 1.0), -149)), 'e'));
// f32 ULP of 1.0 = 2^-23 ~ 1.19e-7
try testing.expect(hasChar(formatCompactFloat(&buf, std.math.ldexp(@as(f64, 1.0), -23)), 'e'));
// f64 ULP of 1.0 = 2^-52 ~ 2.2e-16
try testing.expect(hasChar(formatCompactFloat(&buf, std.math.ldexp(@as(f64, 1.0), -52)), 'e'));
}
test "formatCompactFloat: very large magnitudes use scientific" {
var buf: [64]u8 = undefined;
try testing.expect(hasChar(formatCompactFloat(&buf, 1e20), 'e'));
try testing.expect(hasChar(formatCompactFloat(&buf, -1e18), 'e'));
}
test "formatCompactFloat: zero is fixed-point, not scientific" {
var buf: [64]u8 = undefined;
try testing.expectEqualStrings("0", formatCompactFloat(&buf, 0.0));
}
test "formatCompactFloat: non-finite values" {
var buf: [64]u8 = undefined;
try testing.expectEqualStrings("inf", formatCompactFloat(&buf, std.math.inf(f64)));
try testing.expectEqualStrings("-inf", formatCompactFloat(&buf, -std.math.inf(f64)));
try testing.expectEqualStrings("nan", formatCompactFloat(&buf, std.math.nan(f64)));
}