Upvote Upvoted 31 Downvote Downvoted
1 2
The small ammo pack on villa last
1
#1
0 Frags +

Why does it give me 41 metal in some servers and 40 metal in others?
Has anyone else seen this?

https://medal.tv/clips/55310669/d1337NfMGSDE
(you can skip past young man hitting kill bind)

Why does it give me 41 metal in some servers and 40 metal in others?
Has anyone else seen this?

https://medal.tv/clips/55310669/d1337NfMGSDE
(you can skip past young man hitting kill bind)
2
#2
30 Frags +

thread theme

https://youtu.be/Qz2wnSVeITg

thread theme

[youtube]https://youtu.be/Qz2wnSVeITg[/youtube]
3
#3
40 Frags +

paging shounic

paging shounic
4
#4
47 Frags +

cosmic radiation bit flip

cosmic radiation bit flip
5
#5
14 Frags +

rounded up

rounded up
6
#6
Spaceship Servers
-26 Frags +

what in the fuck

what in the fuck
7
#7
9 Frags +

No wonder people complain about the map. Literally unplayable.

No wonder people complain about the map. Literally unplayable.
8
#8
7 Frags +

So I'm pretty sure internally that small health kits used to (and small ammo kits possibly still do) give 20.5% of max health/ammo respectively rather than 20. With metal, this would be 40 or 41 metal depending on which way the rounding goes. Wondering if there's something going on somewhere that uses the 20.5% code/does the rounding inconsistently and however that's decided happens on loading the map or starting the server? I'm pretty sure small ammo kits always used to give 41 metal? Or am I remembering this wrong.

I doubt this is solely a villa issue but maybe somehow you can specify the percentage for ammo kits and it's set to 20.5% for small ones on villa (don't use hammer so idk about this one). Then when the map is read by the server it could be doing some sort of inconsistent rounding?

So I'm pretty sure internally that small health kits used to (and small ammo kits possibly still do) give 20.5% of max health/ammo respectively rather than 20. With metal, this would be 40 or 41 metal depending on which way the rounding goes. Wondering if there's something going on somewhere that uses the 20.5% code/does the rounding inconsistently and however that's decided happens on loading the map or starting the server? I'm pretty sure small ammo kits always used to give 41 metal? Or am I remembering this wrong.

I doubt this is solely a villa issue but maybe somehow you can specify the percentage for ammo kits and it's set to 20.5% for small ones on villa (don't use hammer so idk about this one). Then when the map is read by the server it could be doing some sort of inconsistent rounding?
9
#9
2 Frags +

https://clips.twitch.tv/CoyApatheticCoyoteArsonNoSexy-ZvcmdrYOgIOlsxyV

clip of it giving 40 on a diff server

https://clips.twitch.tv/CoyApatheticCoyoteArsonNoSexy-ZvcmdrYOgIOlsxyV

clip of it giving 40 on a diff server
10
#10
5 Frags +

Another thing to note is that searching for "41 metal" tf2 ammo pack on google gives several old results from people who just seem to assume that you always get 41 metal and/or quote the 20.5% number so I think I'm right that it used to be that way.

Another thing to note is that searching for [i]"41 metal" tf2 ammo pack[/i] on google gives several old results from people who just seem to assume that you always get 41 metal and/or quote the 20.5% number so I think I'm right that it used to be that way.
11
#11
32 Frags +

I'm sitting here waiting for you to descend into madness as you discover floating point rounding modes.

I'm sitting here waiting for you to descend into madness as you discover floating point rounding modes.
12
#12
7 Frags +
SetsulI'm sitting here waiting for you to descend into madness as you discover floating point rounding modes.

I watched the shounic video and now I see the small ammo pack in my mind stuttering and it hurts might cause anuresm

[quote=Setsul]I'm sitting here waiting for you to descend into madness as you discover floating point rounding modes.[/quote]
I watched the shounic video and now I see the small ammo pack in my mind stuttering and it hurts might cause anuresm
13
#13
10 Frags +

The small ammo pack is actually an eldritch construct designed to break minds.

The small ammo pack is actually an eldritch construct designed to break minds.
14
#14
10 Frags +
ZestySo I'm pretty sure internally that small health kits used to (and small ammo kits possibly still do) give 20.5% of max health/ammo respectively rather than 20. With metal, this would be 40 or 41 metal depending on which way the rounding goes. Wondering if there's something going on somewhere that uses the 20.5% code/does the rounding inconsistently and however that's decided happens on loading the map or starting the server? I'm pretty sure small ammo kits always used to give 41 metal? Or am I remembering this wrong.

I doubt this is solely a villa issue but maybe somehow you can specify the percentage for ammo kits and it's set to 20.5% for small ones on villa (don't use hammer so idk about this one). Then when the map is read by the server it could be doing some sort of inconsistent rounding?

Small packs have always been set to give 20%, not 20.5%, and there is also no way for the map maker to change this. Floating point error, especially dependent on the C implementation of the system is likely the cause here. The full calculation for this is:

ceil(MaxMetal * PackRatio) * (1.0f * mult_metal_pickup)

So, we have:

  1. the floating point multiply operation between MaxMetal and PackRatio
  2. the ceil implementation, which is implemented using hand crafted assembly, of which there are many implementations for (UCRT, glibc, etc)
  3. the floating point operation between the base metal pickup multiplier and the metal pickup multiplier attribute (this is cached, it will only run once per player)
  4. the floating point operation between the metal give count and the metal pickup multiplier

Since you say this happens consistently, but depends on the server, I would suspect it mostly depends on what the server is running on, and the implementation of the C standard library (2) combined with the result of (1). Given that this is a ceil, you wouldn't need a huge error for it to bump up to 41.

But if you can reproduce different results on the same server, just for different sessions (different players, maybe on reconnect, map change or server restart), then it is likely the attribute manager causing it (3), but I find it extremely unlikely it could create an error large enough to see this discrepancy.

[quote=Zesty]So I'm pretty sure internally that small health kits used to (and small ammo kits possibly still do) give 20.5% of max health/ammo respectively rather than 20. With metal, this would be 40 or 41 metal depending on which way the rounding goes. Wondering if there's something going on somewhere that uses the 20.5% code/does the rounding inconsistently and however that's decided happens on loading the map or starting the server? I'm pretty sure small ammo kits always used to give 41 metal? Or am I remembering this wrong.

I doubt this is solely a villa issue but maybe somehow you can specify the percentage for ammo kits and it's set to 20.5% for small ones on villa (don't use hammer so idk about this one). Then when the map is read by the server it could be doing some sort of inconsistent rounding?[/quote]
Small packs have always been set to give 20%, not 20.5%, and there is also no way for the map maker to change this. Floating point error, especially dependent on the C implementation of the system is likely the cause here. The full calculation for this is:

ceil(MaxMetal * PackRatio) * (1.0f * mult_metal_pickup)

So, we have:

[olist]
[*] the floating point multiply operation between MaxMetal and PackRatio
[*] the ceil implementation, which is implemented using hand crafted assembly, of which there are many implementations for (UCRT, glibc, etc)
[*] the floating point operation between the base metal pickup multiplier and the metal pickup multiplier attribute (this is cached, it will only run once per player)
[*] the floating point operation between the metal give count and the metal pickup multiplier
[/olist]

Since you say this happens consistently, but depends on the server, I would suspect it mostly depends on what the server is running on, and the implementation of the C standard library (2) combined with the result of (1). Given that this is a ceil, you wouldn't need a huge error for it to bump up to 41.

But if you can reproduce different results on the same server, just for different sessions (different players, maybe on reconnect, map change or server restart), then it is likely the attribute manager causing it (3), but I find it extremely unlikely it could create an error large enough to see this discrepancy.
15
#15
26 Frags +
ZestyAnother thing to note is that searching for "41 metal" tf2 ammo pack on google gives several old results from people who just seem to assume that you always get 41 metal and/or quote the 20.5% number so I think I'm right that it used to be that way.

To this day I'm still getting 41 metal on a listen server. As it should be. I'm not about to play engie in a pub to test it further.

mastercomsceil(MaxMetal * PackRatio) * (1.0f * mult_metal_pickup)

So, we have:

  1. the floating point multiply operation between MaxMetal and PackRatio
  2. the ceil implementation, which is implemented using hand crafted assembly, of which there are many implementations for (UCRT, glibc, etc)
  3. the floating point operation between the base metal pickup multiplier and the metal pickup multiplier attribute (this is cached, it will only run once per player)
  4. the floating point operation between the metal give count and the metal pickup multiplier


Since you say this happens consistently, but depends on the server, I would suspect it mostly depends on what the server is running on, and the implementation of the C standard library (2) combined with the result of (1). Given that this is a ceil, you wouldn't need a huge error for it to bump up to 41.

Yeah, that's completely wrong.
Floating point arithmetic is not some kind of lottery where you sometimes get more than you expected.
Ceil will not randomly round 40.00000... to 41 because of some mysterious "hand crafted assembly". Either you've got exactly 40 and ceil will give you 40 or you've got 40.000001 or something like that and ceil will give you 41, as it should. If you are running TF2 on x86 (and by god I hope you do) the mysterious hand crafted assembly is simply ROUNDSD if you're allowed to use SSE4.1 but because TF2 is ancient it's most likely FRNDINT and a lot of code to save/restore the state of the FPU and generate a control word because x87 is a piece of shit that just won't die. They are both guaranteed to give exact roundings. If someone bothered to optimize it then maybe you'll get FISTP/FILD because FRNDINT is slow, but that's just the same thing by actually converting to an integer with rounding, then loading it again, instead of rounding the float/double directly, which is somehow slower because x87 is an ancient piece of shit that just won't die.
Anyway, that's neither an error, nor implementation dependent.
Next up, the result of 200 * 0.2 being slightly more than 40 depending on your library implementation would be hugely concerning. Why have you rewritten floating point multiply by hand instead of using the floating point instructions the CPU provides and why is it giving wrong results? On the other hand if you are using FMUL/FMULP/FIMUL (or more modern instructions) why would you get the wrong result? The last time an Intel FPU gave results that were slightly off it was a huge deal and ended in a complete recall. I can guarantee you that that is not the case.

No, you forgot a step:
0. Convert PackRatio (0.2f) to float

And this is where the error is introduced. The floating point math from that point on is completely flawless, there will be no errors regardless of implementation. Because there is absolutely no leeway in the implementation of multiply and ceil in IEEE 754 and actual errors, where you get an actual wrong result, not just one that is "inaccurate in different ways depending on the implementation" aren't allowed.

But the starting point is "wrong", because it is inaccurate.
0.2 can not be represented as binary floating point number. 0.2 is 1/5 and 5 does not share all of its divisors with our base, which is 2, because it is actually coprime to 2 because both 2 and 5 are actual primes. Basically this means that 0.2 in binary floating point is repeating. It's 0.0011001100110011... and so on.
Because float/double are finite this has to be rounded. Either up or down. For float that means the choice is between 0.199999988079071044921875 and 0.20000000298023223876953125. The latter is simply closer to the "true value" 0.2 than the former. Also printf with 8 or more decimal places showing "0.2f" as "0.19999999" tends to really upset people.

So yeah, do a quick

printf("%.30f\n", 0.2f);

and watch your worldview crumble.

And 200 * 0.20000000298023223876953125 = 40.00000059604644775390625 and ceil of that is indeed 41.

If you get 40 then that's because you weren't using ceil. Why that happens is a different rabbit hole.

This could've all been avoided if Valve went with 0.25. That's a perfectly representable 0.01 in binary.

EDIT:
If you want something more poignant:
It's not that computers can't do 200 * 0.2 correctly, it's that they can't do 0.2 correctly.

[quote=Zesty]Another thing to note is that searching for [i]"41 metal" tf2 ammo pack[/i] on google gives several old results from people who just seem to assume that you always get 41 metal and/or quote the 20.5% number so I think I'm right that it used to be that way.[/quote]
To this day I'm still getting 41 metal on a listen server. As it should be. I'm not about to play engie in a pub to test it further.

[quote=mastercoms]
ceil(MaxMetal * PackRatio) * (1.0f * mult_metal_pickup)

So, we have:

[olist]
[*] the floating point multiply operation between MaxMetal and PackRatio
[*] the ceil implementation, which is implemented using hand crafted assembly, of which there are many implementations for (UCRT, glibc, etc)
[*] the floating point operation between the base metal pickup multiplier and the metal pickup multiplier attribute (this is cached, it will only run once per player)
[*] the floating point operation between the metal give count and the metal pickup multiplier
[/olist]

Since you say this happens consistently, but depends on the server, I would suspect it mostly depends on what the server is running on, and the implementation of the C standard library (2) combined with the result of (1). Given that this is a ceil, you wouldn't need a huge error for it to bump up to 41.[/quote]
Yeah, that's completely wrong.
Floating point arithmetic is not some kind of lottery where you sometimes get more than you expected.
Ceil will not randomly round 40.00000... to 41 because of some mysterious "hand crafted assembly". Either you've got exactly 40 and ceil will give you 40 or you've got 40.000001 or something like that and ceil will give you 41, as it should. If you are running TF2 on x86 (and by god I hope you do) the mysterious hand crafted assembly is simply ROUNDSD if you're allowed to use SSE4.1 but because TF2 is ancient it's most likely FRNDINT and a lot of code to save/restore the state of the FPU and generate a control word because x87 is a piece of shit that just won't die. They are both guaranteed to give exact roundings. If someone bothered to optimize it then maybe you'll get FISTP/FILD because FRNDINT is slow, but that's just the same thing by actually converting to an integer with rounding, then loading it again, instead of rounding the float/double directly, which is somehow slower because x87 is an ancient piece of shit that just won't die.
Anyway, that's neither an error, nor implementation dependent.
Next up, the result of 200 * 0.2 being slightly more than 40 depending on your library implementation would be hugely concerning. Why have you rewritten floating point multiply by hand instead of using the floating point instructions the CPU provides and why is it giving wrong results? On the other hand if you are using FMUL/FMULP/FIMUL (or more modern instructions) why would you get the wrong result? The last time an Intel FPU gave results that were slightly off it was a huge deal and ended in a complete recall. I can guarantee you that that is not the case.

No, you forgot a step:
0. Convert PackRatio (0.2f) to float

And this is where the error is introduced. The floating point math from that point on is completely flawless, there will be no errors regardless of implementation. Because there is absolutely no leeway in the implementation of multiply and ceil in IEEE 754 and actual errors, where you get an actual wrong result, not just one that is "inaccurate in different ways depending on the implementation" aren't allowed.

But the starting point is "wrong", because it is inaccurate.
0.2 can not be represented as binary floating point number. 0.2 is 1/5 and 5 does not share all of its divisors with our base, which is 2, because it is actually coprime to 2 because both 2 and 5 are actual primes. Basically this means that 0.2 in binary floating point is repeating. It's 0.0011001100110011... and so on.
Because float/double are finite this has to be rounded. Either up or down. For float that means the choice is between 0.199999988079071044921875 and 0.20000000298023223876953125. The latter is simply closer to the "true value" 0.2 than the former. Also printf with 8 or more decimal places showing "0.2f" as "0.19999999" tends to really upset people.

So yeah, do a quick
[code]printf("%.30f\n", 0.2f);[/code]
and watch your worldview crumble.

And 200 * 0.20000000298023223876953125 = 40.00000059604644775390625 and ceil of that is indeed 41.

If you get 40 then that's because you weren't using ceil. Why that happens is a different rabbit hole.

This could've all been avoided if Valve went with 0.25. That's a perfectly representable 0.01 in binary.

EDIT:
If you want something more poignant:
It's not that computers can't do 200 * 0.2 correctly, it's that they can't do 0.2 correctly.
16
#16
5 Frags +

Setsul, idk why you assumed I thought this is a lottery, thinking that I claimed floating point arithmetic was random. You completely misunderstood me if you think I claimed something so obviously wrong, and I am not sure why you would invest so much time into disproving this false premise rather than understand what I said better.

Now, let's look back at the whole point of this discussion, which in your many paragraphs, you granted only one sentence to: "Why that happens is a different rabbit hole.", where there is some kind of variation that causes some servers to grant 40 and on others grant 41.

If you actually look at the source code for glibc (or UCRT, if you reverse engineered it), you'll see they don't use the instructions you mentioned. This is why in my response, I expected this to be the most likely cause of that difference, and then put a very, very low chance on some other system. These implementations are constant on any one system, but vary based on what implementation is on that system. Far from the "random" you somehow interpreted.

Anyways, to make it more clear: when I laid out every single aspect of the system which computes this value (notice how I listed out operation (4) but did not identify it as a cause, because there was no new data involved in the operation), and talked about error, I was talking about the IEEE 754 representations of those values, but I didn't bother to actually check the representation as you did, so thank you for that. To be even more specific:

"combined with the result of (1). Given that this is a ceil, you wouldn't need a huge error for it to bump up to 41."

I was talking about the representation of 0.2f, which I did not look at exactly, but suspected was the cause.

"it is likely the attribute manager causing it (3), but I find it extremely unlikely it could create an error large enough to see this discrepancy."

I was talking about the representations of the various numbers used in the attribute manager system, which again, I did not bother to look up.

Given that "floating point error" is a standard term, I struggle to see what really tripped you up here and caused you to so confidently write up this response without a single doubt. Looking forward to your reply. Hope this finds you well.

Setsul, idk why you assumed I thought this is a lottery, thinking that I claimed floating point arithmetic was random. You completely misunderstood me if you think I claimed something so obviously wrong, and I am not sure why you would invest so much time into disproving this false premise rather than understand what I said better.

Now, let's look back at the whole point of this discussion, which in your many paragraphs, you granted only one sentence to: "Why that happens is a different rabbit hole.", where there is some kind of variation that causes some servers to grant 40 and on others grant 41.

If you actually look at the source code for glibc (or UCRT, if you reverse engineered it), you'll see they don't use the instructions you mentioned. This is why in my response, I expected this to be the most likely cause of that difference, and then put a very, very low chance on some other system. These implementations are constant on any one system, but vary based on what implementation is on that system. Far from the "random" you somehow interpreted.

Anyways, to make it more clear: when I laid out every single aspect of the system which computes this value (notice how I listed out operation (4) but did not identify it as a cause, because there was no new data involved in the operation), and talked about error, I was talking about the IEEE 754 representations of those values, but I didn't bother to actually check the representation as you did, so thank you for that. To be even more specific:

"combined with the result of (1). Given that this is a ceil, you wouldn't need a huge error for it to bump up to 41."

I was talking about the representation of 0.2f, which I did not look at exactly, but suspected was the cause.

"it is likely the attribute manager causing it (3), but I find it extremely unlikely it could create an error large enough to see this discrepancy."

I was talking about the representations of the various numbers used in the attribute manager system, which again, I did not bother to look up.

Given that "floating point error" is a standard term, I struggle to see what really tripped you up here and caused you to so confidently write up this response without a single doubt. Looking forward to your reply. Hope this finds you well.
17
#17
11 Frags +
mastercomsIf you actually look at the source code for glibc (or UCRT, if you reverse engineered it), you'll see they don't use the instructions you mentioned.

Show me.

My issue here is that this

mastercomsSince you say this happens consistently, but depends on the server, I would suspect it mostly depends on what the server is running on, and the implementation of the C standard library (2) combined with the result of (1). Given that this is a ceil, you wouldn't need a huge error for it to bump up to 41.

is wrong.

There is not a single implementation of the C standard library where ceil behaves differently. I guarantee it. Any ceil that does not return 41 when you put in 40.something is fundamentally broken.

No, this depends entirely on the compiler and optimization settings and you will get exactly the same result regardless of which C stdlib implementation you've used.

You've also identified a multiplication by 1.0 as a source of floating point error. We know mult_metal_pickup has to be 1 or it wouldn't be 40/41 metal. Multiplying by 1 does not affect the floating point error in any way. It never has and never will.

So you have identified two wrong causes (2 and 3 in your list) and one that is somewhat close (1) but still misses the mark with no explanation for why it actually happens other than "this just happens sometimes" and failed to mention the actual cause.
So forgive me for assuming you did not know what you're talking about.

Since you asked nicely I will tell you why the error introduced by converting 0.2 to binary float propagates and is finally amplified enough by ceil to become visible.
There are two main mechanisms controlled by the compiler to get either 40 or 41 permanently and one nasty exception that you have absolutely no control over.
The first is quite simple, you do all your math in float and rounding is entirely controlled by the compiler. The compiler sees a ceil following the multiply and for good or bad the compiler decides that that means the multiply should round up to not invalidate the following ceil. Because 40.00000059604644775390625 does not actually fit in a float. You either get exactly 40 or 40.000003814697265625. Should the compiler not bother to analyse that deeply or otherwise decide to go with a rounding mode that ends up rounding down (the default, round to nearest, would, since the "true" result is closer to 40 than the alternative) then the floating point error becomes 0, it disappears completely. That's pretty much floating point arithmetic in a nutshell, just hope the rounding errors cancel each other out.

The second (and third for that matter) is, most likely, because, bear with me, x87 is an ancient piece of shit that needs to die. x87 does not possess any 32-bit registers. It's 80-bit or bust. Now you might think that a higher precision format can only improve accuracy, but it does not. It makes things so much worse. Because now 40.00000059604644775390625 actually fits as a result, at least partially. Regardless of rounding mode you will now always get 40.something and ceil does with that what it should. This doesn't happen if you put 0.2 in a double and the result is a double, then it behaves like the first one (float to float), but I think even with double to 80-bit the error is large enough to make it through the rounding. So if PackRatio is a double then we know it's this one.
However, should an SSE(2) path exist then servers running that (r_sse2? no idea if that actually does anything) then you get to use actual, real-life float and double registers which should result in 40 since SSE instructions would round down (unless they make the same mistake of putting 0.2 in a float, then multiplying as double). So that's another mechanism for different servers to produce different results. Again, the library implementation doesn't matter.

And the third. The third is nasty. Because x87 is you-know-what the rounding mode is controlled by a special register. And sometimes, some people butt in via interrupt or other means and for one reason or another do not restore the control word like they should, nor is it done for them. And then you end up with the rounding mode randomly changing with no way to prevent it and no way to know it happened other than that your results keep changing on each run. This is not fun. At all.

So yeah, none of this is on the operations. All the errors happen in rounding (and not with ceil, ceil is the only innocent one, it only reveals the sins of others) and how you round is all that matters. Both the rounding mode and to what precision, which depends on the format of your intermediate result(s).

EDIT:

mastercomsAnyways, to make it more clear: when I laid out every single aspect of the system which computes this value (notice how I listed out operation (4) but did not identify it as a cause, because there was no new data involved in the operation)

This is also dead wrong. Just because data has already been rounded once does not make it immune to further rounding errors. Multiplying two 23-bit mantissas (plus their implicit leading 1) leads to a 48-bit result (minus the implicit 1). Unsurprisingly this does not fit into the new 23-bit mantissa. If any of the lower 24 bits are set there will be a rounding error.
In this case there can't be any further error introduced because the mantissas contain 23 and 21/18 trailing zeroes respectively. But this is completely unrelated to how "old" the data is. If there was no ceil forcing a lot of trailing zeroes and if the other was something more interesting than 1.0, like for example 0.8f, then you absolutely would get another rounding error, despite "no new data being involved".

[quote=mastercoms]If you actually look at the source code for glibc (or UCRT, if you reverse engineered it), you'll see they don't use the instructions you mentioned. [/quote]
Show me.

My issue here is that this
[quote=mastercoms]
Since you say this happens consistently, but depends on the server, I would suspect it mostly depends on what the server is running on, and the implementation of the C standard library (2) combined with the result of (1). Given that this is a ceil, you wouldn't need a huge error for it to bump up to 41.[/quote]
is wrong.

There is not a single implementation of the C standard library where ceil behaves differently. I guarantee it. Any ceil that does not return 41 when you put in 40.something is fundamentally broken.


No, this depends entirely on the compiler and optimization settings and you will get exactly the same result regardless of which C stdlib implementation you've used.

You've also identified a multiplication by 1.0 as a source of floating point error. We know mult_metal_pickup has to be 1 or it wouldn't be 40/41 metal. Multiplying by 1 does not affect the floating point error in any way. It never has and never will.

So you have identified two wrong causes (2 and 3 in your list) and one that is somewhat close (1) but still misses the mark with no explanation for why it actually happens other than "this just happens sometimes" and failed to mention the actual cause.
So forgive me for assuming you did not know what you're talking about.

Since you asked nicely I will tell you why the error introduced by converting 0.2 to binary float propagates and is finally amplified enough by ceil to become visible.
There are two main mechanisms controlled by the compiler to get either 40 or 41 permanently and one nasty exception that you have absolutely no control over.
The first is quite simple, you do all your math in float and rounding is entirely controlled by the compiler. The compiler sees a ceil following the multiply and for good or bad the compiler decides that that means the multiply should round up to not invalidate the following ceil. Because 40.00000059604644775390625 does not actually fit in a float. You either get exactly 40 or 40.000003814697265625. Should the compiler not bother to analyse that deeply or otherwise decide to go with a rounding mode that ends up rounding down (the default, round to nearest, would, since the "true" result is closer to 40 than the alternative) then the floating point error becomes 0, it disappears completely. That's pretty much floating point arithmetic in a nutshell, just hope the rounding errors cancel each other out.

The second (and third for that matter) is, most likely, because, bear with me, x87 is an ancient piece of shit that needs to die. x87 does not possess any 32-bit registers. It's 80-bit or bust. Now you might think that a higher precision format can only improve accuracy, but it does not. It makes things so much worse. Because now 40.00000059604644775390625 actually fits as a result, at least partially. Regardless of rounding mode you will now always get 40.something and ceil does with that what it should. This doesn't happen if you put 0.2 in a double and the result is a double, then it behaves like the first one (float to float), but I think even with double to 80-bit the error is large enough to make it through the rounding. So if PackRatio is a double then we know it's this one.
However, should an SSE(2) path exist then servers running that (r_sse2? no idea if that actually does anything) then you get to use actual, real-life float and double registers which should result in 40 since SSE instructions would round down (unless they make the same mistake of putting 0.2 in a float, then multiplying as double). So that's another mechanism for different servers to produce different results. Again, the library implementation doesn't matter.

And the third. The third is nasty. Because x87 is you-know-what the rounding mode is controlled by a special register. And sometimes, some people butt in via interrupt or other means and for one reason or another do not restore the control word like they should, nor is it done for them. And then you end up with the rounding mode randomly changing with no way to prevent it and no way to know it happened other than that your results keep changing on each run. This is not fun. At all.

So yeah, none of this is on the operations. All the errors happen in rounding (and not with ceil, ceil is the only innocent one, it only reveals the sins of others) and how you round is all that matters. Both the rounding mode and to what precision, which depends on the format of your intermediate result(s).

EDIT:
[quote=mastercoms]
Anyways, to make it more clear: when I laid out every single aspect of the system which computes this value (notice how I listed out operation (4) but did not identify it as a cause, because there was no new data involved in the operation)[/quote]
This is also dead wrong. Just because data has already been rounded once does not make it immune to further rounding errors. Multiplying two 23-bit mantissas (plus their implicit leading 1) leads to a 48-bit result (minus the implicit 1). Unsurprisingly this does not fit into the new 23-bit mantissa. If any of the lower 24 bits are set there will be a rounding error.
In this case there can't be any further error introduced because the mantissas contain 23 and 21/18 trailing zeroes respectively. But this is completely unrelated to how "old" the data is. If there was no ceil forcing a lot of trailing zeroes and if the other was something more interesting than 1.0, like for example 0.8f, then you absolutely would get another rounding error, despite "no new data being involved".
18
#18
10 Frags +
SetsulTo this day I'm still getting 41 metal on a listen server. As it should be. I'm not about to play engie in a pub to test it further.

This is really interesting because I'm getting 40 on a listen server as well as my own server.

[quote=Setsul]
To this day I'm still getting 41 metal on a listen server. As it should be. I'm not about to play engie in a pub to test it further.[/quote]

This is really interesting because I'm getting 40 on a listen server as well as my own server.
19
#19
0 Frags +

._.

._.
20
#20
Spaceship Servers
31 Frags +

https://media.discordapp.net/attachments/765385446212501525/855169222590464040/alc7m1vnap571.png

[img]https://media.discordapp.net/attachments/765385446212501525/855169222590464040/alc7m1vnap571.png[/img]
21
#21
27 Frags +

Honestly this thread peaked when this guy got it right without having a clue

zx37rounded up
Honestly this thread peaked when this guy got it right without having a clue
[quote=zx37]rounded up[/quote]
22
#22
34 Frags +

tried to tell u guys

tried to tell u guys
23
#23
25 Frags +

wait hold on did setsul and mastercoms just have a giant nerd essay dick measuring contest and still not answer how a specific map could cause different rounding errors

this is definitely a thread for the ages though, i think it's fair to say ggglygy's famous dissertation on damage numbers has now been outdone by setsul's ammo pack numbers dissertation

wait hold on did setsul and mastercoms just have a giant nerd essay dick measuring contest and still not answer how a specific map could cause different rounding errors

this is definitely a thread for the ages though, i think it's fair to say ggglygy's famous dissertation on damage numbers has now been outdone by setsul's ammo pack numbers dissertation
24
#24
15 Frags +

What the fuck is this thread? Is this what an engie thread looks like?

What the fuck is this thread? Is this what an engie thread looks like?
25
#25
2 Frags +
bearodactylwait hold on did setsul and mastercoms just have a giant nerd essay dick measuring contest and still not answer how a specific map could cause different rounding errors

this is definitely a thread for the ages though, i think it's fair to say ggglygy's famous dissertation on damage numbers has now been outdone by setsul's ammo pack numbers dissertation

https://www.youtube.com/watch?v=2WuR5GCqcgo

Anyone wanna do this thread? I can edit.

[quote=bearodactyl]wait hold on did setsul and mastercoms just have a giant nerd essay dick measuring contest and still not answer how a specific map could cause different rounding errors

this is definitely a thread for the ages though, i think it's fair to say ggglygy's famous dissertation on damage numbers has now been outdone by setsul's ammo pack numbers dissertation[/quote]
[youtube]
https://www.youtube.com/watch?v=2WuR5GCqcgo
[/youtube]

Anyone wanna do this thread? I can edit.
26
#26
9 Frags +

Can't believe aimisadick missed out on the action

Can't believe aimisadick missed out on the action
27
#27
4 Frags +
bearodactylwait hold on did setsul and mastercoms just have a giant nerd essay dick measuring contest and still not answer how a specific map could cause different rounding errors

this is definitely a thread for the ages though, i think it's fair to say ggglygy's famous dissertation on damage numbers has now been outdone by setsul's ammo pack numbers dissertation

Neither of us thinks it's a specific map, even if we disagree about the mechanism because mastercoms doesn't quite get how fucked up floating point arithmetic is.

It's more likely to depend on the server. See Zesty getting 40 and me getting 41 on a local/listen server.

I haven't checked whether it's 20% of 20.5% though. If it is 20.5% then you should be always getting 41 on some servers and might get 41 or 40 depending on whether the number you started with was odd or even on others.

If you want my theory on why it was "discovered" on villa it's that engie positions are usually next to medium or larger ammo packs.

[quote=bearodactyl]wait hold on did setsul and mastercoms just have a giant nerd essay dick measuring contest and still not answer how a specific map could cause different rounding errors

this is definitely a thread for the ages though, i think it's fair to say ggglygy's famous dissertation on damage numbers has now been outdone by setsul's ammo pack numbers dissertation[/quote]
Neither of us thinks it's a specific map, even if we disagree about the mechanism because mastercoms doesn't quite get how fucked up floating point arithmetic is.

It's more likely to depend on the server. See Zesty getting 40 and me getting 41 on a local/listen server.

I haven't checked whether it's 20% of 20.5% though. If it is 20.5% then you should be always getting 41 on some servers and might get 41 or 40 depending on whether the number you started with was odd or even on others.

If you want my theory on why it was "discovered" on villa it's that engie positions are usually next to medium or larger ammo packs.
28
#28
5 Frags +

So I decided to look at the glibc source code like mastercoms told me to. I mean either I prove I'm right or I get to see some super secret x86 assembly implementation of ceil that is faster than a 1 cycle ROUNDSD.

mastercomsIf you actually look at the source code for glibc (or UCRT, if you reverse engineered it), you'll see they don't use the instructions you mentioned. This is why in my response, I expected this to be the most likely cause of that difference, and then put a very, very low chance on some other system.

And look what I found:

ENTRY(__ceil_sse41)
	roundsd	$10, %xmm0, %xmm0
	ret
END(__ceil_sse41)

For SSE4.1 (same with ROUNDSS for float) and

ENTRY(__ceill)
	fldt	4(%esp)
	subl	$32,%esp
	cfi_adjust_cfa_offset (32)

	fnstenv	4(%esp)			/* store fpu environment */

	/* We use here %edx although only the low 1 bits are defined.
	   But none of the operations should care and they are faster
	   than the 16 bit operations.  */
	movl	$0x0800,%edx		/* round towards +oo */
	orl	4(%esp),%edx
	andl	$0xfbff,%edx
	movl	%edx,(%esp)
	fldcw	(%esp)			/* load modified control word */

	frndint				/* round */

	/* Preserve "invalid" exceptions from sNaN input.  */
	fnstsw
	andl	$0x1, %eax
	orl	%eax, 8(%esp)

	fldenv	4(%esp)			/* restore original environment */

	addl	$32,%esp
	cfi_adjust_cfa_offset (-32)
	ret
END (__ceill)

in various variations for x87. x86/x64, float/double, all pretty much the same, all FRNDINT, slightly different x87 bullshit surrounding it.
I think now everyone can understand why I ... dislike x87.

Who could've seen it coming that glibc would use rounding instructions to round? Wait, I did!

SetsulIf you are running TF2 on x86 (and by god I hope you do) the mysterious hand crafted assembly is simply ROUNDSD if you're allowed to use SSE4.1 but because TF2 is ancient it's most likely FRNDINT and a lot of code to save/restore the state of the FPU and generate a control word because x87 is a piece of shit that just won't die.

They used exactly what I said they would! What a surprise! It's almost as if I knew what I was talking about.

Of course, absence of proof is not proof of absence. Maybe this is all a diversion and someone just wrote that code to distract everyone from the super secret hand crafted assembly implementation that doesn't use those instructions, but I haven't found it so far. Maybe Microsoft reinvented the wheel for UCRT. But until mastercoms can show me those mysterious assembly implementations of ceil that give different results, I'll continue assuming that they don't exist.

So I decided to look at the glibc source code like mastercoms told me to. I mean either I prove I'm right or I get to see some super secret x86 assembly implementation of ceil that is faster than a 1 cycle ROUNDSD.
[quote=mastercoms]
If you actually look at the source code for glibc (or UCRT, if you reverse engineered it), you'll see they don't use the instructions you mentioned. This is why in my response, I expected this to be the most likely cause of that difference, and then put a very, very low chance on some other system.[/quote]

And look what I found:
[code]ENTRY(__ceil_sse41)
roundsd $10, %xmm0, %xmm0
ret
END(__ceil_sse41)[/code]
For SSE4.1 (same with ROUNDSS for float) and
[code]ENTRY(__ceill)
fldt 4(%esp)
subl $32,%esp
cfi_adjust_cfa_offset (32)

fnstenv 4(%esp) /* store fpu environment */

/* We use here %edx although only the low 1 bits are defined.
But none of the operations should care and they are faster
than the 16 bit operations. */
movl $0x0800,%edx /* round towards +oo */
orl 4(%esp),%edx
andl $0xfbff,%edx
movl %edx,(%esp)
fldcw (%esp) /* load modified control word */

frndint /* round */

/* Preserve "invalid" exceptions from sNaN input. */
fnstsw
andl $0x1, %eax
orl %eax, 8(%esp)

fldenv 4(%esp) /* restore original environment */

addl $32,%esp
cfi_adjust_cfa_offset (-32)
ret
END (__ceill)[/code]
in various variations for x87. x86/x64, float/double, all pretty much the same, all FRNDINT, slightly different x87 bullshit surrounding it.
I think now everyone can understand why I ... dislike x87.

Who could've seen it coming that glibc would use rounding instructions to round? Wait, I did![quote=Setsul]If you are running TF2 on x86 (and by god I hope you do) the mysterious hand crafted assembly is simply ROUNDSD if you're allowed to use SSE4.1 but because TF2 is ancient it's most likely FRNDINT and a lot of code to save/restore the state of the FPU and generate a control word because x87 is a piece of shit that just won't die.[/quote]They used exactly what I said they would! What a surprise! It's almost as if I knew what I was talking about.

Of course, absence of proof is not proof of absence. Maybe this is all a diversion and someone just wrote that code to distract everyone from the super secret hand crafted assembly implementation that doesn't use those instructions, but I haven't found it so far. Maybe Microsoft reinvented the wheel for UCRT. But until mastercoms can show me those mysterious assembly implementations of ceil that give different results, I'll continue assuming that they don't exist.
29
#29
28 Frags +

bro go touch some grass its a fucking ammopack

bro go touch some grass its a fucking ammopack
30
#30
9 Frags +

Hey setsul, I'm a bit confused on a few points and am wondering if you can clarify:

For possible cause 1, if it's determined at compile-time then shouldn't it be consistent?

For possible cause 2, it's true that storing 0.2 into an 80-bit floating point can cause this behavior, but why is it inconsistent depending on the system? I would've assumed that which instructions to use would be determined at compile-time, is this not the case? As well, you mention support for SSE2, but shouldn't essentially every processor used today support SSE2 (considering it was introduced by Intel in 2000 and became compatible with AMD in 2003)?

For possible cause 3, doesn't the snippet you provided for __ceill set the rounding mode and such, so this shouldn't be a concern?

I hope this doesn't come off as standoffish because I think we'd be better focused on figuring it out and hammering out the details than arguing about it, but I'm not fully following some of your points and they appear to me (in my admittedly poor understanding of the intricacies of floating point instruction sets) like they don't explain the observed behavior.

Hey setsul, I'm a bit confused on a few points and am wondering if you can clarify:

For possible cause 1, if it's determined at compile-time then shouldn't it be consistent?

For possible cause 2, it's [url=https://www.ideone.com/Efnddz]true[/url] that storing 0.2 into an 80-bit floating point can cause this behavior, but why is it inconsistent depending on the system? I would've assumed that which instructions to use would be determined at compile-time, is this not the case? As well, you mention support for SSE2, but shouldn't essentially every processor used today support SSE2 (considering it was introduced by Intel in 2000 and became compatible with AMD in 2003)?

For possible cause 3, doesn't the snippet you provided for __ceill set the rounding mode and such, so this shouldn't be a concern?

I hope this doesn't come off as standoffish because I think we'd be better focused on figuring it out and hammering out the details than arguing about it, but I'm not fully following some of your points and they appear to me (in my admittedly poor understanding of the intricacies of floating point instruction sets) like they don't explain the observed behavior.
1 2
Please sign in through STEAM to post a comment.