1
\$\begingroup\$

I'm working on a browser MMORPG in PHP and MySQL and today got a bug report about int32 overflow, due to the player having too much money. When I was designing the core system, I completely forgot about the integer 2,147,483,647 limit, so now I'm trying to come up with some way to fix this without breaking everything apart.

How to better handle in-game currency (or basically any number that's going to increase indefinitely)? I have couple of ideas, but would like to ask for help choosing as this is the first time I'm dealing with this kind of stuff (int overflow and large numbers.)

  1. Fix the current mechanics so that players won't ever have that much money. Doesn't really address the integer problem, but it still needs to be done.

  2. Use float instead of int. This seems like an easy option, but I'm not sure if there would be any potential side-effects. Also, I read somewhere that you shouldn't compare floats, so I'm not sure how I would handle basic hasEnoughMoney? type of conditions. Also, is float the same in PHP and MySQL?

  3. Have some sort of higher currency, like bronze/silver/gold coins and automatically convert it at certain threshold (ie. 1,000,000 would become 1M or something like that.) The downside here is more apparent than with the floats - more complexity and more room for mistakes to happen.

  4. Straight out cap the amount of money player can have few points below the overflow limit. Straight forward and effective, but I don't really like the idea of having a limit on money - though I just realized, GTA (and many more I'm sure) has it if I'm not mistaken, so maybe it could work?

Any help would be much appreciated. I searched for hours, but couldn't find much. I read this How do idle games handle such large numbers? among other things, but it doesn't really help as what I'm doing is multiplayer, so not every player will have either too much (and not need precision, so to use floats) or too little (and need precision), and it needs to be unified for all players.

Thanks!

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Implement a taxation system that effectively taxes 100% at the highest end, thereby limiting overflow. \$\endgroup\$
    – Steven
    Commented May 22, 2020 at 5:25

6 Answers 6

4
\$\begingroup\$

I read somewhere that you shouldn't compare floats

Beware programming by hearsay. This is a good way to make mistakes for reasons you don't understand. Instead, ask for clarification.

The full advice here is "be careful when comparing floats for exact equality." Code like if (playerCoinBalance == 0) is safe enough with integers, but with floats the player's coin balance could conceivably be 1.401298E-45, which is a perfectly valid float that is exceptionally close to zero, but is not actually zero, so the == comparison will return false, even though the player can't buy anything with one quattuordecillionth of a coin, and if we printed the balance in their inventory it would likely round to "0".

But when you're comparing ranges, you're reasonably safe. if (playerCoinBalance >= cost) will pass for any balance exactly at, slightly over, or way over the cost, just as desired. So, the trick for float comparisons is to think about ranges of tolerance, rather than exact equality.

That said, floats are not a good choice for solving the problem of players getting too much money. For two reasons:

  • They still have a maximum value, so you've just moved the problem, you haven't solved it. If you go past 3.40282347E+38 (a little shy of a duodecillion), you spill out to infinity, after which the player has unlimited money to spend.

  • They lose precision as the number gets larger. Once you exceed about 33 million, you no longer have integer precision with a 32-bit float. So while you don't get an overflow error right away, you can get weird rounding artifacts: like I give you 5 coins, but your balance goes up by only 4 coins, because it got rounded to the closest representable value. Or you spend a coin without changing your balance, because currentBalance - 1 isn't a representable number.

As you point out, many games successfully solve this problem by enforcing a maximum cap on your balance - often something like 99, 999, 9999, or 99 999. (These numbers tend to make more intuitive sense to your players than 2 147 483 646, and also neatly cap how many digits / how much text field width you need to display the balance in base 10 in your UI, while making maximum use of that available real estate).

A benefit you get from this is that it discourages stockpiling money, and encourages spending. The player risks losing value if they continue earning while close to their cap, so it's in their best interest to spend the money before it gets there. Just make sure the cap is clearly advertised to the player, so it doesn't bite them by surprise.

You can even make a game mechanic out of the cap, as Legend of Zelda games do by letting you earn bigger and bigger wallets with higher caps as part of your character progression. This helps ensure the player is aware of the cap and not surprised by it, gives them opportunities to set and achieve goals with a major impact on their purchasing power, and lets you gate certain purchases behind these progression milestones by setting their cost just over one of the lower caps.

We can't tell you what the right solution is for your game, but I don't see any particular reason not to enforce a cap like this, in tandem with the game mechanics changes you have in mind for point 1, to reduce the likelihood that players stockpile that much in the first place.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ It looks I deleted that part of the question in final edit, but I mentioned the float thing because I was hoping someone would clarify it, so thanks :) I almost always compare ranges with numbers, but the other issues probably aren't worth it (UI, precision after 30M). You convinced me to the limit path, as it doesn't require changing datatypes and I really like the idea of implementing it into the game (already got few ideas on how to implement it into the game world as well.) Thank you! I still have to read other answers, but I'm leaning towards accepting this one as that's the one I'll use. \$\endgroup\$
    – matronator
    Commented May 22, 2020 at 9:58
1
\$\begingroup\$

You can straight up cap your players money. However, if this is a game where high numbers are going to be common, you are better of using a big integer solution, such as Philipp suggests.

Automatically converting to another currency is doing a poor man's big integer. However, there might be value in setting a cap, but letting player manually convert to a higher currency.


As per floats… It depends on the game if that is a good idea.

Once you go over the maximum safe integer that can be represented, you may start having items that are essentially free. That is, when you subtract the price from the float, you get the same float.

For what I read in php.net the float size is platform depended, and thus the maximum safe integer is also platform depended.

Are you OK with that? Many idle game designers are OK with that, but that does not sound like a good approach for a game with a player economy, it is essentially infinite money (if exploited appropriately).


As per the design of the economy, you probably want good reasons to use large amounts of money.

MMORPGs economies are often designed with built-in sinks. Such as buying from in-game shops, paying for crafting/repairing items, healing pets, and taxes to player transactions. These are meant to introduce a counter balance to the influx of money from drops (which are often endless).

I would like to encourage to study the economy of the game as a whole. I do not have the context to suggest a solution for players hoarding money without having a look at all the ways people gain and use money.

If you wish to not cause mechanic unbalance when tweaking the economy, one option is to offer expensive cosmetics (yes, paid with non-premium currency). And be wary of sticker shock. Avoid big jumps between the prices of items. Try to have a couple options to buy at any price range.

\$\endgroup\$
1
\$\begingroup\$

Instead of int32, use uint32, it doesn't hold negative numbers thus max value will be 4,294,967,295. (Not for PHP. Currently PHP supports int32 or int64 depending on the type of machine its running on)

Also, just make your money harder to gain! for example instead of giving N amount of money for a battle, give them N/100. Thats it. Now it will be 100 times harder to get this amount.

Next. Instead of saving players' money as PlayerMoney = 4294967295, do something like PlayerMoney = array(4294967295, 4294967295, 4294967295, 150);

If you'll want to display money as 1 value in UI, you will write a simple script that just adds the numbers in "column" as you add them on paper(You know, starting from last digit, and moving each time to the left). This will allow you to add very big numbers together.

Or you could display it in a list as a "PlayerBank", and each bank account or wallet has maximum of again this 4294967295.

But I think just making money 100 times harder to gain will be enough.

Oh and last. Why not use uint64? Its max value will be 18,446,744,073,709,551,615 (Again, not for PHP)

\$\endgroup\$
4
  • \$\begingroup\$ Yeah, there's a one feature which is currently kinda broken (game is still in very early development), that works kinda like a stock market but with much higher margin, so after a while the profits rise exponentially because there's no limit on the quantity. I really like the array idea, though not for the core player money as that would require too much refactoring. But as something like a bank or something could actually work pretty well. Thank you! \$\endgroup\$
    – matronator
    Commented May 22, 2020 at 10:08
  • \$\begingroup\$ Please note that this question is about PHP. The PHP programming language does not have int32, uint32 or unit64 types. It only has the integer type, and its length depends on the platform. For more information on integers in PHP, check out the documentation \$\endgroup\$
    – Philipp
    Commented May 22, 2020 at 11:22
  • \$\begingroup\$ @Philipp Right, but as title doesn't include "php" I thought I'd answer a bit more generally, because someone may search this question on google, get to this answer and say thanks because they didn't use PHP but used something else. My answer includes solution that doesn't require uint64 too. Also in a very side case, you can use libraries with fake uint64. Or maybe one day PHP will include uint64 too. :) I will edit my answer to not cause confusion. \$\endgroup\$
    – Nick
    Commented May 22, 2020 at 13:14
  • \$\begingroup\$ @matronator Your welcome. Having something like a "World Bank" in your game where players will be able to store their money also opens many other ways of creativity you can do :) \$\endgroup\$
    – Nick
    Commented May 22, 2020 at 13:19
1
\$\begingroup\$

The GMP (GNU Multiple Precision) library for PHP offers you the class GMP which can represent integers of arbitrary length and perform arithmetic with them.

MySQL offers you the BIGINT type which represents 8-byte (64-bit) integers (up to 9 Quintillion). If that's not enough, the type DECIMAL supports up to 65 decimal digits. If that's still not enough, you might have to encode your numbers as BLOBs or strings.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ The accepted answer provided me with the "design/creative" way which I ultimately decided to use. Your answer is a close runner up as it provides the "technical" approach and implementation in the technology I specified (PHP/MySQL). I might actually use it in the future for another game mechanic where limiting the number is not an option. Thank you! \$\endgroup\$
    – matronator
    Commented May 22, 2020 at 19:03
0
\$\begingroup\$

Another simple way to at least alleviate the problem is storing the amount by subtracting -2147483648 internally (assuming the amount doesn't go below 0). This allow you to use the full range of values (0 to 2^32 - 1). Not a long term solution, but it could work.

PHP allows for 64-bit signed integers, but only on 64-bit systems. The gives you a maximum of 9 * 10^18.

If not, there are libraries that allow for BigInt numbers. You would need an overhaul for some parts of your code, but it allows you to use arbitrary length integers.

\$\endgroup\$
4
  • \$\begingroup\$ That's a really interesting idea, I didn't even thought of that the range go both ways. As the player can't go under 0, it's a very creative way of increasing the limit, thanks! \$\endgroup\$
    – matronator
    Commented May 22, 2020 at 10:01
  • \$\begingroup\$ @matronator But that only givey you one more bit. Does doubling the money cap really solve your problem well enough to warrant the additional complexity of handling money that way? And if you are going to check all the places in your code where you deal with money to make sure this works out anyway, you can just as well implement the proper solution and use a BigInteger class. \$\endgroup\$
    – Philipp
    Commented May 22, 2020 at 11:18
  • \$\begingroup\$ I agree with @Philipp. The only reason I mentioned it was completeness. It’s just a temporary hack; you should definitely consider using 64-bit integers or BigInt libraries if possible. \$\endgroup\$
    – Rish
    Commented May 22, 2020 at 13:47
  • \$\begingroup\$ I think you might have misunderstood what I meant, sorry I probably could have phrase it better. Int64 or BigInt is without a doubt the better approach than this one, no argument there. I just meant to say that I really like this method in terms of creativity as an original technique of using the full bit range to full capacity. \$\endgroup\$
    – matronator
    Commented May 22, 2020 at 18:46
0
\$\begingroup\$

What about moving to 64-bit server, with 64-bit standard PHP integers (much bigger!) and using BIGINT in MySQL?

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .