Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion ps2xRuntime/include/ps2_runtime_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,17 @@ inline float ps2_frsqrt(float a, float b)
return ps2_fall_normal(a, b, r) ? r : ps2_frsqrt_slow(a, b);
}

// CVT.W.S: the EE always truncates toward zero, whatever FCR31 says. A magnitude of 2^31 or more
// saturates by sign to 0x7FFFFFFF / 0x80000000. The EE has no Inf/NaN, so exponent-255 patterns are
// just large numbers and saturate the same way (as PCSX2's interpreter CVT_W does).
inline int32_t ps2_fcvt_w(float a)
{
const uint32_t bits = std::bit_cast<uint32_t>(a);
if ((bits & PS2_FLOAT_EXP_MASK) <= 0x4E800000u) // exponent <= 157: |a| < 2^31
return static_cast<int32_t>(a);
return (bits & PS2_FLOAT_SIGN_MASK) ? INT32_MIN : INT32_MAX;
}

enum class Ps2VecOp
{
Add,
Expand Down Expand Up @@ -853,7 +864,7 @@ inline __m128i ps2_u64_to_epi64_pair(uint64_t value)
#define FPU_FLOOR_W_S(a) ((int32_t)floorf((float)(a)))
#define FPU_CVT_S_W(a) ((float)(int32_t)(a))
#define FPU_CVT_S_L(a) ((float)(int64_t)(a))
#define FPU_CVT_W_S(a) ((int32_t)nearbyintf((float)(a)))
#define FPU_CVT_W_S(a) ps2_fcvt_w((float)(a))
#define FPU_CVT_L_S(a) ((int64_t)(float)(a))
#define FPU_C_F_S(a, b) (0)
#define FPU_C_UN_S(a, b) (isnan((float)(a)) || isnan((float)(b)))
Expand Down
19 changes: 19 additions & 0 deletions ps2xTest/src/ps2_float_semantics_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,5 +224,24 @@ void register_ps2_float_semantics_tests()
t.IsTrue(rsqrt.find("ctx->f[2] = FPU_RSQRT_S(ctx->f[3], ctx->f[4])") != std::string::npos,
"RSQRT.S should compute fs / sqrt(ft) but got: " + rsqrt);
});

tc.Run("FPU CVT.W.S truncates toward zero and saturates by sign", [](TestCase &t)
{
const auto cvt = [](float f) { return static_cast<uint32_t>(FPU_CVT_W_S(f)); };
// 2.5 and 3.7 differ between round-to-nearest (2/4) and truncation (2/3).
t.Equals(cvt(3.7f), 3u, "3.7 should truncate to 3");
t.Equals(cvt(-3.7f), static_cast<uint32_t>(-3), "-3.7 should truncate toward zero to -3");
t.Equals(cvt(0.999f), 0u, "0.999 should truncate to 0");
// A volume scale like 0.9 * 0x3FFF = 14744.7 truncates to 14744, not 14745.
t.Equals(cvt(FPU_MUL_S(0.9f, 16383.0f)), 14744u, "0.9 * 0x3FFF should truncate to 14744");
t.Equals(cvt(floatOf(0x4EFFFFFFu)), 0x7FFFFF80u, "the largest float below 2^31 should convert exactly");
t.Equals(cvt(2147483648.0f), 0x7FFFFFFFu, "2^31 should saturate to 0x7FFFFFFF");
t.Equals(cvt(-2147483648.0f), 0x80000000u, "-2^31 should give 0x80000000");
t.Equals(cvt(1.0e20f), 0x7FFFFFFFu, "1e20 should saturate to 0x7FFFFFFF");
t.Equals(cvt(-1.0e20f), 0x80000000u, "-1e20 should saturate to 0x80000000");
t.Equals(cvt(floatOf(0x7FFFFFFFu)), 0x7FFFFFFFu, "+max (a NaN pattern on the host) should saturate to 0x7FFFFFFF");
t.Equals(cvt(floatOf(0xFFC00000u)), 0x80000000u, "a negative exponent-255 pattern should saturate to 0x80000000");
t.Equals(cvt(floatOf(0x00000001u)), 0u, "a denormal should convert to 0");
});
});
}