Allow floating-point division by zero in ZScript
Moderator: GZDoom Developers
Allow floating-point division by zero in ZScript
Maybe I'm crazy, but since ZScript can represent floating-point infinity, shouldn't floating-point division by zero result in that, instead of crashing?
For the second time now, I've had to special-case division by zero, where infinity would have been a perfectly correct result. That got me thinking about this. Again, feel free to ignore me if I'm crazy.
For the second time now, I've had to special-case division by zero, where infinity would have been a perfectly correct result. That got me thinking about this. Again, feel free to ignore me if I'm crazy.
- Major Cooke
- Posts: 8170
- Joined: Sun Jan 28, 2007 3:55 pm
- Preferred Pronouns: He/Him
- Location: QZDoom Maintenance Team
Re: Allow floating-point division by zero in ZScript
ಠ_ಠ
Pardon my asking but, just what would you need infinity for?
Pardon my asking but, just what would you need infinity for?
Re: Allow floating-point division by zero in ZScript
From Use To Pickup:
It needs to compute how fast an animation is to run, in floating-point change per tic (TweenChange). For user-friendliness, however, the menu options represent the animation speed as total duration in seconds (ps.Settings.Fade{In,Out}Time). To get from the latter to the former, I compute 1/(time*TICRATE).
It's perfectly valid to set the durations to 0, to turn off the animation entirely. This would be represented by TweenChange ≥ 1. ∞ ≥ 1, so an infinite result would result in correct behavior (clamp(Tween + ∞, 0, 1) = 1), and wouldn't require the special case.
Code: Select all
if (HighlightState == FADING_IN || HighlightState == FADING_OUT) {
double time;
if (HighlightState == FADING_IN)
time = ps.Settings.FadeInTime.GetFloat();
else
time = ps.Settings.FadeOutTime.GetFloat();
// Avoid division by zero.
// Division by zero would actually yield correct results (positive infinity), but the engine crashes instead of passing such a value through, so we have to special-case it.
if (time <= 0.)
TweenChange = 1;
else
TweenChange = 1. / (time * TICRATE);
if (HighlightState == FADING_OUT)
TweenChange = -TweenChange;
}
if (TweenChange)
Tween = clamp(Tween + TweenChange, 0., 1.);
It's perfectly valid to set the durations to 0, to turn off the animation entirely. This would be represented by TweenChange ≥ 1. ∞ ≥ 1, so an infinite result would result in correct behavior (clamp(Tween + ∞, 0, 1) = 1), and wouldn't require the special case.
Re: Allow floating-point division by zero in ZScript
The other time I ran into this problem, I needed to compute how long it would take an actor to reach a destination at a given speed (ignoring portals and such), like so:
Again, division by zero resulting in infinity would be correct here: an object with zero velocity takes infinite time to reach its destination.
I didn't end up using that code, but it did strike me as interesting at the time. It was the first time I remember writing a potential division by zero on purpose—or I would have, if the engine allowed it.
Of course, this would still yield incorrect results if the distance to the destination is also zero. 0/0 usually results in NaN or infinity, but in this case the actor would reach its destination in zero time despite zero speed, since it's already there.
Code: Select all
Actor a = …;
Vector3 destination = …;
let travelTime = (destination - a.Pos).Length() / a.Speed;
I didn't end up using that code, but it did strike me as interesting at the time. It was the first time I remember writing a potential division by zero on purpose—or I would have, if the engine allowed it.
Of course, this would still yield incorrect results if the distance to the destination is also zero. 0/0 usually results in NaN or infinity, but in this case the actor would reach its destination in zero time despite zero speed, since it's already there.
Re: Allow floating-point division by zero in ZScript
Division by zero should never result in infinity. The only correct value is NaN.
There are pros and cons for allowing division by zero. The pro is obviously that it doesn't crash. On the other hand, you're now allowing NaN values to get stored and the rest of the engine has to be able to handle that. It also gets significantly harder to figure out what went wrong when you suddenly see NaN values a million miles away from the code that caused it.
Why not just check for zero the places where that is an allowed value? If travelTime gets a NaN value you have to check for that anyway.
There are pros and cons for allowing division by zero. The pro is obviously that it doesn't crash. On the other hand, you're now allowing NaN values to get stored and the rest of the engine has to be able to handle that. It also gets significantly harder to figure out what went wrong when you suddenly see NaN values a million miles away from the code that caused it.
Why not just check for zero the places where that is an allowed value? If travelTime gets a NaN value you have to check for that anyway.
- Graf Zahl
- Lead GZDoom+Raze Developer
- Posts: 49056
- Joined: Sat Jul 19, 2003 10:19 am
- Location: Germany
Re: Allow floating-point division by zero in ZScript
Considering how much trouble NaNs have been with the JSON serializer, let's better not think about this any further.
Re: Allow floating-point division by zero in ZScript
IEEE 754 disagrees with you on this point. And it makes sense to me: as the divisor approaches zero, the result approaches infinity, so it follows that a divisor of zero ought to result in infinity.dpJudas wrote:Division by zero should never result in infinity. The only correct value is NaN.
Ok, fair enough.Graf Zahl wrote:Considering how much trouble NaNs have been with the JSON serializer, let's better not think about this any further.
Re: Allow floating-point division by zero in ZScript
Alright, I only tested the 0.0/0.0 case before writing that. Nevertheless, you can't apply that kind of logic you're doing here mathematically. If you read the link closely this is about trying to handle underflow situations in a more graceful way.argv wrote:IEEE 754 disagrees with you on this point. And it makes sense to me: as the divisor approaches zero, the result approaches infinity, so it follows that a divisor of zero ought to result in infinity.
Remember that infinity in floating point doesn't mean mathematical infinity. It means the value overflowed for what can be represented with this number of bits.
Re: Allow floating-point division by zero in ZScript
Nothing makes sense when division by zero is involved.argv wrote:And it makes sense to me: as the divisor approaches zero, the result approaches infinity, so it follows that a divisor of zero ought to result in infinity.
Keep in mind that division is the inverse operation of multiplication. That is to say, if you have X × Y = Z, then you also have Z ÷ Y = X. Based on that, if some random number, let's say 12, is divided by zero and gives you infinity, then that means that infinity multiplied by zero gives you 12. Do you see the problem?
Even better: X ÷ X = 1. This is true for every number. So what is X = 0? Shouldn't you get 0 ÷ 0 = 1? But wait, 0 ÷ X = 0; this also is true for any number. So 0 ÷ 0 = 0? But it's also equal to 1 and to infinity!
Therefore, if X ÷ 0 is legal, then 0 = 1 = infinity. Good luck getting the rest of your maths to still work after that.
Re: Allow floating-point division by zero in ZScript
The result only approaches infinity as you take the limit in the positive numbers, taking the limit in the negative numbers gives a limit of -infinity, so this has to be undefined because it has (at least) two limits. As Gez points out, sometimes you can have more than two, like with 0/0 = x which, using algebra, can be turned into 0 = 0x, which is true for every number, so therefore x must be undefined here too but with an infinite number of results.argv wrote:And it makes sense to me: as the divisor approaches zero, the result approaches infinity, so it follows that a divisor of zero ought to result in infinity.
Re: Allow floating-point division by zero in ZScript
For brevity's sake, I usually end up putting in a "max([divisor that might end up as zero], [arbitrary small number]);" call in cases like this...
e.g.:
Not sure if that slows things down internally any more/less than using if statements to the same effect, but I haven't noticed any issues so far.
EDIT: Fixed incorrect example, in case someone else stumbles across this post. Thanks, Gez!
e.g.:
Code: Select all
TweenChange = 1. / (max(time, 0.0001) * TICRATE);
EDIT: Fixed incorrect example, in case someone else stumbles across this post. Thanks, Gez!
Last edited by AFADoomer on Sat Jul 28, 2018 5:08 pm, edited 2 times in total.
- Graf Zahl
- Lead GZDoom+Raze Developer
- Posts: 49056
- Joined: Sat Jul 19, 2003 10:19 am
- Location: Germany
Re: Allow floating-point division by zero in ZScript
In terms of scripting, min and max are VM opcodes so they are very efficient. An 'if' requires a branch which is 2 more costly instructions most of the time.
Re: Allow floating-point division by zero in ZScript
Is clamp also a single VM operation?
- Graf Zahl
- Lead GZDoom+Raze Developer
- Posts: 49056
- Joined: Sat Jul 19, 2003 10:19 am
- Location: Germany
Re: Allow floating-point division by zero in ZScript
No. It's a min followed by a max.
Re: Allow floating-point division by zero in ZScript
I don't get it -- this doesn't avoid the potential division by zero, does it?AFADoomer wrote:For brevity's sake, I usually end up putting in a "max([calculations], [arbitrary small number]);" call in cases like this...
e.g.:Not sure if that slows things down internally any more/less than using if statements to the same effect, but I haven't noticed any issues so far.Code: Select all
TweenChange = max(1. / (time * TICRATE), 1);
I'd expect more something like this:
Code: Select all
TweenChange = 1. / (max(time, 0.0001) * TICRATE);