add IEEE 754 support
This commit is contained in:
parent
ebef9c52e4
commit
ae2b10a5cc
6 changed files with 144 additions and 40 deletions
|
|
@ -695,6 +695,11 @@ IEEE 754 float interpretation:
|
|||
- Input: can type a decimal float value (e.g. `3.14`) and it
|
||||
shows the closest representable bit pattern, noting if rounding
|
||||
occurred
|
||||
- Auto-entry: switching into programmer mode (Tab) with a non-integer
|
||||
value in `ans` opens this float view automatically on the exact f64
|
||||
bit pattern, since the integer bit views cannot represent a fraction.
|
||||
An integer `ans` uses the integer views as usual. Infinity/NaN also
|
||||
open the float view (the only place they are representable).
|
||||
- Toggling bits in the grid updates the float interpretation live
|
||||
- Does NOT change bit_width setting - float32 uses 32-bit view,
|
||||
float64 uses 64-bit view (auto-switches width when toggling
|
||||
|
|
|
|||
|
|
@ -280,21 +280,32 @@ Subcommands deferred until Phase 2 engine work is done.
|
|||
- 128-bit width also added (u64 -> u128 throughout engine)
|
||||
- Verify: `'A'` = 0x41, `'ELF'` = 0x454C46, overflow on too-long strings
|
||||
|
||||
### Task 5.3.3: Implement IEEE 754 float interpretation [NOT STARTED]
|
||||
- Add float interpretation engine module (`engine/src/float_interp.zig`)
|
||||
- Given a u32 or u64 bit pattern, decompose into sign, exponent, significand
|
||||
- Compute exact decimal value (including denormals, infinities, NaN)
|
||||
- Compute ULP (delta to next/previous representable value)
|
||||
- Classify: normal, denormal, zero, +inf, -inf, quiet NaN, signaling NaN
|
||||
- Given a decimal float, find the closest IEEE 754 bit pattern and report rounding
|
||||
- TUI sub-view (Ctrl-F toggles float interpretation overlay in programmer mode):
|
||||
- Color-coded bit grid (sign=pink, exponent=cyan, significand=green)
|
||||
- Formula display, classification, ULP, exact value
|
||||
- Typing a float value shows its bit pattern
|
||||
- Auto-switch to 32-bit or 64-bit width based on selected float format
|
||||
- Support float32 and float64 formats
|
||||
- Verify: 0x3F800000 = 1.0 (float32), special values display correctly,
|
||||
bit toggling updates float display, `3.14` shows nearest representation
|
||||
### Task 5.3.3: Implement IEEE 754 float interpretation [DONE]
|
||||
- Engine module `engine/src/float_interp.zig`:
|
||||
- `FloatFormat` (f32/f64) with total/exponent/significand bit counts + bias
|
||||
- `decompose(format, bits)` -> `FloatInfo` (sign, raw+unbiased exponent,
|
||||
significand, class, value, ULP)
|
||||
- Classifies normal, denormal, zero, infinity, quiet NaN, signaling NaN
|
||||
- `reinterpretValue` reinterprets low bits as the float value (exact f32->f64)
|
||||
- `nearestBits(format, value)` finds the closest representable pattern and
|
||||
reports whether rounding occurred (detectable for f32)
|
||||
- ULP computed from the exponent (2^(unbiased - significand_bits); subnormals
|
||||
step by the smallest positive subnormal); NaN for inf/NaN
|
||||
- 21 unit tests: 0x3F800000=1.0, special values, denormals, ULP, decimal
|
||||
rounding (3.14 rounds in f32), overflow to inf, NaN handling, round trips
|
||||
- TUI overlay (`src/tui/float_view.zig`, toggled with Ctrl-F in programmer mode):
|
||||
- Color-coded bit grid: sign=pink, exponent=cyan, significand=green
|
||||
- Shows value, classification, formula ((-1)^s x 2^e x mantissa), ULP, and
|
||||
raw fields (sign, biased/unbiased exponent + bias, significand hex)
|
||||
- Ctrl-W toggles f32/f64 and auto-snaps the bit width (32/64)
|
||||
- Typing a decimal float (e.g. `3.14`) stores its nearest bit pattern and
|
||||
notes rounding; non-float input falls back to the integer expression engine
|
||||
- Auto-entry: Tab into programmer mode with a non-integer (or inf/NaN) `ans`
|
||||
opens the float view on the exact f64 bit pattern; integer `ans` uses the
|
||||
integer views
|
||||
- Bit toggling in the grid updates the interpretation live
|
||||
- Verify: `0x3F800000` shows 1.0 (f32), special values classify correctly,
|
||||
`3.14` shows nearest representation with rounding note
|
||||
|
||||
### Task 5.4: Implement programmer mode TUI view - bit grid [DONE - merged into 5.3]
|
||||
- Bit grid widget: 8 bits per group, groups separated by space
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ pub const parser = @import("parser.zig");
|
|||
pub const evaluator = @import("evaluator.zig");
|
||||
pub const programmer = @import("programmer.zig");
|
||||
pub const formatter = @import("formatter.zig");
|
||||
pub const float_interp = @import("float_interp.zig");
|
||||
|
||||
// Re-export primary types for convenience
|
||||
pub const Value = types.Value;
|
||||
|
|
@ -25,6 +26,11 @@ pub const evalStringInfo = evaluator.evalStringInfo;
|
|||
pub const EvalInfo = evaluator.EvalInfo;
|
||||
pub const evalProgrammerString = programmer.evalProgrammerString;
|
||||
|
||||
// Float interpretation
|
||||
pub const FloatFormat = float_interp.FloatFormat;
|
||||
pub const FloatClass = float_interp.FloatClass;
|
||||
pub const FloatInfo = float_interp.FloatInfo;
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
|
|
|
|||
127
src/tui.zig
127
src/tui.zig
|
|
@ -11,6 +11,7 @@ const vxfw = vaxis.vxfw;
|
|||
const engine = @import("engine");
|
||||
const draw = @import("tui/draw.zig");
|
||||
const programmer_view = @import("tui/programmer.zig");
|
||||
const float_view = @import("tui/float_view.zig");
|
||||
const help_view = @import("tui/help.zig");
|
||||
const C = draw.C;
|
||||
|
||||
|
|
@ -34,6 +35,9 @@ pub const App = struct {
|
|||
bit_cursor: u7,
|
||||
prog_field: ProgField,
|
||||
value_zone_active: bool, // true = cursor in value display, false = in input
|
||||
// Float interpretation overlay (programmer mode)
|
||||
float_view_active: bool,
|
||||
float_format: engine.FloatFormat,
|
||||
|
||||
pub const ProgField = enum {
|
||||
bits,
|
||||
|
|
@ -108,6 +112,8 @@ pub const App = struct {
|
|||
.bit_cursor = 0,
|
||||
.prog_field = .bits,
|
||||
.value_zone_active = false,
|
||||
.float_view_active = false,
|
||||
.float_format = .f32,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -166,13 +172,29 @@ pub const App = struct {
|
|||
self.env.mode = if (self.mode == .standard) .standard else .programmer;
|
||||
if (self.mode == .programmer) {
|
||||
const ans = self.env.ans;
|
||||
if (ans >= 0 and ans == @trunc(ans) and ans < 18446744073709551616.0) {
|
||||
self.prog_value = @intFromFloat(ans);
|
||||
} else if (ans < 0 and ans == @trunc(ans) and ans >= -9223372036854775808.0) {
|
||||
const signed: i128 = @intFromFloat(ans);
|
||||
self.prog_value = @bitCast(signed);
|
||||
if (ans == @trunc(ans) and ans >= -9223372036854775808.0 and ans < 18446744073709551616.0) {
|
||||
// Integer value: show the integer bit views.
|
||||
self.float_view_active = false;
|
||||
if (ans >= 0) {
|
||||
self.prog_value = @intFromFloat(ans);
|
||||
} else {
|
||||
const signed: i128 = @intFromFloat(ans);
|
||||
self.prog_value = @bitCast(signed);
|
||||
}
|
||||
self.prog_value &= self.prog_config.bit_width.mask();
|
||||
} else {
|
||||
// Non-integer (or out-of-range / inf / NaN): the integer
|
||||
// views cannot represent it, so open the float overlay on
|
||||
// the exact f64 bit pattern of the value.
|
||||
self.float_format = .f64;
|
||||
self.float_view_active = true;
|
||||
self.syncFloatWidth();
|
||||
self.prog_value = @as(u64, @bitCast(ans));
|
||||
self.prog_field = .bits;
|
||||
if (self.bit_cursor >= self.prog_config.bit_width.bits()) {
|
||||
self.bit_cursor = @intCast(self.prog_config.bit_width.bits() - 1);
|
||||
}
|
||||
}
|
||||
self.prog_value &= self.prog_config.bit_width.mask();
|
||||
}
|
||||
self.value_zone_active = false;
|
||||
ctx.redraw = true;
|
||||
|
|
@ -186,22 +208,31 @@ pub const App = struct {
|
|||
return;
|
||||
}
|
||||
|
||||
// Ctrl-W: cycle bit width (programmer mode)
|
||||
// Ctrl-W: cycle bit width (programmer mode). In the float overlay it
|
||||
// instead toggles the float format (f32 <-> f64) and snaps the width.
|
||||
if (self.mode == .programmer and key.matches('w', .{ .ctrl = true })) {
|
||||
self.prog_config.bit_width = switch (self.prog_config.bit_width) {
|
||||
.bits8 => .bits16,
|
||||
.bits16 => .bits32,
|
||||
.bits32 => .bits64,
|
||||
.bits64 => .bits128,
|
||||
.bits128 => .bits8,
|
||||
};
|
||||
// NOTE: prog_value is intentionally NOT masked here. Width is a
|
||||
// display lens over the full value, so narrowing then widening
|
||||
// restores the hidden upper bits. The display masks to width and
|
||||
// a warning is shown while the value does not fit (see
|
||||
// drawProgrammerMode). Explicit value edits still commit to width.
|
||||
if (self.bit_cursor >= self.prog_config.bit_width.bits()) {
|
||||
self.bit_cursor = @intCast(self.prog_config.bit_width.bits() - 1);
|
||||
if (self.float_view_active) {
|
||||
self.float_format = switch (self.float_format) {
|
||||
.f32 => .f64,
|
||||
.f64 => .f32,
|
||||
};
|
||||
self.syncFloatWidth();
|
||||
} else {
|
||||
self.prog_config.bit_width = switch (self.prog_config.bit_width) {
|
||||
.bits8 => .bits16,
|
||||
.bits16 => .bits32,
|
||||
.bits32 => .bits64,
|
||||
.bits64 => .bits128,
|
||||
.bits128 => .bits8,
|
||||
};
|
||||
// NOTE: prog_value is intentionally NOT masked here. Width is a
|
||||
// display lens over the full value, so narrowing then widening
|
||||
// restores the hidden upper bits. The display masks to width and
|
||||
// a warning is shown while the value does not fit (see
|
||||
// drawProgrammerMode). Explicit value edits still commit to width.
|
||||
if (self.bit_cursor >= self.prog_config.bit_width.bits()) {
|
||||
self.bit_cursor = @intCast(self.prog_config.bit_width.bits() - 1);
|
||||
}
|
||||
}
|
||||
ctx.redraw = true;
|
||||
return;
|
||||
|
|
@ -217,6 +248,21 @@ pub const App = struct {
|
|||
return;
|
||||
}
|
||||
|
||||
// Ctrl-F: toggle the IEEE 754 float interpretation overlay (programmer mode)
|
||||
if (self.mode == .programmer and key.matches('f', .{ .ctrl = true })) {
|
||||
self.float_view_active = !self.float_view_active;
|
||||
if (self.float_view_active) {
|
||||
self.syncFloatWidth();
|
||||
// Keep the bit grid focused so arrows/space edit bits directly.
|
||||
self.prog_field = .bits;
|
||||
if (self.bit_cursor >= self.prog_config.bit_width.bits()) {
|
||||
self.bit_cursor = @intCast(self.prog_config.bit_width.bits() - 1);
|
||||
}
|
||||
}
|
||||
ctx.redraw = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// VALUE ZONE key handling
|
||||
if (self.value_zone_active) {
|
||||
self.handleValueZoneKey(key);
|
||||
|
|
@ -406,6 +452,17 @@ pub const App = struct {
|
|||
}
|
||||
}
|
||||
|
||||
/// Snap the bit width to match the active float format (f32 -> 32, f64 -> 64).
|
||||
fn syncFloatWidth(self: *App) void {
|
||||
self.prog_config.bit_width = switch (self.float_format) {
|
||||
.f32 => .bits32,
|
||||
.f64 => .bits64,
|
||||
};
|
||||
if (self.bit_cursor >= self.prog_config.bit_width.bits()) {
|
||||
self.bit_cursor = @intCast(self.prog_config.bit_width.bits() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// How many bits the cursor moves per left/right step in the current field.
|
||||
fn fieldBitStep(self: *App) u8 {
|
||||
return switch (self.prog_field) {
|
||||
|
|
@ -438,7 +495,11 @@ pub const App = struct {
|
|||
}
|
||||
|
||||
if (self.mode == .programmer) {
|
||||
try self.submitProgrammer(expr_text);
|
||||
if (self.float_view_active) {
|
||||
try self.submitFloat(expr_text);
|
||||
} else {
|
||||
try self.submitProgrammer(expr_text);
|
||||
}
|
||||
} else {
|
||||
try self.submitStandard(expr_text);
|
||||
}
|
||||
|
|
@ -490,6 +551,22 @@ pub const App = struct {
|
|||
});
|
||||
}
|
||||
|
||||
/// In the float overlay, interpret typed input as a decimal float value and
|
||||
/// store its nearest representable bit pattern. Falls back to the integer
|
||||
/// expression engine for input that is not a plain float literal (e.g.
|
||||
/// "0xFF" or "1 << 3").
|
||||
fn submitFloat(self: *App, expr_text: []const u8) !void {
|
||||
const val = std.fmt.parseFloat(f64, expr_text) catch {
|
||||
try self.submitProgrammer(expr_text);
|
||||
return;
|
||||
};
|
||||
const nearest = engine.float_interp.nearestBits(self.float_format, val);
|
||||
self.prog_value = nearest.bits;
|
||||
const note: []const u8 = if (nearest.rounded) " (rounded to nearest)" else "";
|
||||
const result = try std.fmt.allocPrint(self.allocator, "{d}{s}", .{ val, note });
|
||||
try self.history.append(self.allocator, .{ .expr = expr_text, .result = result, .is_error = false });
|
||||
}
|
||||
|
||||
fn submitProgrammer(self: *App, expr_text: []const u8) !void {
|
||||
const is_error, const display_text = if (engine.evalProgrammerString(self.allocator, expr_text, self.prog_config)) |int| blk: {
|
||||
self.prog_value = int.unsignedValue();
|
||||
|
|
@ -561,7 +638,11 @@ pub const App = struct {
|
|||
draw.writeStr(&surface, 0, tab_col + 10, " Programmer ", prog_style);
|
||||
|
||||
if (self.mode == .programmer) {
|
||||
programmer_view.drawProgrammerMode(self, &surface, width, height);
|
||||
if (self.float_view_active) {
|
||||
float_view.drawFloatView(self, &surface, width, height);
|
||||
} else {
|
||||
programmer_view.drawProgrammerMode(self, &surface, width, height);
|
||||
}
|
||||
} else {
|
||||
self.drawStandardMode(&surface, width, height);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ pub fn drawHelp(surface: *vxfw.Surface, width: u16, height: u16) void {
|
|||
.{ "`", "Toggle input / value zone" },
|
||||
.{ "Ctrl-W", "Cycle bit width (8/16/32/64/128)" },
|
||||
.{ "Ctrl-E", "Toggle endianness (BE / LE)" },
|
||||
.{ "Ctrl-F", "IEEE 754 float view (Ctrl-W: f32/f64)" },
|
||||
.{ "Space", "Toggle bit (in grid)" },
|
||||
.{ "Arrows", "Navigate fields and bits (value zone)" },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -120,9 +120,9 @@ pub fn drawProgrammerMode(app: *tui.App, surface: *vxfw.Surface, width: u16, hei
|
|||
app.drawInput(surface, height -| 2);
|
||||
draw.fillRow(surface, height -| 1, ' ', .{ .fg = C.muted, .bg = C.bg });
|
||||
const status = if (focused == .bits)
|
||||
"Arrows:nav | Space:toggle | Up/Down:field | Ctrl-W:width | Ctrl-E:endian | Tab:mode | Ctrl-C:quit"
|
||||
"Arrows:nav | Space:toggle | Up/Down:field | Ctrl-W:width | Ctrl-E:endian | Ctrl-F:float | Tab:mode"
|
||||
else
|
||||
"Up/Down:field | Enter:set value | Ctrl-W:width | Ctrl-E:endian | Tab:mode | ?:help | Ctrl-C:quit";
|
||||
"Up/Down:field | Enter:set | Ctrl-W:width | Ctrl-E:endian | Ctrl-F:float | Tab:mode | ?:help";
|
||||
draw.writeStr(surface, height -| 1, 1, status, .{ .fg = C.muted, .bg = C.bg });
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue