4 ms·
Aren't rationals subject to exponential blowup in storage/precision? IMO the best approach for business logic values is picking the smallest meaningful unit and
by PhilipRoman 4y ago
Aren't rationals subject to exponential blowup in storage/precision? IMO the best approach for business logic values is picking the smallest meaningful unit and representing it with integers. Precision and storage requirements become very easy to reason about.
- DaiPlusPlus 4y ago> IMO the best approach for business logic values is picking the smallest meaningful unit and representing it with integers. How would you design an RTS unit stats system to avoid this AoE Monk HP bug using only integer types?
- bruce343434 4y agoHow much health precision do you need? Make hp an int. Or like a /64 fixed point.
- account42 4y agoCongratulations, you just introduced a new damage threshold / resistance threshold / etc where things effectively do no damage. Fixed point would not have helped here. Integer would only have helped because dividing first would break immediately.
- falcor84 4y agoWell, in this case, if a unit is converted to a different type and back, it would just keep its (absolute) value of HP (in this case 1), no need to scale anything.
- db48x 4y agoYou haven’t thought it through yet. In this case, HP _is_ an integer, and so is the MaxHP for the unit type. And yet the bug still occurs! Storing the HP as an integer does not prevent the problem from ever occurring, because fundamentally the user still expects HP to be proportional. That is, suppose a unit is sitting there at full health, and you research a tech that increases that unit’s max hp? Should the unit now be at less than full health, as if it had been in combat? Users probably don’t like that. Suppose it is at full health, and then it gets downgraded to a type with less maximum health. Does it stay at it’s current HP? Users probably won’t like being attacked with supercharged units that have extra HP; they’ll think that the other player was cheating somehow. What if it is damaged and at half health, then gets upgraded. Should it be fully healed? Gain HP equal to the difference between the new and old maximum HP? Or should it gain half of that, so that it stays at half health? Or perhaps HP is too limiting, and the game should do what Dwarf Fortress does. DF knows the approximate surface area of every body part (based on each creature’s body plan, plus individual stats such as strength, fatness, size, etc), and the size of every weapon. Every attack therefore deals damage to a certain area, measured in square inches, and individual body parts will be destroyed once a sufficient percentage of that surface area is damaged by wounds. Or maybe you are designing this game in the 90’s, and you have to worry about squeezing unit updates for 200 units per player into the bandwidth provided by the average modem of the day (probably 28.8kbaud), so you stick to one integer because it’s the simplest think that can possibly work, and the number of bytes per update can be calculated in advance. And then some other schmuck gets stuck with the job of handling unit type changes (but be warned that the schmuck might be yourself in six months).
- ilyt 4y ago>That is, suppose a unit is sitting there at full health, and you research a tech that increases that unit’s max hp? Should the unit now be at less than full health, as if it had been in combat? Users probably don’t like that. >Suppose it is at full health, and then it gets downgraded to a type with less maximum health. Does it stay at it’s current HP? Users probably won’t like being attacked with supercharged units that have extra HP; they’ll think that the other player was cheating somehow. >What if it is damaged and at half health, then gets upgraded. Should it be fully healed? Gain HP equal to the difference between the new and old maximum HP? Or should it gain half of that, so that it stays at half health? if (oldHP == maxOldHP) { newHP = newMaxHP } else { newHP = max(1,scale(oldHP,oldMaxHP,newMaxHP)) not exactly complex
- ghusbands 4y agoSure, if you fully predict all bugs in advance and compensate for them, nothing is complex, but that's not really feasible.
- mrguyorama 4y ago>Sure, if you fully predict all bugs in advance and compensate for them, nothing is complex, but that's not really feasible. Uhh, last time I checked, that was literally my job, and specifically what I went to school for!
- db48x 4y agoNot exactly complex, but you posted too quickly to have found the best solution. A better solution is to kill the unit when it drops below zero HP, not when it drops below 1.0; scaling will never move the current HP past zero in either direction. Having 0.9999994 HP instead of 1.0 HP would not cause any problems then; the unit is still one hit away from death in either case. The best solution is probably to store the current HP as a percentage of the max, because then you never have to rescale it in the first place.
- 4y ago
- CodesInChaos 4y ago`(new_maxhp * old_hp) / old_maxhp` avoids the original problem when using integers. Though you still need to make sure your type is big enough to not overflow. Or using fixed-point (which are a relatively simple abstraction over integers): `old_hp * (new_maxhp * old_maxhp)`.
- rvnx 4y agoThey probably just added: return ROUND()... and went back to play
- ilyt 4y agomax(1,...
- rini17 4y agoDid this exponential blowup ever prove troublesome in practice? I think it can be easily solved by strategically placed rounding steps. But explicit rounding is advisable in many cases anyway.
- jrochkind1 4y agoBut strategically placed rounding was in fact the solution to the original problem that in this thread Rationals were proposed as solving... Rational types are not super popular and don't get used that much, most (not all) people that end up using them do it very intentionally and consciously and know what they're dealing with -- if they were more ubiquitious, I suspect more trouble would be seen "in practice". While it's probably true that any digital number format will have edge cases that in fact come up in practice, my personal choice of "Why the heck isn't this more popular, why doesn't every language support it, why isn't it in fact the default representation of a numeric literal" -- is floating point "decimal" types, like ruby (or I think Java?) BigDecimal, rather than rational. I think they mostly work matching programmer's mental models of numbers, and for many/most common uses on 2023 platforms the performance is just fine. (this would not have been true 30 years ago).
- rini17 4y agoOr it's vendors that don't care about correctness, like providing decimals by default and high performance floating point as an "optimized" option. Python did something like that by using bigints as a default for integers. What would not have been true 30 years ago? I remember "real" fixed-point type built-in in Turbo Pascal and explicitly documented as suitable for money. Common Lisp has had first class fractions support since the start.
- jrochkind1 4y agoI meant to suggest that 30 years ago the performance difference between "floats" (floating-point binary) and "BigDecimal"-style arbitrary-precision floating-point decimal would have been much more significant to many more real-world use cases, compared to now. So that may have been a reason not to make them the default when you simply write a literal `5.4` in code, but that argument is less now.