Aztec monks with 1/55 HP no longer die when picking up or dropping a relic
101–110 of 267 posts
Re: Aztec monks with 1/55 HP no longer die when picking up or dropping a relic
#102Not knowing it was about a game, that was one of the strangest headlines ever to read on here.
Re: Aztec monks with 1/55 HP no longer die when picking up or dropping a relic
#103Earlier quoted context omitted.
Did 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.
But 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…
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.
Re: Aztec monks with 1/55 HP no longer die when picking up or dropping a relic
#104Earlier quoted context omitted.
> The formula was probably `return new_maxhp * (old_hp / old_maxhp);` ... > Due to floating point rounding errors, convert_hp(1, 55, 55) equals 0.999999940395355224609375, which is less than 1, which means the unit dies. Ahh. The old "Multiplying and dividing is associative... for the set of reals, not for the subset that you can actually afford to compute with." So it should have been `(new_maxhp * old_hp) / old_max…
Seems all that could have been avoided by using rationals in general and the condition for living being >0 instead of dying The more I write code, the more I notice how rarely one truly should want floats in most kinds of programs and how they almost always carry a whole bag of problems with them.
Why can’t they store HP internally as “millihealth” integers, do integer arithmetic (including division), and then divide that by 1000 purely for display?
Re: Aztec monks with 1/55 HP no longer die when picking up or dropping a relic
#105Earlier quoted context omitted.
Rationals fail in their own spectacular ways. Take a simple approximate exponential decay: x ← (99/100) * x. In exact rational arithmetic, your program will happily calculate integers (99^k) and (100^k) for unboundedly large values of k (there's no reduction – they're all coprime), grinding the program to a halt until it runs out of memory. "Oh, the solution is obvious: you should simply round that to an approximate…
The difference is, that with rationals you do not lose any precision on the way. I doubt, that 99^k is part of AoE2 code. For the simple calculations in AoE2 regarding unit health, rationals will do a fine job. With rationals the rounding does not happen during the precise calculation. It happens explicitly, only once, at the end of the calculation, at the programmer's wish and conscious decision, thereby avoiding th…
loop { x ← a*x }
as one function call x(t) = exp(-k*t), so that one looks pointless. But moving-average [0] type expressions loop { x ← a*x + f(t) }
are more interesting. You can't generally refactor those into exp() calls, like x(t) = sum[ f(t_i) * exp(-k*(t - t_i)) ], since that introduces a memory leak – you need to hold an explicit list of all the past values of f(t), in order to compute it this way. That's why the pattern [0] is uniquely useful: it's a trick to incrementally accumulate a moving average using just one variable of state.[0] https://en.wikipedia.org/wiki/Moving_average#Exponential_mov...
Here's one uncontrived way this pattern might show up: "the NPC has a numeric disposition towards the player, and the player's actions have positive and negative effects on it. These effects have finite duration, and disposition decays back to the neutral value". Another way: you have a large list (like a time series) you want to reduce to a small one by interpolation. Another way: you have some PID-like controller in your program, and to make it behave, you need a cheap filter to clean a jittery input signal. (I'm building these for my Factorio factories right now – it's the only reason this example occurred to me. "x ← (99/100) * x" is a factory control component I had to implement out of int32_t's).
Re: Aztec monks with 1/55 HP no longer die when picking up or dropping a relic
#106Fun fact: The competitive AoE2 scene remains very much alive and thriving. Several top players are able to make a living from it, thanks to streaming revenue and tournament prize money. There are even some streamers, such as T90 and MembTV, that have managed to create successful careers despite not being competitive players themselves.
Note that T90 is like 2300 Elo. Which isn't "top level pro", but I'm pretty sure he'd kick most of our asses. T90 is probably the strongest dedicated commentator / announcer / caster in this community.
Re: Aztec monks with 1/55 HP no longer die when picking up or dropping a relic
#107Earlier quoted context omitted.
But 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…
Or 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 su…
Re: Aztec monks with 1/55 HP no longer die when picking up or dropping a relic
#108From the comments: "This was caused by a floating point rounding error. A unit switching jobs internally becomes a different unit (i.e. a monk with a relic becomes a monk without a relic), and when doing that, HP is scaled proportionally based on the old and new max HP using 32-bit floating point maths." And there are YouTube videos about this https://www.youtube.com/watch?v=MytWNbpnAVY
> The formula was probably `return new_maxhp * (old_hp / old_maxhp);` ... > Due to floating point rounding errors, convert_hp(1, 55, 55) equals 0.999999940395355224609375, which is less than 1, which means the unit dies. Ahh. The old "Multiplying and dividing is associative... for the set of reals, not for the subset that you can actually afford to compute with." So it should have been `(new_maxhp * old_hp) / old_max…
Rationals have a big problem where under multiplication and division they need to be re-normalized periodically, and that re-normalization is slow (running gcd). Under addition and subtraction, rationals are great.
*the one big difference is that division rounding is different
Re: Aztec monks with 1/55 HP no longer die when picking up or dropping a relic
#109Earlier quoted context omitted.
Seems all that could have been avoided by using rationals in general and the condition for living being >0 instead of dying The more I write code, the more I notice how rarely one truly should want floats in most kinds of programs and how they almost always carry a whole bag of problems with them.
> Seems all that could have been avoided by using rationals in general It's 2023: I'm still surprised that so many new languages/platforms still only provide only basic integer and IEEE-754 types for numerics: Rust, C#, Java, Swift and others still lack built-in, runtime-provided, nor even standard-library-provided rational types, which I assume really should be the preferred type for most business/domain/application…
A decimal type is definitely necessary, and you can use IEEE decimal floating point for that, too! Other decimal types are very useful. Rationals are a different story: rational numbers are great for addition and subtraction, but if you're going to be doing a lot of multiplication and division, you are going to find yourself also doing a lot of slow gcd calculations to reduce/renormalize the fractions (incidentally, decimal types also need renormalization, but it's a lot cheaper). Most cases where rational types are used today are very careful about not having long chains of these operations.
As to NaNs: those are all considered pretty carefully, and 754-2019 actually reduced the amount of NaNs that propagate around quite a bit. For example, make sure you are using fmax(a, b) instead of std::max(a, b).