Skip to content

Commit 674dbf1

Browse files
committed
VM_rint: do not generate negative zeroes when the input is positive.
This matches what rint() does by default in all current compilers, but does not depend on rounding mode. NOTE: VM_rint from Quake is _not_ rint from the C standard; it does not round to _even_ when breaking ties. It actually is C23's roundeven(). Fixes issue #276.
1 parent a5e5b88 commit 674dbf1

File tree

2 files changed

+44
-3
lines changed

2 files changed

+44
-3
lines changed

prvm_cmds.c

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "mdfour.h"
1818

1919
extern cvar_t prvm_backtraceforwarnings;
20+
extern cvar_t prvm_gameplayfix_rintisround;
2021
#ifdef USEODE
2122
extern dllhandle_t ode_dll;
2223
#endif
@@ -1511,10 +1512,48 @@ void VM_rint(prvm_prog_t *prog)
15111512
VM_SAFEPARMCOUNT(1,VM_rint);
15121513

15131514
f = PRVM_G_FLOAT(OFS_PARM0);
1514-
if (f > 0)
1515-
PRVM_G_FLOAT(OFS_RETURN) = floor(f + 0.5);
1515+
1516+
if (prvm_gameplayfix_rintisround.integer)
1517+
{
1518+
// Guarantee that integers (including negative zeroes) and
1519+
// infinities remain as is. This also guarantees the following
1520+
// code doesn't run for zeroes.
1521+
if (f == floor(f))
1522+
PRVM_G_FLOAT(OFS_RETURN) = f;
1523+
// The copysign ensures that output negative zeroes always have
1524+
// the same sign as the input. This likely is already the case
1525+
// anyway, but I honestly can't find anywhere in the C
1526+
// standards which kind of zero floor(0.8) and ceil(-0.8) are
1527+
// supposed to be.
1528+
else if (f > 0)
1529+
PRVM_G_FLOAT(OFS_RETURN) = copysign(floor(f + 0.5), f);
1530+
else
1531+
PRVM_G_FLOAT(OFS_RETURN) = copysign(ceil(f - 0.5), f);
1532+
// NOTE: Technically rint() when rounding to nearest should
1533+
// round towards even. Leaving things as is for now, though, as
1534+
// it matches Quake.
1535+
}
15161536
else
1517-
PRVM_G_FLOAT(OFS_RETURN) = ceil(f - 0.5);
1537+
{
1538+
// Known broken implementation of Quake, except with well
1539+
// defined overflow behavior.
1540+
if (f >= 2147483647.5 || f <= -2147483648.5)
1541+
{
1542+
// Intel 8087 and SSE2 overflow behavior.
1543+
PRVM_G_FLOAT(OFS_RETURN) = -2147483648.0;
1544+
}
1545+
else
1546+
{
1547+
// Original code from Quake.
1548+
//
1549+
// Uses cast to int, and thus truncates and never
1550+
// produces negative zeroes.
1551+
if (f > 0)
1552+
PRVM_G_FLOAT(OFS_RETURN) = (int)(f + 0.5);
1553+
else
1554+
PRVM_G_FLOAT(OFS_RETURN) = (int)(f - 0.5);
1555+
}
1556+
}
15181557
}
15191558

15201559
/*

prvm_edict.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ cvar_t prvm_garbagecollection_strings = {CF_CLIENT | CF_SERVER, "prvm_garbagecol
5555
cvar_t prvm_stringdebug = {CF_CLIENT | CF_SERVER, "prvm_stringdebug", "0", "Print debug and warning messages related to strings"};
5656
cvar_t sv_entfields_noescapes = {CF_SERVER, "sv_entfields_noescapes", "wad", "Space-separated list of fields in which backslashes won't be parsed as escapes when loading entities from .bsp or .ent files. This is a workaround for buggy maps with unescaped backslashes used as path separators (only forward slashes are allowed in Quake VFS paths)."};
5757
cvar_t prvm_gameplayfix_div0is0 = {CF_SERVER, "prvm_gameplayfix_div0is0", "0", "When set to 1, floating point division by 0 will return zero instead of returning the IEEE standardized result (likely nan or inf). Other ways of getting non-finite values are not affected, and the warning will still print."};
58+
cvar_t prvm_gameplayfix_rintisround = {CF_SERVER, "prvm_gameplayfix_rintisround", "1", "When set to 1, rint() behaves like C99's round() function. When set to 0, it matches Quake exactly and has the following quirks: if the value doesn't fit into int, it returns -2147483648, and it never returns negative zero, even if the input is negative."}; // TODO: remove if nobody ever has toggle this off.
5859

5960
static double prvm_reuseedicts_always_allow = 0;
6061
qbool prvm_runawaycheck = true;
@@ -3275,6 +3276,7 @@ void PRVM_Init (void)
32753276
Cvar_RegisterVariable (&prvm_stringdebug);
32763277
Cvar_RegisterVariable (&sv_entfields_noescapes);
32773278
Cvar_RegisterVariable (&prvm_gameplayfix_div0is0);
3279+
Cvar_RegisterVariable (&prvm_gameplayfix_rintisround);
32783280

32793281
// COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!)
32803282
prvm_runawaycheck = !Sys_CheckParm("-norunaway");

0 commit comments

Comments
 (0)