- Home /
Big Numbers - How to convert (k, M, a )
Hello!
I'm making an Idle Click Game and I have some problems about the way to handle big numbers.
First of all, I'm not sure yet which variable type I should use to save the player's money. My options are Long, BigInteger, Decimal.
The first problem is that I need to be able to Serialize the numbers and some of them can't be serializable (bigInteger). I would have to save it as a String and convert all the time.
Also What do I do when the money reaches the limit of the variable (Long, BigInteger or Decimal) ?
I need a way so when he gets to the limit, I start using maybe an array of this variable and the total money it's the sum.
Can someone advice me about this question ?
Second question:
How can I convert the money in a way so I could display :
1 k instead of 1 000
1 M instead of 1 000 000
1 B instead of 1 000 000 000
1 T instead of 1 000 000 000 000
1 a instead of 1 000 000 000 000 000
1 b instead of 1 000 000 000 000 000
etc
I'm really lost in this problem and I need to solve it.
I would love if someone helps me.
Thank you in advance !
Answer by Eno-Khaon · Sep 14, 2018 at 03:55 AM
If you're especially worried about perfect accuracy, you may want to dig around the internet a bit to find a solution that's suitable for your needs. The first few examples I found as far as related links go include here and here.
If 100% perfect accuracy isn't important (and, looking at this realistically, it might not be when you consider that 150,000 out of 500 duoquinquagintillion is genuinely insignificant), you could try taking a different approach to this.
For example, you might consider comprising your values of a pair of numbers. The first would be the general value, where the second is the exponent of 10 to apply to it. If you had a base value of 3.424126 and an exponent of 10, the resulting number would be 34,241,260,000. It's fundamentally like what floating point numbers do internally, but you'd just assemble the number yourself (seeing how the first number would even be a float in the first place).
To compare values, you'd multiply the difference in exponents with the base value. For example,
3.4e10 - 2.2e9 = 3.18e10
3.4 - (2.2 * 109-10) = 3.18
3.4 - (2.2 * 10-1) =3.18
3.4 - 0.22 = 3.18
If the difference between values is greater than a number of significant digits specified (for instance, Unity's floating point has 7 digits of accuracy), then the difference can potentially be considered large enough to ignore the math altogether.
3.4e10 - 5e2
2 - 10 = -8
|-8| > 7 (accuracy)
As far as it matters...
3.4e10 - 5e2 = 3.4e10
If you put together a general value-type script to utilize number pairs in that manner, you can also make quick assumptions for shortcuts. For instance, if the exponent for a "purchase" is greater than your current "money" exponent, you already know you can't afford it.
Edit: For additional reading, consider this list of large number naming conventions.
Thank you so much for your detailed answer !
Basically I save 2 values (float and int) and use them to calculate the values of stuff, right ?
Using this method I think it's pretty easy to write the number in the screen.
If the money is 3.4e6 , I just need to check the exponential (6) . If it's 6, then the player has 3.4 $$anonymous$$illion Coins.
You are awesome. Thank you so much.
Now I "just" have to write the functions :)
Happy to help. Yeah, it'll involve a bit of script-writing to make it all fit together nicely, but it shouldn't be too bad.
The key point to keep in $$anonymous$$d with this approach will be that comparing exponents will be the ever-important first step. You can't rely on converting the values first, since the true, complete values (even as differences between them) may easily exceed the accuracy available to the float values themselves.
Furthermore, you can also give yourself string output variations in the script for when you want to display it shorthand (1e15) or verbose (1 quadrillion). If you wish to pursue that route, then a little research on the na$$anonymous$$g patterns should let you assemble value names based on the exponent value (or, rather, its multiple of 3).
Thanks man :D
I'm implementing the functions right now. How can I keep a float with only 3 decimal places.
1,024 ins$$anonymous$$d of 1,024523
I know how to write in the console with just 3 decimal places, but I want to save the value as 1,024. Or maybe I can write a function where : if (decimalPlaces == 4) ignore(lastOne)
How do I accomplish this?
Edit: Or maybe I'm overthinking and I don't need to worry about it. Just need to show the value with 3 decimal places.
Thank you very much
Sorry to burst your bubble on this one, but your suggested solution is pretty pointless ^^. A normal float already has a range of 10^38 which is already huge. Extending just the exponent manually just increases the overall range but you stay at the same precision which is pretty bad the higher the values get. See this table. Even you can represent such huge numbers you can't really do any useful math with them since the usable range is relative to the binary point. If the number is about 100 million the smallest change you can apply to the number is about "4". This relative distance scales linearly. So when you have 100 billion the smallest representable change would be around "4000".
Just using double ins$$anonymous$$d of float will boost the actual significant digits but also the exponent range. double has a range of about 10^300.
If all the number scale linearly with your max amount it would be just a matter of using different suffix letters while using the same down scaled number. If you actually need to work with those numbers it would be more practical to split the number into a float and a "long" part. Say you allow the float a range of +-1000 to keep the precision. If the float gets larger than 1000 you would carry those 1000 as 1 into the long value. The long type goes up to 9,223,372,036,854,775,807. Since value each represents 1000 you get an even larger range.
Of course it's not as large as any floating point solution but it can at least work with small changes like "+1". Again the single precision float looses the "+1" ability around 32 million. If you want to be able to smoothly increase a value by 1 every second you would need the ability to add 1/60 every frame (when running at a framerate of 60fps). So that would be "0.01666". Using just a float (with or without extended exponent) you can't go higher than about 100k in total
Due to the general nature of the "Idle Click Game" genre, much of the point of them is purely to reach ridiculously huge numbers for that sense of endless "progress" throughout them.
as an example of how over-the-top the number systems in this genre can be, Clicker Heroes reaches money values exceeding 1.0*1050000. Despite that, it starts off with single-digit numbers.
It's definitely true that smaller values (relatively speaking) will become insignificant after just a few factors of ten. With that in $$anonymous$$d, along with consideration for the extraordinary numbers these games intentionally reach toward, my aim is to provide a means of reaching excessively large numbers, providing feedback on values while they're still meaningful (within a few factors of ten), and also trying to keep the math library simpler and cheaper by not relying on a 128-bit+ math library.
While your points are all perfectly valid (and normal, really), this genre of game has a knack for ignoring all typical, sensible logic when it comes to numbers. Once you reach those high values, nothing will visibly matter for millennia or more unless they're "close enough" already.
Wow!
This is to theoretical for me ahah! I think I understood just an half of it
Answer by andrew-lukasik · Nov 18, 2021 at 10:44 AM
Since this question seems to be an active one once again, please let me drop a link to an implementation example of what @Eno-Khaon described in his answer, meaning:
(...) you might consider comprising your values of a pair of numbers. The first would be the general value, where the second is the exponent of 10 to apply to it. (...)
BFN
src: https://github.com/andrew-raphael-lukasik/BFN
Pros:
BFN.MaxValue is 1E+9223372036854776115
(for comparison: 1 googol is 1E+100)
fully serializable
editable in Inspector window
implements operators such as:
+
-
*
/
<
>
Cons:
lossy (
double
with auxiliaryInt64
exponent)
Those rad arithmetic operators lets you multiply or divide crazy-big numbers by each other. Also it's open-source, so you can extend and customize it however you need.
Answer by Max_Bol · Nov 18, 2021 at 08:01 AM
This is an old problem that was answered already, but I would like to add another simpler approach which can be used in many scenarios such as games like idle clickers, RPG with insane inflation (think Diablo III with its end-game damages being in the hundred if not thousands of Trillions) or even space simulation.
Instead of taking a number like a float as a whole when it reaches high numbers, it's far easier to simply cut it into simpler factors. To put it in simple terms, instead of "managing" a float, simply manage a List (of integers). For example, 100,300,200,459,000,124,294,25 could be counted as an a series of integer like:
int decimals = 25
int hundreds = 294;
int thousands = 124
int millions = 0
int billions = 459
int trillions = 200
int quadrillions = 300
int cinquillions = 100
Obviously, this as shown above comes at a breaking point where you would have to put, in advance, every single kinds of "steps" which is why I previously specify a List. The above would become
List<int> ListName = (25, 294, 124, 0, 459, 200, 300, 100);
Making a number manager for that list is simple (when 1 of any of the integer in the list reaches 1000, turn it to 0 and add 1 to the integer next to the list. If the next exceed the List.Count, add a new int to the list and so on.)
From this, you get many advantage:
You keep both high and low profile precision; (which is why I mentioned space simulator)
You can store the values up to... 2.47 billions (in theory) integers in a list which is serializable so, again theoretically, you could reach up to 1000^247000000000 which is kinda hard to exceed without doing something to reach it willingly. (Technically, you could reach far higher by using integers of something like 1000000000 x 2.47 billions times, but that makes it hard to cut up into good "step" slices.)
By getting the List.count, you can get which step the number is currently at, hence converting the example 100,300,200,459,000,124,294,25 to 100.3C by converting the 2 last integers of the list to string and added them with a dot (.) in-between as well as adding the proper value symbol (based on the List count) at the end.
As much as I like the theoretical angle of this approach, it's a bit too impractical for significant realtime use. It's not actually as "simple" as it sounds!
Between creating new a List<> for every variable and using 32 bits (~4 billion) to display just under 10 bits-worth (1024) of value (per 1000-size units), as well as actually maintaining accuracy for all values, this would explode in computational and RAM costs too rapidly for realistic use.
For example, 1 millinillion (1e3003) would use a 1002-entry List<> (1 for the top unit, 1001 more for the zeroes), totaling ~4kb per variable, and perfor$$anonymous$$g math on those broken-up segments would be crazy enough as-is.
1002 * 4 bytes = 4008 bytes, or ~4 kilobytes
By extension, use the maximum of int.MaxValue List<> elements and you've got a ~16 gigabyte allocation for a single number!
@andrew-lukasik and I took a different approach to this, focusing on the nearby range of values to the exponent/factor of the result.
Sorry to say, I don't plan on posting my since-made implementation here (mainly, it's REALLY long, 1600+ lines), but as an example in terms of string-printing, I pull from various tables just to build names for values:
// The "early" values, used only to represent values from 1e6 to 1e30 (9.999e32 effective limit)
private static string[] early_illions = new string[]
{
"m",
"b",
"tr",
"quadr",
"quint",
"sext",
"sept",
"oct",
"non"
};
// The "late" values, used from 1e33 and on, in conjunction with the "ten_illions" values
private static string[] late_illions = new string[]
{
"",
"un",
"duo",
"tre",
"quattuor",
"quinqua",
"se",
"septe",
"octo",
"nove",
};
// The "tens" values, used from 1e33 and on, in conjunction with the "late_illions" values
private static string[] ten_illions = new string[]
{
"",
"deci",
"viginti",
"triginta",
"quadraginta",
"quinquaginta",
"sexaginta",
"septuaginta",
"octoginta",
"nonaginta",
};
// The "hundreds" value, used from 1e303 and on, in conjunction with late- and ten- illions values
private static string[] hundred_illions = new string[]
{
"",
"cent",
"ducent",
"trecent",
"quadringent",
"quingent",
"sescent",
"septingent",
"octingent",
"nongent"
};
// The 3-part cycle for small values, such as []millionth, [ten-]millionth, or [hundred-]millionth
private static string[] small_cycle = new string[]
{
"",
"ten-",
"hundred-"
};
private enum Na$$anonymous$$gRules
{
NONE = 0,
m = 1, // 0001
n = 2, // 0010
s = 4, // 0100
x = 8 // 1000
}
Various names use various rules which take special-casing.
Example: 1e742 = 10 sesquadragintaducentillion ("s" na$$anonymous$$g rule for "ses" instead of "sex")
@Eno-Khaon This approach works really well if used properly or, at least, if used under the assumption that you know what you are doing and how to get whatever data out of it. It's a custom approach that require custom thinking. I'm using it for my space simulator game project and can generate an Universe of 4096 random stars with hundreds of thousands planets with coordinates and distance and movements. The universe is only 1 Em cubic in sizes (1 Em = 1000^6 Km) and the whole thing loads in 0.046ms. That's how fast it can run if properly managed
Fair enough. In the context of the universe, you definitely don't need ludicrously huge numbers, and exact positions are definitely valuable.
I was looking at it more from the typical-use-case context of the "Idle Click Game" as mentioned in the question, so I wasn't exactly focused on "location" as the main use-case. It makes a lot of sense to have a clear means of identifying positions in a space-based game.
(ran out of comment space)
As an addendum to this, what about cases where you have super small or negative numbers? There can be "value" to numbers like 1.638e-152 (1.638 hundred-novenquadragintillionths) or -5.2e38 (-520 undecillion). There's more to large numbers than being well above zero.
@Eno-Khaon It's rational and can cover small numbers by extending the list from its first instead of adding onto its last. (a native function to Lists) In case of relative usage, just keep the first number, in the array as a reference toward where the array starts in relativity. For example: 183,350,356.120300 Would be (-2, 300, 120, 356, 350, 183) meaning the decimal is located at 2 steps (1000^step) prior the the initial strep which is the decimal. You're right that there's a limit, but it's not only a relative limit, but on that exceed other possibilities in usages. (Especially if you building the number system as its own class which allows custom memory allocation to a constant limited size.)
Answer by FuturPlanet · Jun 03 at 05:57 PM
Unity made "BigInteger" a thing now: https://docs.microsoft.com/en-us/dotnet/api/system.numerics.biginteger?view=net-6.0
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
How to transfer time script to 24 hours and not decimals 1 Answer
How to display more than 6DP 2 Answers
C# public decimal variable not showing up in inspector 2 Answers