Skip to content

Commit

Permalink
Network: Send IEEE floats (#7768)
Browse files Browse the repository at this point in the history
  • Loading branch information
SmallJoker authored and nerzhul committed Dec 13, 2018
1 parent 8471d02 commit 839e935
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 12 deletions.
12 changes: 6 additions & 6 deletions src/client/content_cao.cpp
Expand Up @@ -1347,19 +1347,19 @@ void GenericCAO::processMessage(const std::string &data)
} else if (cmd == GENERIC_CMD_UPDATE_POSITION) {
// Not sent by the server if this object is an attachment.
// We might however get here if the server notices the object being detached before the client.
m_position = readV3F1000(is);
m_velocity = readV3F1000(is);
m_acceleration = readV3F1000(is);
m_position = readV3F32(is);
m_velocity = readV3F32(is);
m_acceleration = readV3F32(is);

if (std::fabs(m_prop.automatic_rotate) < 0.001f)
m_rotation = readV3F1000(is);
m_rotation = readV3F32(is);
else
readV3F1000(is);
readV3F32(is);

m_rotation = wrapDegrees_0_360_v3f(m_rotation);
bool do_interpolate = readU8(is);
bool is_end_position = readU8(is);
float update_interval = readF1000(is);
float update_interval = readF32(is);

// Place us a bit higher if we're physical, to not sink into
// the ground due to sucky collision detection...
Expand Down
10 changes: 5 additions & 5 deletions src/genericobject.cpp
Expand Up @@ -49,19 +49,19 @@ std::string gob_cmd_update_position(
// command
writeU8(os, GENERIC_CMD_UPDATE_POSITION);
// pos
writeV3F1000(os, position);
writeV3F32(os, position);
// velocity
writeV3F1000(os, velocity);
writeV3F32(os, velocity);
// acceleration
writeV3F1000(os, acceleration);
writeV3F32(os, acceleration);
// rotation
writeV3F1000(os, rotation);
writeV3F32(os, rotation);
// do_interpolate
writeU8(os, do_interpolate);
// is_end_position (for interpolation)
writeU8(os, is_movement_end);
// update_interval (for interpolation)
writeF1000(os, update_interval);
writeF32(os, update_interval);
return os.str();
}

Expand Down
1 change: 1 addition & 0 deletions src/network/networkprotocol.h
Expand Up @@ -191,6 +191,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
PROTOCOL VERSION 37:
Redo detached inventory sending
Add TOCLIENT_NODEMETA_CHANGED
New network float format
*/

#define LATEST_PROTOCOL_VERSION 37
Expand Down
72 changes: 72 additions & 0 deletions src/unittest/test_serialization.cpp
Expand Up @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

#include "util/string.h"
#include "util/serialize.h"
#include <cmath>

class TestSerialization : public TestBase {
public:
Expand All @@ -43,6 +44,7 @@ class TestSerialization : public TestBase {
void testVecPut();
void testStringLengthLimits();
void testBufReader();
void testFloatFormat();

std::string teststring2;
std::wstring teststring2_w;
Expand Down Expand Up @@ -70,6 +72,7 @@ void TestSerialization::runTests(IGameDef *gamedef)
TEST(testVecPut);
TEST(testStringLengthLimits);
TEST(testBufReader);
TEST(testFloatFormat);
}

////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -631,6 +634,75 @@ void TestSerialization::testBufReader()
UASSERT(!buf.getRawDataNoEx(raw_data, sizeof(raw_data)));
}

void TestSerialization::testFloatFormat()
{
FloatType type = getFloatSerializationType();
u32 i;
f32 fs, fm;

// Check precision of float calculations on this platform
const std::unordered_map<f32, u32> float_results = {
{ 0.0f, 0x00000000UL },
{ 1.0f, 0x3F800000UL },
{ -1.0f, 0xBF800000UL },
{ 0.1f, 0x3DCCCCCDUL },
{ -0.1f, 0xBDCCCCCDUL },
{ 1945329.25f, 0x49ED778AUL },
{ -23298764.f, 0xCBB1C166UL },
{ 0.5f, 0x3F000000UL },
{ -0.5f, 0xBF000000UL }
};
for (const auto &v : float_results) {
i = f32Tou32Slow(v.first);
if (std::abs((s64)v.second - i) > 32) {
printf("Inaccurate float values on %.9g, expected 0x%X, actual 0x%X\n",
v.first, v.second, i);
UASSERT(false);
}

fs = u32Tof32Slow(v.second);
if (std::fabs(v.first - fs) > std::fabs(v.first * 0.000005f)) {
printf("Inaccurate float values on 0x%X, expected %.9g, actual 0x%.9g\n",
v.second, v.first, fs);
UASSERT(false);
}
}

if (type == FLOATTYPE_SLOW) {
// conversion using memcpy is not possible
// Skip exact float comparison checks below
return;
}

auto test_single = [&fs, &fm](const u32 &i) -> bool {
memcpy(&fm, &i, 4);
fs = u32Tof32Slow(i);
if (fm != fs) {
printf("u32Tof32Slow failed on 0x%X, expected %.9g, actual %.9g\n",
i, fm, fs);
return false;
}
if (f32Tou32Slow(fs) != i) {
printf("f32Tou32Slow failed on %.9g, expected 0x%X, actual 0x%X\n",
fs, i, f32Tou32Slow(fs));
return false;
}
return true;
};

// Use step of prime 277 to speed things up from 3 minutes to a few seconds
// Test from 0 to 0xFF800000UL (positive)
for (i = 0x00000000UL; i <= 0x7F800000UL; i += 277)
UASSERT(test_single(i));

// Ensure +inf and -inf are tested
UASSERT(test_single(0x7F800000UL));
UASSERT(test_single(0xFF800000UL));

// Test from 0x80000000UL to 0xFF800000UL (negative)
for (i = 0x80000000UL; i <= 0xFF800000UL; i += 277)
UASSERT(test_single(i));
}

const u8 TestSerialization::test_serialized_data[12 * 13] = {
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc,
Expand Down
1 change: 1 addition & 0 deletions src/util/CMakeLists.txt
Expand Up @@ -4,6 +4,7 @@ set(UTIL_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp
${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp
${CMAKE_CURRENT_SOURCE_DIR}/enriched_string.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ieee_float.cpp
${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serialize.cpp
Expand Down
136 changes: 136 additions & 0 deletions src/util/ieee_float.cpp
@@ -0,0 +1,136 @@
/*
* Conversion of f32 to IEEE-754 and vice versa.
*
* © Copyright 2018 Pedro Gimeno Fortea.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "ieee_float.h"
#include "log.h"
#include "porting.h"
#include <limits>
#include <cmath>

// Given an unsigned 32-bit integer representing an IEEE-754 single-precision
// float, return the float.
f32 u32Tof32Slow(u32 i)
{
// clang-format off
int exp = (i >> 23) & 0xFF;
u32 sign = i & 0x80000000UL;
u32 imant = i & 0x7FFFFFUL;
if (exp == 0xFF) {
// Inf/NaN
if (imant == 0) {
if (std::numeric_limits<f32>::has_infinity)
return sign ? -std::numeric_limits<f32>::infinity() :
std::numeric_limits<f32>::infinity();
return sign ? std::numeric_limits<f32>::max() :
std::numeric_limits<f32>::lowest();
}
return std::numeric_limits<f32>::has_quiet_NaN ?
std::numeric_limits<f32>::quiet_NaN() : -0.f;
}

if (!exp) {
// Denormal or zero
return sign ? -ldexpf((f32)imant, -149) : ldexpf((f32)imant, -149);
}

return sign ? -ldexpf((f32)(imant | 0x800000UL), exp - 150) :
ldexpf((f32)(imant | 0x800000UL), exp - 150);
// clang-format on
}

// Given a float, return an unsigned 32-bit integer representing the f32
// in IEEE-754 single-precision format.
u32 f32Tou32Slow(f32 f)
{
u32 signbit = std::copysign(1.0f, f) == 1.0f ? 0 : 0x80000000UL;
if (f == 0.f)
return signbit;
if (std::isnan(f))
return signbit | 0x7FC00000UL;
if (std::isinf(f))
return signbit | 0x7F800000UL;
int exp;
f32 mant = frexpf(f, &exp);
u32 imant = (u32)std::floor((signbit ? -16777216.f : 16777216.f) * mant);
exp += 126;
if (exp <= 0) {
// Denormal
return signbit | (exp <= -31 ? 0 : imant >> (1 - exp));
}

if (exp >= 255) {
// Overflow due to the platform having exponents bigger than IEEE ones.
// Return signed infinity.
return signbit | 0x7F800000UL;
}

// Regular number
return signbit | (exp << 23) | (imant & 0x7FFFFFUL);
}

// This test needs the following requisites in order to work:
// - The float type must be a 32 bits IEEE-754 single-precision float.
// - The endianness of f32s and integers must match.
FloatType getFloatSerializationType()
{
// clang-format off
const f32 cf = -22220490.f;
const u32 cu = 0xCBA98765UL;
if (std::numeric_limits<f32>::is_iec559 && sizeof(cf) == 4 &&
sizeof(cu) == 4 && !memcmp(&cf, &cu, 4)) {
// u32Tof32Slow and f32Tou32Slow are not needed, use memcpy
return FLOATTYPE_SYSTEM;
}

// Run quick tests to ensure the custom functions provide acceptable results
warningstream << "floatSerialization: f32 and u32 endianness are "
"not equal or machine is not IEEE-754 compliant" << std::endl;
u32 i;
char buf[128];

// NaN checks aren't included in the main loop
if (!std::isnan(u32Tof32Slow(0x7FC00000UL))) {
porting::mt_snprintf(buf, sizeof(buf),
"u32Tof32Slow(0x7FC00000) failed to produce a NaN, actual: %.9g",
u32Tof32Slow(0x7FC00000UL));
infostream << buf << std::endl;
}
if (!std::isnan(u32Tof32Slow(0xFFC00000UL))) {
porting::mt_snprintf(buf, sizeof(buf),
"u32Tof32Slow(0xFFC00000) failed to produce a NaN, actual: %.9g",
u32Tof32Slow(0xFFC00000UL));
infostream << buf << std::endl;
}

i = f32Tou32Slow(std::numeric_limits<f32>::quiet_NaN());
// check that it corresponds to a NaN encoding
if ((i & 0x7F800000UL) != 0x7F800000UL || (i & 0x7FFFFFUL) == 0) {
porting::mt_snprintf(buf, sizeof(buf),
"f32Tou32Slow(NaN) failed to encode NaN, actual: 0x%X", i);
infostream << buf << std::endl;
}

return FLOATTYPE_SLOW;
// clang-format on
}
34 changes: 34 additions & 0 deletions src/util/ieee_float.h
@@ -0,0 +1,34 @@
/*
Minetest
Copyright (C) 2018 SmallJoker <mk939@ymail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#pragma once

#include "irrlichttypes.h"

enum FloatType
{
FLOATTYPE_UNKNOWN,
FLOATTYPE_SLOW,
FLOATTYPE_SYSTEM
};

f32 u32Tof32Slow(u32 i);
u32 f32Tou32Slow(f32 f);

FloatType getFloatSerializationType();
2 changes: 2 additions & 0 deletions src/util/serialize.cpp
Expand Up @@ -28,6 +28,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <iomanip>
#include <vector>

FloatType g_serialize_f32_type = FLOATTYPE_UNKNOWN;

////
//// BufReader
////
Expand Down

0 comments on commit 839e935

Please sign in to comment.