#How to handle addition/subtraction on very distant numbers for incremental game

1 messages · Page 1 of 1 (latest)

tulip hornet
#

Hi. I'd like to ask about your experience with performing +/- operations on numbers that passes over double.MaxValue range. IK i could stick within expotents in range ~630 (sum of double.MaxValue and double.Epsilon expontents to work with normalization) but i decided to overcome these limits and make my incremental gameplay either having way faster progression or being close-to-infinite. So... i came up with this rather trivial implementation of scientific notation:
https://hastebin.com/share/ekituwotin.csharp

then i use DOTS to perform quite safe calculations and accumulate numbers that cannot be added to the overall currency due to too much distance between them. If any1 interested, here's the ECS system for that:
https://hastebin.com/share/ayexotolay.csharp

I have a few questions about that implementation.

  • first of all: am i overengineering here?
  • is this the proper usage of DOTS? (quite fresh in the topic, so not sure about the "essence" of DOTS) yeah IK thats specifically dots question, but thread fits best under general channel
  • I thought i can benefit from inlining some methods, because they are used a lot in most operators, but not sure if i would profit due to variable range loops in there
  • any experience with similar design? I didnt find any industry standard that would overcome 600 expotents
  • no matemathician here, so some of you can find that Scientific.Number struct amusing. But well.. it works, even with some precision issues, not a big deal tho. Plan to refactor and utilize Log10 in the future perhaps.

Before you ask about out parameters and that weird FixedString conversion function - Burst Compiler was whining about any other approach.

Of course i am aware of BigInteger, though by design my game would operate on multiple "work units" each having its own "big number", so they would eat all my RAM, thats why i try to figure out the alternative.

#

(open up for critics, even most harsh would be greatly appreciated ;D)

lethal trout
#

LGTM 👍 sry no harsh reviews here

#

the AreCloseEnough looks a little excessive, especially with the const there. I don't understand why you check for negative exponents too

tulip hornet
#

right. Probably i was having a brainfart while writing that method, then i didn't look again into it. the "else" statement contents there should be just enough, thanks for pointing that

#

not sure wdym by having const there is wrong. Assuming that i strip the code to leave just if-else, i have to define maximum range of numbers valid for addition and subtraction. Const value seems to be accurate in this case

#

btw it's useful in normalizing to count the iterations. Just a safety check to not go to infinite loop accidentally

lethal trout
#

and a more simplified version could be to just hold the (e.g.) 4 most important digits rounded, ignoring the rest.

e.g.:
num: 0122.8584 E12
display: 122.86 E12

then if the number reaches 1000 first, it increases to E13 of the same format, and the last digit becomes insignificant (you'd divide the num /10 and do exponent++).

#

final comment is that jumping from Ns to Ks to Ms/Bs/Ts is a quite fun part of idle incremental games, so might wanna map exponential representation to in-game display representation as well.

e.g.:

    { 0, "" },
    { 3, "K" },
    { 6, "M" },
    { 9, "B" },
    [...]
};

And if the number's current exponent is before the last number of the dictionary's entries, stick with the lowest and only increase the exponent by 3 (e.g. 1.02M as soon as they reach the Ms)

#

this is more of a design comment, but yeah something as small as this can feel REALLY rewarding

tulip hornet
#

yes, of course i plan to add a player settings to choose between scientific notation, engineering notation (which will be just another form of ToString) and its verbose variants up to Centillion (or Googol, depends of sources i find as they are quite spread out all over internet and kinda not-complete) maybe, but all based on calculations of Scientific.Numbers

tulip hornet
lethal trout
#

there I'm referring to another algorithm that would only need to hold 2 shorts (num, exp) from 0-999

tulip hornet
#

ah, okay, got the point. But using shorts would effectively narrow the range of available addition/subtraction. I mean having significand as a double, allows me to add eg. (double.MaxValue - 1) + double.Epsilon

#

memory isnt as huge concern with the types, i do not plan to have >100k work units anyways, and PiggyBanks buffers wont get filled with many values, because for example having app closed for 24hours will accumulate only ~5 expotents provided tickSpan is ~1second, then its just a single index in the buffer

#

tbh i designed the system to handle the most edge cases, that would probably never be reached by the player anyways unless they spend entire lifetime playing my game which is unlikely lol

lethal trout
#

btw expotents exponents, right?

tulip hornet
#

O-M-G i cant read/write xD thanks

#

Thanks for feedback man, much appreciate this 🙏

tulip hornet
#

For any1 reading this thread - feel free to steal that nasty impl if you like

lethal trout
#

also this implementation is wrong now:

#

5.23 E54 should not be close enough with 1.18 E606, but this method will say it is

#

a nice impl would be:

public static bool AreCloseEnough(Number a, Number b, double epsilon) => a.exp == b.exp && abs(a.num - b.num) <= epsilon;
public static bool IsSignificant(this Number a, Number b) => abs(a.exp - b.exp) <= CONSTS.RoundingLevel; // e.g. E12 is insignificant to E20
tulip hornet
# lethal trout `5.23 E54` should not be close enough with `1.18 E606`, but this method will say...

of course they should. idk if we're talking about the same thing. We can perform +- operations on any double value. so the valid range is from the smallest positive to highest positive, where double.MaxValue (E308) and double.Epsilon (E-324) that gives us the safe range be 632, i made it 630 to be 100% sure to avoid situations like double.MaxValue + 1
Then in +operator i simply iterate to find the common exponent between E308 and E-324 for both values, perform addition on significands then normalize the result.
or am i missing something?
That is nice code you pasted, but actually my AreCloseEnough does just the same thing as yours IsSignificant. Just the matter of naming ig.

lethal trout
#

oh you want number 5 to be Close Enough with 5 trillions? Then sure

#

I assumed your AreCloseEnough implied AreApproximatelyEqual

tulip hornet
#

yeah basically i want to squeeze out every bit of capabilities of double type.

#

its not for display ofc, just to perform calculations on distant numbers

#

for display is just the two decimal points

lethal trout
#

Then that might be over engineering 😛
I understand if it’s for educational purposes but as I said you can do this with 2 shorts, so actually going out of your way to support the addition of E52 to E600 might not be optimal

#

Imagine the difference in size of galaxies vs a granule of sand is ~E24 lol

tulip hornet
#

Yeah I agree. Most likely such op as adding E52 to E600 will never happen in playtime so I could narrow that const range. However, I still see some some advantages keeping it as it is and using double^long with safe addition range of 630exp. First of all - it allows me to push the maximum value up to double.MaxValue^long.MaxValue so I can get to the way larger number. Secondly, it covers really edge case where adding ridiculously distant numbers becomes possible, even if will never happen, it could. Makes me feel comfortable xd And for the last thing, just as you said, there is also the educational aspect of making such system actually working and not eating every bit of RAM

tulip hornet
#

One of the ways I can think of actual usefulness of such design is a situation (not sure if I will alowe smth like this to happen, but mayyyyyybe) when we have 2 WorkUnits. The first is set up to add 1e20 to the wallet every 0.5 sec, and the other one adding 1e500 every month (from top of my head, so exponents are more like a guess here, not actual calculated values that would be valid for 0.5s x 1month difference). Then it will still allow me to add these nums directly to the wallet without touching the second index in the piggybank.
Or another example: start new game with a single WorkUnits set to 1e0/1sec, close the app and open again after 3yrs xd
*May be talking nonsense here, just got up n didn't even finish the coffee, but hopefully you'll get the point. If we could cover such situations, even if they are basically abstract then... Why not 😅

#

Not much difference between short^short with safe addition range of 50exp in terms of how actual system works

tulip hornet
# tulip hornet One of the ways I can think of actual usefulness of such design is a situation (...

Ok, woke up. Actually the 3yrs example is irrelevant here. But I have a plan for my game to have modifiers applied per WorkUnit/Group of WorkUnits. Such modifier would eg. Raise their earnings for a short duration. As I said about design covering way faster progression than most games of the genre, such modifier could be able to raise earnings of a unit from 1e400 to 1e1000 for 30seconds for example. After that we've got 3e1001 in the wallet. Buy some upgrade that costs about that much, so basically subtracting all currency earned with bonus, and then after 30 seconds and costly upgrade we maintain accurate earnings from other non-boosted units that were accumulated during that 30s

#

Actually it could be just a regular idle progression as we know, but kind of stretched to cover larger values / differences. It's all about design and making players able to have 1.23^456789 currency in their wallet