02-25-2015, 09:23 PM
(This post was last modified: 02-25-2015, 09:27 PM by Blyss Sarania.)

(02-25-2015, 07:37 PM)karasuhebi Wrote: So what happens when a game comes along that requires a certain clamping or round mode to fix an error in a scene and then a certain OTHER clamping or round mode to fix another scene? How is an automatic game fix going to handle that?

Are you saying that there will never be a point where PCSX2 is 100% accurate without looking for these problematic formulas in the game code and making fixes for them by using x86 implementations?

Quote from cotton's explanation:

cottonvibes Wrote:Basically there's something called the "The IEEE Standard for Floating-Point Arithmetic" (or IEEE 754), which defines floating point representation; and most systems abide by those rules (of course not our wonderful PS2).

There's 32-bit "Single Precision Floats" (commonly referred to as 'float'), 64-bit "Double Precision Floats" (commonly referred to as 'double'), and there's even 128-bit floats called Quads (most processors don't support these natively).

The one we're concerned about is the Single Precision Float, which is 32bits and has a format like this

S x 1bit | E x 8bits | M x 23bits

S = Sign Bit (0 = Positive Number; 1 = Negative Number)

E = Exponent (Biased according to some formula)

M = Mantissa (Normalized with a hidden bit)

I'm not going to go into detail on how this stuff works, but wikipedia is quite helpful

Anyways what we need to know is that the IEEE 754 standard defines values with the Exponent bits "11111111", as "NaN" values (short for "Not a Number").

To be more precise, there's also INF (infinity) values, qNaN (quiet NaN), and sNaN (signaling NaN).

Basically NaNs are given when the result of an operation is undefined.

Like if you divide 1 / 0, you get a NaN result, because you can't divide a number by 0.

Or if you take the Square-Root of a negative number, you get a NaN value.

INF values are basically what it sounds like, its an attempt to represent infinity.

The tricky part is that now that you have a NaN or INF value, if you ever try and do a calculation with them, you'll get a NaN or INF value as the result.

Example:

NaN + 500 = NaN (generally the same NaN, since it propagates)

INF - 19999999999 = INF

This is how the IEEE 754 standard defined this stuff (I honestly don't like it, but w/e)

The ps2 however doesn't support NaN's and Infinities!

So basically, the numbers in the range of INF/NaN on a IEEE 754 pc, are just very-large numbers (or very small numbers if negative (if the sign bit is 1)).

For comparison of what your x86-32 CPU does VS the ps2 we have:

Your PC:

1/0 = NaN

sqrt (-4) = NaN

INF - 1000 = INF

The PS2:

1/0 = Greatest Positive Float Number (0x7ffffffff)

sqrt (-4) = sqrt(abs(-4)) = 2

INF - 1000 = Some Large Value - 100

And there's many more combinations, but the point is that the results are not the same between PC and the PS2.

So how do we solve these problems?

Well there's no 'fast' way to emulate the ps2 behavior 100% correct (this would require a software FPU, which will be very-slow).

What we do instead is use a combination of clamping, simulation, and cleverness.

The way clamping works is if a value is INF or NaN, we 'clamp' it into the closest value that's not a INF or NaN.

Basically we force values into 'normal numbers' so that it doesn't mess up operations as much.

This isn't 100% exact, but its generally 'good enough' to fix many problems with games.

Let me give you a quick example of something clamping can solve:

Code:

int main() {

float value = INF;

do {

value = value / 2;

if (value <= 1) { print("Hello World"); break; }

} while (true);

}

If the above code was ran on the ps2, it will eventually print "Hello World", because INF is treated as a regular large number, and will continue to be divided by 2 until its less than 1.

But if done on your x86-32 PC, it will never print "Hello World" but continue to loop forever, because Infinity divided by 2 is still Infinity!

Ever seen games that just hang in pcsx2? Sometimes its caused by situations like above.

Clamping can solve this problem like this:

Code:

int main() {

float value = INF;

do {

value = clamp(value); // If value is NaN/INF, make value into a big normal number

value = value / 2;

if (value <= 1) { print("Hello World"); break; }

} while (true);

}

The clamping will convert the INF into a normal (ordered) number. Then that normal number can be divided by 2 repetitively until its less than 1. Then it will eventually satisfy (value less or equal 1), and print "Hello World".

Sometimes clamping breaks games (because of a lot of reasons that will take too long to explain), but the basic problem is:

The ps2's floats have a larger range of valid-values than your PC's floats. Therefore we have to make values 'as close as possible' on the PC to try and make games happy.

Because its impossible to know if clamping will break or help a game in some situations, we have different clamp modes in pcsx2's advanced options.

Also clamping is SLOW if done a lot, so different clamp modes are faster than others ('None' is the fastest of course Razz)

I also should mention that in earlier examples i said:

sqrt(-4) = NaN; // on your PC

sqrt(-4) = 2; // on the ps2

In this case, we simulate the ps2's sqrt instruction by doing this

Code:

float ps2_sqrt(float value) {

value = clamp(value); // Clamp Value if NaN or Inf to an ordered/normal number

value = abs(value); // Make Positive

value = sqrt(value); // Get sqrt of now-positive value

return value;

}

so:

ps2_sqrt(-4) = 2;

Note the part I bolded:

"Well there's no 'fast' way to emulate the ps2 behavior 100% correct (this would require a software FPU, which will be very-slow)."

Maybe one day machines will be fast enough for that software FPU, until then, this is about as good as it gets I think.

Gaming: Intel i7 3770k @ 4.2Ghz | R9 290 | 16GB RAM | 480GB(240GB+240GB RAID0) SSD | 3 TB HDD | 1 TB HDD | 500GB HDD

Server: AMD FX 6300 @ 4.4Ghz | GTX 670 | 16GB RAM | 240GB SSD | 320GB HDD

PCSX2 General Troubleshooting FAQ