Floating-Point Notes

The entire field of 3D graphics is inherently connected with calculations on floating-point numbers. You should be aware, that it can be a bit tricky to work with such values (or rather variables of such type). In this article I'll begin with some Cinema 4D SDK basics, before showing typical floating-point pitfalls in the end.

Special care needs to be taken, when

• converting `Float` datatypes into integer datatypes
• comparing `Float` datatypes
• calculating with `Float` datatypes

Before diving into these oddities, some basics and convenience functions should be noted.

# Cinema 4D Datatypes

Note
Actually this note is not limited to floating-point. Yet, it should be noted here, that you are advised to Cinema 4D's datatypes, unless you absolutely need other types to communicate with an absolutely needed external library.

# Limits

In case you need to have the minimum or maximum values of a Float datatype you should use this syntax:

• `LIMIT<datatype>::Min()`
• `LIMIT<datatype>::Max()`

Like so:

Maximum Value of Float Datatype

```const Float32 FMAX32 = LIMIT<Float32>::Max();
const Float64 FMAX64 = LIMIT<Float64>::Max();
```
Note
This is different for integer datatypes! There the syntax looks like this: `LIMIT<datatype>::MIN` and `LIMIT<datatype>::MAX`.

# Inverse

Beginning with Cinema 4D R15 there's a convenience function `Inverse()` to get the inverse of a `Float` datatype, which already cares for division by zero. In which case it will return zero.

# Safely Convert Float-Point into Integer

When assigning `Float` values to integer variables, simply use the following macros to be on the safe side:

Cinema 4D < R15Cinema 4D ≥ R15
`SAFELONG()``SAFEINT32(), SAFEINT64(), SAFEINT()`

Like so:

Safe Float Conversion

Float fValue = (Float)5.3;
Int32 iValue;
iValue = SAFEINT32(fValue); // use explicit cast macros to convert from Float to int
// of course the other way round is simple and perfectly fine
fValue = (Float)iValue;
Note
It is good practice to explicitly mark `Float` constants with a cast. By this you can be sure, the constant has the desired precision and it prevents falling into pitfalls like compiler oddities or redundant conversion code generated by typos.

While using the above cast macros is perfectly safe and fine, you should never use a pointer to convert a Float into an Int or vice versa.

Never ever!

You never know, if the variable is currently stored within an register of your CPU and in this case such code fails big time. The search for such a bug can easily cost you a day.

void DontConvertFloatLikeThis(const UInt32* const pIntValue, const Float* const pFloatValue)
{
// NEITHER DON'T DO SOMETHING LIKE THIS
Float fValue = *(Float*)pIntValue; // DO NOT TRY TO CONVERT AN INT INTO A FLOAT BY CASTING A POINTER!!!
// NOR DO IT THE OTHER WAY ROUND
Int32 iValue = *(Int32*)pFloatValue; // DO NOT TRY TO CONVERT A FLOAT INTO AN INT BY CASTING A POINTER!!!
// NEVER EVER!
}

# Safely Compare Floating-Point on Equality

Always use tolerant comparisons! Use of less (`<`), less equal (`≤`), greater (`>`) and greater equal (`≥`) is fine. But for any comparison on equality use `CompareFloatTolerant()` instead of a simple equality operator (`==`).

Like so:

Safe Float Comparison

const Float FVALUE= (Float)0.2 + (Float)0.1;
if (CompareFloatTolerant(FVALUE, (Float)0.3))
{
GePrint("Working fine, the values are equal.");
}

And never like so:

// DON'T COMPARE FLOATS ON EQUALITY WITH EQUALITY OPERATOR
const Float FVALUE = (Float)0.2 + (Float)0.1;
if (FVALUE == (Float)0.3)
{
GePrint("There are cases it may work...");
}
else
{
GePrint("...but, ooops, most likely it won't. The values are different!");
}

# Floating-Point Weirdness

Always be aware, that math known from school might not hold with floating-point calculations. Some examples, I just saw neatly collected on a German computer magazine. This is no buggy behavior, just completely normal implications of the floating-point number format:

Float Weirdness #1

const Float64 A = (Float64)1000.0;
const Float64 B = (Float64)1e-14; // this value is still very well in the range of Float64
if ((A + B) == A)
{
GePrint("Ooops, probably not, what you expected, A equals (A+B)"); // THIS GET'S PRINTED
}
else
{
GePrint("Yeah, sure, A and (A+B) are different.");
}
// ok, then let's try to add a thousand B's to A
Float64 c = A;
for (Int32 idx = 0; idx < 1000; ++idx)
{
c += B;
}
if (c == A)
{
GePrint("Ooops, still not, what you expected, A equals (A+1000*B)"); // THIS GET'S PRINTED
}
else
{
GePrint("Now that we added b a thousand times, these had to be different.");
}
// now, let's change the order of calculation
Float64 d = (Float64)0.0;
for (Int32 idx = 0; idx < 1000; ++idx)
{
d += B;
}
d += A;
if (d == A)
{
GePrint("Got it, same calculation as above, same result.");
}
else
{
GePrint("Ooops, now they are different? A is not equal (1000*B+A)"); // THIS GET'S PRINTED
}

Let's try another one, this time subtraction. Again, no bugs shown, just the day to day floating-point madness:

Float Weirdness #2

const Float32 A = (Float32)2.0;
const Float32 B = (Float32)2.0000001;
GePrint("B - A = " + String::FloatToString(B - A, 1, 9, true));
// prints: B - A = 0.000000000E+000
const Float32 C = (Float32)0.2;
const Float32 D = (Float32)0.2000001;
GePrint("D - C = " + String::FloatToString(D - C, 1, 9, true));
// prints: D - C = 1.043081284E-007
const Float32 E = (Float32)0.0000001;
const Float32 F = (Float32)5.0;
GePrint("F - E = " + String::FloatToString(F - E, 1, 9, true));
// prints: F - E = 5.000000000E+000

You may think, oh my god, the old Intel bug is back. Yet, it is not, your machine is working fine. Just keep in the back of your head, that calculating with `Float` datatypes can get tricky and especially, when comparing them, you should remember the above mentioned rule. And yes, in the second example I cheated a bit, using `Float32`, but trust me, you can have the same effect with `Float64`.

# Floating-Point Exceptions

By default certain floating-point exceptions, like division by zero, are disabled in Cinema 4D, for good reason I may add. Yet, you may want to check for these in your code. On a per thread basis you can use `GeDebugSetFloatingPointChecks()` to temporarily(!) enable floating-point exceptions. Needless to say, that you should do so only in debug builds, your release builds have to be free of floating-point exceptions anyway!

Attention
Once more:
Do not enable floating-point exceptions in a plugin release. For users this is equivalent to any other crash and his data/work will be lost. Get your exceptions fixed before you release.

# Importing Floating-Point Data

Suppose you are reading floating-point data from external files. There are cases you may not know, that this data is valid. You can use `CheckFloat()` to check the read values or even `RepairFloat()` to restore the integrity of such values. In the later case you should be aware, that `RepairFloat()` will return a valid floating-point value in regards to IEEE-754 standard, only. `RepairFloat()` is not able to regain any data lost due to for example file corruption.