torquescript math routines for large integers
by Orion Elenzil · 04/17/2009 (3:20 pm) · 17 comments
try the following in torqueScript:
echo(10000000 + 1);
the result is "1e+007".
when doing math, torquescript pleasantly converts large integers to floats, which then get represented as scientific notation, and goodbye, precision. some time ago Peter Simard made some routines to do proper integer arithmetic. i can't find his original code now, because i chose different names for the console functions, and it must have been in a forum post rather than as a resource. so thanks, Peter!
example:
in mConsoleFunctions.cc, right before ConsoleFunctionGroupEnd( GeneralMath );, insert:
echo(10000000 + 1);
the result is "1e+007".
when doing math, torquescript pleasantly converts large integers to floats, which then get represented as scientific notation, and goodbye, precision. some time ago Peter Simard made some routines to do proper integer arithmetic. i can't find his original code now, because i chose different names for the console functions, and it must have been in a forum post rather than as a resource. so thanks, Peter!
example:
echo(10000000 + 1); 1e+007 echo(mAddS32(10000000, 1)); 10000001
in mConsoleFunctions.cc, right before ConsoleFunctionGroupEnd( GeneralMath );, insert:
// thanks to Peter Simard for the following
ConsoleFunction( mAddS32, const char*, 3, 3, "Add 2 large numbers" )
{
S32 v1 = dAtoi(argv[1]);
S32 v2 = dAtoi(argv[2]);
S32 res = v1 + v2;
char* ret = Con::getReturnBuffer(64);
dSprintf(ret, 64, "%i", res);
return ret;
}
ConsoleFunction( mSubS32, const char*, 3, 3, "Subtract 2 large numbers" )
{
S32 v1 = dAtoi(argv[1]);
S32 v2 = dAtoi(argv[2]);
S32 res = v1 - v2;
char* ret = Con::getReturnBuffer(64);
dSprintf(ret, 64, "%i", res);
return ret;
}
ConsoleFunction( mMulS32, const char*, 3, 3, "Multiply 2 large numbers" )
{
S32 v1 = dAtoi(argv[1]);
S32 v2 = dAtoi(argv[2]);
S32 res = v1 * v2;
char* ret = Con::getReturnBuffer(64);
dSprintf(ret, 64, "%i", res);
return ret;
}
ConsoleFunction( mDivS32, const char*, 3, 3, "Divide 2 large numbers" )
{
S32 v1 = dAtoi(argv[1]);
S32 v2 = dAtoi(argv[2]);
S32 res = v1 / v2;
char* ret = Con::getReturnBuffer(64);
dSprintf(ret, 64, "%i", res);
return ret;
}About the author
#2
04/17/2009 (11:02 pm)
hey Wes! that's very cool! this issue has been floated (ho ho) numerous times and it's always looked like a major pita to do anything about it, but your change seems very straight-forward. i'll give it a shot monday.
#3
Still suffers the 2^31-1 limit on either side of operators, I will look into that next. Also still only tested on Windows so far.
Note: Hit a 1000 word limit. So here is part 1.
04/19/2009 (12:06 am)
I decided to add support for storing the 64 bit float as a script variable and added a TypeF64 to expose F64 variables to script for C++ classes. This time I had to change a few files so it's easier to give out a patch. This includes the fix from above.Still suffers the 2^31-1 limit on either side of operators, I will look into that next. Also still only tested on Windows so far.
Note: Hit a 1000 word limit. So here is part 1.
Index: engine/console/compiledEval.cc
===================================================================
--- engine/console/compiledEval.cc (revision 2053)
+++ engine/console/compiledEval.cc (working copy)
@@ -97,9 +97,18 @@
char *getFloatArg(F64 arg)
{
+ char *ret = STR.getArgBuffer(32);
+ dSprintf(ret, 32, "%.7f", arg);
- char *ret = STR.getArgBuffer(32);
- dSprintf(ret, 32, "%g", arg);
+ U32 len = dStrlen(ret);
+ while (ret[len-1] == '0')
+ len--;
+
+ if (ret[len-1] == '.')
+ len--;
+
+ ret[len] = 0;
+
return ret;
}
Index: engine/console/compiler.cc
===================================================================
--- engine/console/compiler.cc (revision 2053)
+++ engine/console/compiler.cc (working copy)
@@ -163,7 +163,16 @@
U32 CompilerStringTable::addFloatString(F64 value)
{
- dSprintf(buf, sizeof(buf), "%g", value);
+ dSprintf(buf, sizeof(buf), "%.7f", value);
+
+ U32 len = dStrlen(buf);
+ while (buf[len-1] == '0')
+ len--;
+
+ if (buf[len-1] == '.')
+ len--;
+
+ buf[len] = 0;
return add(buf);
}
void CompilerStringTable::reset()
Index: engine/console/consoleInternal.h
===================================================================
--- engine/console/consoleInternal.h (revision 2053)
+++ engine/console/consoleInternal.h (working copy)
@@ -152,7 +152,7 @@
S32 type;
char *sval;
U32 ival; // doubles as strlen when type = -1
- F32 fval;
+ F64 fval;
U32 bufferLen;
void *dataPtr;
@@ -166,19 +166,19 @@
else
return dAtoi(Con::getData(type, dataPtr, 0));
}
- F32 getFloatValue()
+ F64 getFloatValue()
{
if(type <= TypeInternalString)
return fval;
else
- return dAtof(Con::getData(type, dataPtr, 0));
+ return dAtof64(Con::getData(type, dataPtr, 0));
}
const char *getStringValue()
{
if(type == TypeInternalString)
return sval;
if(type == TypeInternalFloat)
- return Con::getData(TypeF32, &fval, 0);
+ return Con::getData(TypeF64, &fval, 0);
else if(type == TypeInternalInt)
return Con::getData(TypeS32, &ival, 0);
else
@@ -188,7 +188,7 @@
{
if(type <= TypeInternalString)
{
- fval = (F32)val;
+ fval = static_cast<F64>(val);
ival = val;
if(sval != typeValueEmpty)
{
@@ -204,7 +204,7 @@
Con::setData(type, dataPtr, 0, 1, &dptr);
}
}
- void setFloatValue(F32 val)
+ void setFloatValue(F64 val)
{
if(type <= TypeInternalString)
{
@@ -220,7 +220,7 @@
}
else
{
- const char *dptr = Con::getData(TypeF32, &val, 0);
+ const char *dptr = Con::getData(TypeF64, &val, 0);
Con::setData(type, dataPtr, 0, 1, &dptr);
}
}
#4
Edit: Changed line 32 to F64* not F32*
04/19/2009 (12:07 am)
Part 2.Index: engine/console/consoleTypes.cc
===================================================================
--- engine/console/consoleTypes.cc (revision 2053)
+++ engine/console/consoleTypes.cc (working copy)
@@ -279,6 +279,35 @@
}
//////////////////////////////////////////////////////////////////////////
+// TypeF64
+//////////////////////////////////////////////////////////////////////////
+ConsoleType( float, TypeF64, sizeof(F64) )
+
+ConsoleGetType( TypeF64 )
+{
+ char* returnBuffer = Con::getReturnBuffer(256);
+ dSprintf(returnBuffer, 256, "%.7f", *((F64 *) dptr) );
+
+ U32 len = dStrlen(returnBuffer);
+ while (returnBuffer[len-1] == '0')
+ len--;
+
+ if (returnBuffer[len-1] == '.')
+ len--;
+
+ returnBuffer[len] = 0;
+
+ return returnBuffer;
+}
+ConsoleSetType( TypeF64 )
+{
+ if(argc == 1)
+ *((F64 *) dptr) = dAtof64(argv[0]);
+ else
+ Con::printf("(TypeF64) Cannot set multiple args to a single F64.");
+}
+
+//////////////////////////////////////////////////////////////////////////
// TypeF32Vector
//////////////////////////////////////////////////////////////////////////
ConsoleType( floatList, TypeF32Vector, sizeof(Vector<F32>) )
Index: engine/console/consoleTypes.h
===================================================================
--- engine/console/consoleTypes.h (revision 2053)
+++ engine/console/consoleTypes.h (working copy)
@@ -20,6 +20,7 @@
// Define Core Console Types
DefineConsoleType( TypeF32 )
+DefineConsoleType( TypeF64 )
DefineConsoleType( TypeS8 )
DefineConsoleType( TypeS32 )
DefineConsoleType( TypeS32Vector )
Index: engine/console/stringStack.h
===================================================================
--- engine/console/stringStack.h (revision 2053)
+++ engine/console/stringStack.h (working copy)
@@ -80,8 +80,18 @@
void setFloatValue(F64 v)
{
validateBufferSize(mStart + 32);
- dSprintf(mBuffer + mStart, 32, "%g", v);
- mLen = dStrlen(mBuffer + mStart);
+ dSprintf(mBuffer + mStart, 32, "%.7f", v);
+
+ U32 len = dStrlen(mBuffer + mStart);
+ char *string = mBuffer + mStart;
+ while (string[len-1] == '0')
+ len--;
+
+ if (string[len-1] == '.')
+ len--;
+
+ string[len] = 0;
+ mLen = len;
}
/// Return a temporary buffer we can use to return data.
Index: engine/platform/platform.h
===================================================================
--- engine/platform/platform.h (revision 2053)
+++ engine/platform/platform.h (working copy)
@@ -496,6 +496,7 @@
extern int dAtoi(const char *str);
extern float dAtof(const char *str);
+extern double dAtof64(const char *str);
extern bool dAtob(const char *str);
extern unsigned int dAtoui(const char *str);
extern S64 dAtoi64(const char *str);
Index: engine/platformWin32/winStrings.cc
===================================================================
--- engine/platformWin32/winStrings.cc (revision 2053)
+++ engine/platformWin32/winStrings.cc (working copy)
@@ -247,6 +247,12 @@
return (F32)atof(str);
}
+F64 dAtof64(const char *str)
+{
+ // Warning: metrowerks crashes when strange strings are passed in '0x [enter]' for example!
+ return atof(str);
+}
+
bool dAtob(const char *str)
{
return !dStricmp(str, "true") || dAtof(str);Edit: Changed line 32 to F64* not F32*
#5
i would propose:
04/20/2009 (11:03 am)
hey wes, i'm giving this a shot and think i see a potential bug - won't the various sections to truncate trailing zeros truncate "100" to "1" ?i would propose:
char* truncateFloatString(char* string)
{
char* p = string + dStrlen(string) - 1;
while (*p == '0')
{
p--;
}
if (*p == '.')
{
*p = 0;
}
return string;
}
#6
the modifications were a bit different for TGE 1.3.5,
so if anyone's interested in those i'll be happy to post.
imo this should totally go into trunk.
note,
be sure to implement dAtof64 in x86UNIXStrings.cc and macCarbStrings.cc as well!
04/20/2009 (11:18 am)
hokay, have compiled this in and it seems to work like a charm. nice one wes!the modifications were a bit different for TGE 1.3.5,
so if anyone's interested in those i'll be happy to post.
imo this should totally go into trunk.
note,
be sure to implement dAtof64 in x86UNIXStrings.cc and macCarbStrings.cc as well!
#7
Good call forgot about that.
04/20/2009 (12:17 pm)
> be sure to implement dAtof64 in x86UNIXStrings.cc and macCarbStrings.cc as well! Good call forgot about that.
#8
Yes if there is no decimal point. So far as I have seen the "%.7f" in the sprintf guarantees a decimal value to seven decimal places. If that were to change then code needs to also. The difference with yours is that you will keep zeros in the case of short decimals.
Example: 1.0100000
Original: 1.01
truncateFloatString: 1.0100000
04/20/2009 (12:30 pm)
>won't the various sections to truncate trailing zeros truncate "100" to "1" ?Yes if there is no decimal point. So far as I have seen the "%.7f" in the sprintf guarantees a decimal value to seven decimal places. If that were to change then code needs to also. The difference with yours is that you will keep zeros in the case of short decimals.
Example: 1.0100000
Original: 1.01
truncateFloatString: 1.0100000
#9
04/20/2009 (12:44 pm)
I did find a bug though on line 32 of the second part of the patch should be *((F64 *) dptr) = dAtof64(argv[0]);
#10
Example:
Irrelevant if all your numbers are in variables.
04/20/2009 (1:13 pm)
A fix for the parser so it supports larger numbers in the script. You have to force it to use a float to get it to support the 64 bit number.--- engine/console/CMDscan.cc 2009/04/20 19:52:57 2059
+++ engine/console/CMDscan.cc 2009/04/20 20:07:56 2060
@@ -2408,7 +2408,7 @@
static int Sc_ScanNum()
{
CMDtext[CMDleng] = 0;
- CMDlval.f = dAtof(CMDtext);
+ CMDlval.f = dAtof64(CMDtext);
return(FLTCONST);
}Example:
==>echo(2147483647 + 1); 2147483648 ==>echo(2147483648 + 1); 2147483648 ==>echo(2147483647.0 + 1); 2147483648 ==>echo(2147483648.0 + 1); 2147483649
Irrelevant if all your numbers are in variables.
#11
doh, i should have seen that.
for those following along, here's the corrected version:
04/20/2009 (1:40 pm)
> truncateFloatString: 1.0100000 doh, i should have seen that.
for those following along, here's the corrected version:
char* truncateFloatString(char* string)
{
AssertFatal(dStrchr(string, '.') != NULL, "truncateFloatString() - must include a decimal point.");
char* p = string + dStrlen(string) - 1;
while (*p == '0')
{
p--;
}
if (*p == '.')
{
p--;
}
*(p + 1) = '[[60c1f982386e1]]';
return string;
}
#12
04/22/2009 (8:17 am)
Very interesting. But just to be sure... These code changes allow the usage of 32 bit signed / unsigned numbers (S32 / U32) in Torque Script without problems, right?
#13
It does allow you to accept F64 values in the engine and those do cover well over the values of a U32 (technically over the values of U64 as well but with less precision.)
If the engine functions you are using expect a string then you can use the full range of the F64 supported in script.
The most important fix is that the script can now support values higher than 10 million or what ever it used to be when it went into scientific notation and those will work with S32. It's only the real big numbers you have to be careful with, but they are now supported.
04/22/2009 (4:49 pm)
Not exactly it allows you to use numbers much higher than U32, but if you try and use them with functions exposed by the engine that are expecting an S32 you're going to overflow it.It does allow you to accept F64 values in the engine and those do cover well over the values of a U32 (technically over the values of U64 as well but with less precision.)
If the engine functions you are using expect a string then you can use the full range of the F64 supported in script.
The most important fix is that the script can now support values higher than 10 million or what ever it used to be when it went into scientific notation and those will work with S32. It's only the real big numbers you have to be careful with, but they are now supported.
#14
04/22/2009 (10:28 pm)
Thanks a lot for explaining Wes.
#15
03/19/2010 (1:48 pm)
Any updates on how to get this to work in T3D?
#16
Wes's fix is much more transparent, but i'm not sure if it'll integrate seamlessly. have you tried ?
03/19/2010 (2:09 pm)
the mAddS32() et cetera functions should port to T3D no problem.Wes's fix is much more transparent, but i'm not sure if it'll integrate seamlessly. have you tried ?
#17
C:\torque\SDK\engine\console\compiledEval.cc(102): //XXTH2 dSprintf(ret, 32, "%g", arg);
C:\torque\SDK\engine\console\compiler.cc(166): //XXTH2 dSprintf(buf, sizeof(buf), "%g", value);
C:\torque\SDK\engine\console\consoleTypes.cc(187): //XXTH2 dSprintf(returnBuffer, 256, "%g", *((F32 *) dptr) );
C:\torque\SDK\engine\console\stringStack.h(83): //XXTH2 dSprintf(mBuffer + mStart, 32, "%g", v);
There was a post about it, ages ago in TGB Forum.
07/05/2010 (6:56 am)
If have simply changed the "%g" into "%.9g" to get larger numbers on the following locations:C:\torque\SDK\engine\console\compiledEval.cc(102): //XXTH2 dSprintf(ret, 32, "%g", arg);
C:\torque\SDK\engine\console\compiler.cc(166): //XXTH2 dSprintf(buf, sizeof(buf), "%g", value);
C:\torque\SDK\engine\console\consoleTypes.cc(187): //XXTH2 dSprintf(returnBuffer, 256, "%g", *((F32 *) dptr) );
C:\torque\SDK\engine\console\stringStack.h(83): //XXTH2 dSprintf(mBuffer + mStart, 32, "%g", v);
There was a post about it, ages ago in TGB Forum.

Torque Owner Wes Macdonald
I found that Torque uses a 64-bit float table to hold those numbers. It seems that if you remove the code that converts from a float to scientific notation you can get very good precision at high numbers. I'm still working on this but it appears to be a start.
A few notes, it appears you cannot use a number larger than 2147483647 (2^31-1) (the limit of S32) on each side of an operator although that might be fixable. Also if you store the value into a variable it goes into scientific notation, also probably fixable. The in game editors and other places still use %g with scanf so that might break as well. Also only tested on Windows so far.
To remove the scientific notation change the setFloatValue function in stringStack.h to (around line 80 in 1.5.1):
void setFloatValue(F64 v) { validateBufferSize(mStart + 32); dSprintf(mBuffer + mStart, 32, "%.7f", v); U32 len = dStrlen(mBuffer + mStart); char *string = mBuffer + mStart; while (string[len-1] == '0') len--; if (string[len-1] == '.') len--; string[len] = 0; mLen = len; }