Important Javascript, SteamID64, and You


Master Of All That I Survey
Jul 12, 2014
Los Angeles, California, USA
TL;DR: Javascript cannot handle 64bit integers - SteamID as a number - without losing precision in as many as 3 of the least significant digits. Therefore, when presenting a SteamID to Javascript - Player.SteamID & GameID, Entity.OwnerID & CreatorID, and so on - it will always need to be a string, not an int or a float. I am adding a new property .UID to Player and Entity which will return the Uint64 value directly, without conversion to string. Python handles 64 bit integers differently and has a maxint value equal to Int64.MaxValue, and so a SteamID64 won't lose precision.

You might remember me scratching my head about this: Fougerite MC

I was completely wrong. Not even close. The correct answer is that Javascript uses IEEE-754 double-precision floating point format for all numbers, integers and floats. Even though this format is 64 bits wide, the maximum value for a integer in Javascript is 2^52 - 1, because only the bits for the signficand (mantissa or coefficient in scientific notation) of the IEEE number are used for integers in Javascript. The format can represent a number as large as 1.79 x 10^308, but that's using all the fields, significand, base, exponent. And 1 bit is used for the sign.

When I saw SteamID coming back from a call inside a Javascript plugin with the wrong value, it was because I was accessing the value from a C# property where it was an unsigned long integer. The IEEE format causes the value of the significand of the number to lose precision in the least significant digits instead of overflowing. It might round up or down, 1-3 digits depending on the value. The PlayerLog sample plugin for Jint2 that's included with Fougerite demonstrates this. gid is the Player.GameID (a string) and nuid is PlayerClient.netUser.user.Userid.toString() (a long integer converted to string in Javascript, from which Player.SteamID is set).

When I saw it coming back with the correct value, I was accessing it from a C# property that converted it to string, like Player.SteamID does now. Here's where it gets confusing. If I compared a long integer to a long integer in Javascript, and they were the same value, they would match because Javascript loses precision in the same way for each one. If I compared a long integer converted to string in C# to a long integer with the same value but converted to string in Javascript, it wouldn't match. The C# string conversion preserved all digits of precision. Javascript didn't. If I used parseInt() to convert the C# string to Javascript integer, and compared to a value passed from C# as 64 bit integer, that would match. But if I passed a 64 bit integer value from Javascript to C#, it would lose precision and not match the value of the very same C# property I had just taken it from!

sys.maxint          9223372036854775807
Number.MAX_VALUE       9007199254740992
steamID64.Minimum     76561197960265728
C# Int64.MaxValue   9223372036854775807
C# UInt64.MaxValue 18446744073709551615
  • Like
Reactions: Snake