Skip to content

Commit 42cf5e9

Browse files
committedAug 1, 2015
Improve accuracy and safety of float serialization
Multiplying by a factor of 1/1000.f (rather than dividing by 1000.f) directly introduces an error of 1 ULP. With this patch, an exact comparison of a floating point literal with the deserialized F1000 form representing it is now guaranteed to be successful. In addition, the maxmium and minimum safely representible floating point numbers are now well-defined as constants.
1 parent bf991bd commit 42cf5e9

File tree

2 files changed

+21
-17
lines changed

2 files changed

+21
-17
lines changed
 

‎src/unittest/test_serialization.cpp

+8-15
Original file line numberDiff line numberDiff line change
@@ -296,29 +296,22 @@ void TestSerialization::testStreamRead()
296296
UASSERT(readS32(is) == -6);
297297
UASSERT(readS64(is) == -43);
298298

299-
UASSERT(fabs(readF1000(is) - 53.534f) < 0.005);
300-
UASSERT(fabs(readF1000(is) - -300000.32f) < 0.05);
301-
UASSERT(fabs(readF1000(is) - -2147483.f) < 0.05);
302-
UASSERT(fabs(readF1000(is) - 2147483.f) < 0.05);
299+
UASSERT(readF1000(is) == 53.534f);
300+
UASSERT(readF1000(is) == -300000.32f);
301+
UASSERT(readF1000(is) == F1000_MIN);
302+
UASSERT(readF1000(is) == F1000_MAX);
303303

304304
UASSERT(deSerializeString(is) == "foobar!");
305305

306306
UASSERT(readV2S16(is) == v2s16(500, 500));
307307
UASSERT(readV3S16(is) == v3s16(4207, 604, -30));
308308
UASSERT(readV2S32(is) == v2s32(1920, 1080));
309309
UASSERT(readV3S32(is) == v3s32(-400, 6400054, 290549855));
310-
311-
v2f vec2 = readV2F1000(is);
312-
UASSERT(fabs(vec2.X - 500.656f) < 0.005);
313-
UASSERT(fabs(vec2.Y - 350.345f) < 0.005);
310+
UASSERT(readV2F1000(is) == v2f(500.656f, 350.345f));
314311

315312
UASSERT(deSerializeWideString(is) == L"\x02~woof~\x5455");
316313

317-
v3f vec3 = readV3F1000(is);
318-
UASSERT(fabs(vec3.X - 500.f) < 0.005);
319-
UASSERT(fabs(vec3.Y - 10024.2f) < 0.005);
320-
UASSERT(fabs(vec3.Z - -192.54f) < 0.005);
321-
314+
UASSERT(readV3F1000(is) == v3f(500, 10024.2f, -192.54f));
322315
UASSERT(readARGB8(is) == video::SColor(255, 128, 50, 128));
323316

324317
UASSERT(deSerializeLongString(is) == "some longer string here");
@@ -346,8 +339,8 @@ void TestSerialization::testStreamWrite()
346339

347340
writeF1000(os, 53.53467f);
348341
writeF1000(os, -300000.32f);
349-
writeF1000(os, -2147483.f);
350-
writeF1000(os, 2147483.f);
342+
writeF1000(os, F1000_MIN);
343+
writeF1000(os, F1000_MAX);
351344

352345
os << serializeString("foobar!");
353346

‎src/util/serialize.h

+13-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
2121
#define UTIL_SERIALIZE_HEADER
2222

2323
#include "../irrlichttypes_bloated.h"
24+
#include "../debug.h" // for assert
2425
#include "config.h"
2526
#if HAVE_ENDIAN_H
2627
#include <endian.h>
@@ -30,7 +31,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
3031
#include <string>
3132

3233
#define FIXEDPOINT_FACTOR 1000.0f
33-
#define FIXEDPOINT_INVFACTOR (1.0f/FIXEDPOINT_FACTOR)
34+
35+
// 0x7FFFFFFF / 1000.0f is not serializable.
36+
// The limited float precision at this magnitude may cause the result to round
37+
// to a greater value than can be represented by a 32 bit integer when increased
38+
// by a factor of FIXEDPOINT_FACTOR. As a result, [F1000_MIN..F1000_MAX] does
39+
// not represent the full range, but rather the largest safe range, of values on
40+
// all supported architectures. Note: This definition makes assumptions on
41+
// platform float-to-int conversion behavior.
42+
#define F1000_MIN ((float)(s32)((-0x7FFFFFFF - 1) / FIXEDPOINT_FACTOR))
43+
#define F1000_MAX ((float)(s32)((0x7FFFFFFF) / FIXEDPOINT_FACTOR))
3444

3545
#define STRING_MAX_LEN 0xFFFF
3646
#define WIDE_STRING_MAX_LEN 0xFFFF
@@ -163,7 +173,7 @@ inline s64 readS64(const u8 *data)
163173

164174
inline f32 readF1000(const u8 *data)
165175
{
166-
return (f32)readS32(data) * FIXEDPOINT_INVFACTOR;
176+
return (f32)readS32(data) / FIXEDPOINT_FACTOR;
167177
}
168178

169179
inline video::SColor readARGB8(const u8 *data)
@@ -252,6 +262,7 @@ inline void writeS64(u8 *data, s64 i)
252262

253263
inline void writeF1000(u8 *data, f32 i)
254264
{
265+
assert(i >= F1000_MIN && i <= F1000_MAX);
255266
writeS32(data, i * FIXEDPOINT_FACTOR);
256267
}
257268

0 commit comments

Comments
 (0)
Please sign in to comment.