Fix: ESQRT/ERSQRT take the operand's magnitude

The EFU square root roots a negative operand as if it were positive; the
interpreter's `p >= 0` guard returned the operand unchanged instead, so
ESQRT and ERSQRT of -1.0 gave -1.0 where the console gives 1.0, and
ESQRT of -0.0 gave -0.0 where the console gives +0.0.

Both recompilers already AND the raw bits with absclip before FSQRT
(mVU_ESQRT, mVU_ERSQRT), so masking the sign off before vuDouble puts the
interpreter on the same order of operations rather than on a new one.

Clears bad_interp on ERSQRT CVF_NEGONE, ESQRT CVF_NEGZERO and
ESQRT CVF_NEGONE; kEfuBadInterp 119 -> 116.

The remaining negative-operand rows (CVF_MIN, CVF_GARBAGE2, ...) still
fail on BOTH engines and are untouched here: they are Inf/NaN inputs that
vuDouble only clamps to fMax under CHECK_VU_OVERFLOW, which ships off.
That is a separate, all-engines-wrong defect.

Idea by pstef.
This commit is contained in:
bmdhacks
2026-08-02 17:03:02 -07:00
committed by Brian Degenhardt
parent bcf55312d4
commit 4a7284ea4a
2 changed files with 16 additions and 16 deletions
+12 -12
View File
@@ -1776,29 +1776,29 @@ static __ri void _vuERCPR(VURegs* VU)
VU->p.F = p;
}
// The EFU square root takes the operand's MAGNITUDE: a negative input is rooted
// as if positive, it is not passed through unchanged. Both recompilers do this
// by ANDing the raw bits with absclip before FSQRT (mVU_ESQRT / mVU_ERSQRT);
// the `p >= 0` guard here returned the operand untouched instead, so ESQRT and
// ERSQRT of -1.0 gave -1.0 where the console gives 1.0. Masking the sign off the
// raw bits before vuDouble is the same order the recompilers use.
static __ri void _vuESQRT(VURegs* VU)
{
float p = vuDouble(VU->VF[_Fs_].UL[_Fsf_]);
float p = vuDouble(VU->VF[_Fs_].UL[_Fsf_] & 0x7FFFFFFF);
if (p >= 0)
{
p = sqrt(p);
}
p = sqrt(p);
VU->p.F = p;
}
static __ri void _vuERSQRT(VURegs* VU)
{
float p = vuDouble(VU->VF[_Fs_].UL[_Fsf_]);
float p = vuDouble(VU->VF[_Fs_].UL[_Fsf_] & 0x7FFFFFFF);
if (p >= 0)
p = sqrt(p);
if (p)
{
p = sqrt(p);
if (p)
{
p = 1.0f / p;
}
p = 1.0f / p;
}
VU->p.F = p;
+4 -4
View File
@@ -151,7 +151,7 @@ inline constexpr EfuCase kEfuCases[] = {
{"ERSQRT", "CVF_MAX_EXP", "ERSQRT CVF_MAX_EXP", true, {0x7F800001u, 0x7F800001u, 0x7F800001u, 0x7F800001u}, 0x1F800000u, false, true},
{"ERSQRT", "CVF_MIN_EXP", "ERSQRT CVF_MIN_EXP", true, {0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u}, 0x7FFFFFFFu, true, true},
{"ERSQRT", "CVF_ONE", "ERSQRT CVF_ONE", true, {0x3F800000u, 0x3F800000u, 0x3F800000u, 0x3F800000u}, 0x3F800000u, false, false},
{"ERSQRT", "CVF_NEGONE", "ERSQRT CVF_NEGONE", true, {0xBF800000u, 0xBF800000u, 0xBF800000u, 0xBF800000u}, 0x3F800000u, true, false},
{"ERSQRT", "CVF_NEGONE", "ERSQRT CVF_NEGONE", true, {0xBF800000u, 0xBF800000u, 0xBF800000u, 0xBF800000u}, 0x3F800000u, false, false},
{"ERSQRT", "CVF_GARBAGE1", "ERSQRT CVF_GARBAGE1", true, {0x00001337u, 0x00001337u, 0x00001337u, 0x00001337u}, 0x7FFFFFFFu, true, true},
{"ERSQRT", "CVF_GARBAGE2", "ERSQRT CVF_GARBAGE2", true, {0xDEADBEEFu, 0xDEADBEEFu, 0xDEADBEEFu, 0xDEADBEEFu}, 0x2FDBBAACu, true, true},
{"ERSQRT", "CVF_INCREASING", "ERSQRT CVF_INCREASING", true, {0x3F800000u, 0x40000000u, 0x40400000u, 0x40800000u}, 0x3F13CD3Au, false, false},
@@ -192,14 +192,14 @@ inline constexpr EfuCase kEfuCases[] = {
{"ESIN", "CVF_PI", "ESIN CVF_PI", true, {0x40490FDBu, 0x40490FDBu, 0x40490FDBu, 0x40490FDBu}, 0x3B565DE0u, true, false},
{"ESIN", "CVF_3PI_OVER2", "ESIN CVF_3PI_OVER2", true, {0x4096CBE4u, 0x4096CBE4u, 0x4096CBE4u, 0x4096CBE4u}, 0xBF1A9934u, true, false},
{"ESQRT", "CVF_ZERO", "ESQRT CVF_ZERO", true, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, 0x00000000u, false, false},
{"ESQRT", "CVF_NEGZERO", "ESQRT CVF_NEGZERO", true, {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, 0x00000000u, true, false},
{"ESQRT", "CVF_NEGZERO", "ESQRT CVF_NEGZERO", true, {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, 0x00000000u, false, false},
{"ESQRT", "CVF_MAX", "ESQRT CVF_MAX", true, {0x7FFFFFFFu, 0x7FFFFFFFu, 0x7FFFFFFFu, 0x7FFFFFFFu}, 0x5FB504F3u, true, true},
{"ESQRT", "CVF_MIN", "ESQRT CVF_MIN", true, {0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu}, 0x5FB504F3u, true, true},
{"ESQRT", "CVF_MAX_MANTISSA", "ESQRT CVF_MAX_MANTISSA", true, {0x3FFFFFFFu, 0x3FFFFFFFu, 0x3FFFFFFFu, 0x3FFFFFFFu}, 0x3FB504F3u, true, true},
{"ESQRT", "CVF_MAX_EXP", "ESQRT CVF_MAX_EXP", true, {0x7F800001u, 0x7F800001u, 0x7F800001u, 0x7F800001u}, 0x5F800000u, true, true},
{"ESQRT", "CVF_MIN_EXP", "ESQRT CVF_MIN_EXP", true, {0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u}, 0x00000000u, false, false},
{"ESQRT", "CVF_ONE", "ESQRT CVF_ONE", true, {0x3F800000u, 0x3F800000u, 0x3F800000u, 0x3F800000u}, 0x3F800000u, false, false},
{"ESQRT", "CVF_NEGONE", "ESQRT CVF_NEGONE", true, {0xBF800000u, 0xBF800000u, 0xBF800000u, 0xBF800000u}, 0x3F800000u, true, false},
{"ESQRT", "CVF_NEGONE", "ESQRT CVF_NEGONE", true, {0xBF800000u, 0xBF800000u, 0xBF800000u, 0xBF800000u}, 0x3F800000u, false, false},
{"ESQRT", "CVF_GARBAGE1", "ESQRT CVF_GARBAGE1", true, {0x00001337u, 0x00001337u, 0x00001337u, 0x00001337u}, 0x00000000u, false, false},
{"ESQRT", "CVF_GARBAGE2", "ESQRT CVF_GARBAGE2", true, {0xDEADBEEFu, 0xDEADBEEFu, 0xDEADBEEFu, 0xDEADBEEFu}, 0x4F152108u, true, true},
{"ESQRT", "CVF_INCREASING", "ESQRT CVF_INCREASING", true, {0x3F800000u, 0x40000000u, 0x40400000u, 0x40800000u}, 0x3FDDB3D7u, false, false},
@@ -226,7 +226,7 @@ inline constexpr EfuCase kEfuCases[] = {
};
inline constexpr int kEfuCaseCount = 208;
inline constexpr int kEfuBadInterp = 119;
inline constexpr int kEfuBadInterp = 116;
inline constexpr int kEfuBadJit = 126;
} // namespace ps2auto_efu