fix(cpu): PPCBUG-221+227 round_to_i64 + PPCBUG-432 vrfin round-to-even

Phase 5 batch 1 (5a): round-to-int correctness.

PPCBUG-221+227 (coupled): round_to_i64 NearestEven tie-breaking used
`(diff - 0.5).abs() < f64::EPSILON` to detect half-integers, but for
|v| > 2^52 every f64 value is an exact integer (v.trunc() == v), giving
diff == 0. The buggy check fell through to v.round() (round-half-away-
from-zero), giving wrong results for large odd half-integers. Replaced
with a fractional-part-only check that's exact for |v| <= 2^52 and
degenerates to truncation above.

PPCBUG-432: vrfin/vrfin128 used Rust's `f32::round()` which is round-
half-away-from-zero. ISA requires round-to-nearest-even (banker's
rounding). Implemented inline.

PPCBUG-201 (FPSCR.RN for double arithmetic) deferred — requires
MXCSR-set/restore wrappers around 10+ FPU arms; will land in a focused
sub-batch after the remaining 5a-5f fixes.

Tests:
- round_to_i64_nearest_even_on_tie: extended with 0.5, 1.5, -0.5, -1.5.
- round_to_i64_non_tie_cases: 0.4/0.6 (non-tie sanity).
- round_to_i32_nearest_even_on_tie: PPCBUG-227 coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-02 12:13:08 +02:00
parent 5c45108249
commit f6a444b9d1
2 changed files with 54 additions and 9 deletions

View File

@@ -2398,11 +2398,25 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
ctx.pc += 4;
}
PpcOpcode::vrfin | PpcOpcode::vrfin128 => {
// PPCBUG-432: ISA round-to-nearest-even, NOT Rust's round-half-away-from-zero.
let vb = if matches!(instr.opcode, PpcOpcode::vrfin128) { instr.vb128() } else { instr.rb() };
let vd = if matches!(instr.opcode, PpcOpcode::vrfin128) { instr.vd128() } else { instr.rd() };
let b = ctx.vr[vb].as_f32x4();
let mut r = [0f32; 4];
for i in 0..4 { r[i] = b[i].round(); }
for i in 0..4 {
let x = b[i];
let t = x.trunc();
let frac = (x - t).abs();
r[i] = if frac > 0.5 {
t + if x >= 0.0 { 1.0 } else { -1.0 }
} else if frac < 0.5 {
t
} else {
// Tie — round to even.
let ti = t as i64;
if ti & 1 == 0 { t } else { t + if x >= 0.0 { 1.0 } else { -1.0 } }
};
}
ctx.vr[vd] = xenia_types::Vec128::from_f32x4_array(r);
ctx.pc += 4;
}