diff --git a/prvm_cmds.c b/prvm_cmds.c index e540e8517..f5e1fceb3 100644 --- a/prvm_cmds.c +++ b/prvm_cmds.c @@ -17,6 +17,7 @@ #include "mdfour.h" extern cvar_t prvm_backtraceforwarnings; +extern cvar_t prvm_gameplayfix_rintisround; #ifdef USEODE extern dllhandle_t ode_dll; #endif @@ -1511,10 +1512,48 @@ void VM_rint(prvm_prog_t *prog) VM_SAFEPARMCOUNT(1,VM_rint); f = PRVM_G_FLOAT(OFS_PARM0); - if (f > 0) - PRVM_G_FLOAT(OFS_RETURN) = floor(f + 0.5); + + if (prvm_gameplayfix_rintisround.integer) + { + // Guarantee that integers (including negative zeroes) and + // infinities remain as is. This also guarantees the following + // code doesn't run for zeroes. + if (f == floor(f)) + PRVM_G_FLOAT(OFS_RETURN) = f; + // The copysign ensures that output negative zeroes always have + // the same sign as the input. This likely is already the case + // anyway, but I honestly can't find anywhere in the C + // standards which kind of zero floor(0.8) and ceil(-0.8) are + // supposed to be. + else if (f > 0) + PRVM_G_FLOAT(OFS_RETURN) = copysign(floor(f + 0.5), f); + else + PRVM_G_FLOAT(OFS_RETURN) = copysign(ceil(f - 0.5), f); + // NOTE: Technically rint() when rounding to nearest should + // round towards even. Leaving things as is for now, though, as + // it matches Quake. + } else - PRVM_G_FLOAT(OFS_RETURN) = ceil(f - 0.5); + { + // Known broken implementation of Quake, except with well + // defined overflow behavior. + if (f >= 2147483647.5 || f <= -2147483648.5) + { + // Intel 8087 and SSE2 overflow behavior. + PRVM_G_FLOAT(OFS_RETURN) = -2147483648.0; + } + else + { + // Original code from Quake. + // + // Uses cast to int, and thus truncates and never + // produces negative zeroes. + if (f > 0) + PRVM_G_FLOAT(OFS_RETURN) = (int)(f + 0.5); + else + PRVM_G_FLOAT(OFS_RETURN) = (int)(f - 0.5); + } + } } /* diff --git a/prvm_edict.c b/prvm_edict.c index 7f9c3bed1..46cca92da 100644 --- a/prvm_edict.c +++ b/prvm_edict.c @@ -55,6 +55,7 @@ cvar_t prvm_garbagecollection_strings = {CF_CLIENT | CF_SERVER, "prvm_garbagecol cvar_t prvm_stringdebug = {CF_CLIENT | CF_SERVER, "prvm_stringdebug", "0", "Print debug and warning messages related to strings"}; 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)."}; 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."}; +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. static double prvm_reuseedicts_always_allow = 0; qbool prvm_runawaycheck = true; @@ -3275,6 +3276,7 @@ void PRVM_Init (void) Cvar_RegisterVariable (&prvm_stringdebug); Cvar_RegisterVariable (&sv_entfields_noescapes); Cvar_RegisterVariable (&prvm_gameplayfix_div0is0); + Cvar_RegisterVariable (&prvm_gameplayfix_rintisround); // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!) prvm_runawaycheck = !Sys_CheckParm("-norunaway");