From 839e935ba0572c592a791cc4dd4df4a9f6d2d260 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Thu, 13 Dec 2018 11:20:57 +0100 Subject: Network: Send IEEE floats (#7768) --- src/util/CMakeLists.txt | 1 + src/util/ieee_float.cpp | 136 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/ieee_float.h | 34 ++++++++++++ src/util/serialize.cpp | 2 + src/util/serialize.h | 61 +++++++++++++++++++++- 5 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 src/util/ieee_float.cpp create mode 100644 src/util/ieee_float.h (limited to 'src/util') diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index f571ab22c..bf208693b 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -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 diff --git a/src/util/ieee_float.cpp b/src/util/ieee_float.cpp new file mode 100644 index 000000000..e7909360f --- /dev/null +++ b/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 +#include + +// 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::has_infinity) + return sign ? -std::numeric_limits::infinity() : + std::numeric_limits::infinity(); + return sign ? std::numeric_limits::max() : + std::numeric_limits::lowest(); + } + return std::numeric_limits::has_quiet_NaN ? + std::numeric_limits::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::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::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 +} diff --git a/src/util/ieee_float.h b/src/util/ieee_float.h new file mode 100644 index 000000000..42b8641d0 --- /dev/null +++ b/src/util/ieee_float.h @@ -0,0 +1,34 @@ +/* +Minetest +Copyright (C) 2018 SmallJoker + +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(); diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index 53c7c2add..f0e177d57 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -28,6 +28,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +FloatType g_serialize_f32_type = FLOATTYPE_UNKNOWN; + //// //// BufReader //// diff --git a/src/util/serialize.h b/src/util/serialize.h index 89bc0b097..016491a2c 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include "exceptions.h" // for SerializationError #include "debug.h" // for assert +#include "ieee_float.h" #include "config.h" #if HAVE_ENDIAN_H @@ -60,6 +61,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define LONG_STRING_MAX_LEN (64 * 1024 * 1024) +extern FloatType g_serialize_f32_type; + #if HAVE_ENDIAN_H // use machine native byte swapping routines // Note: memcpy below is optimized out by modern compilers @@ -188,6 +191,25 @@ inline f32 readF1000(const u8 *data) return (f32)readS32(data) / FIXEDPOINT_FACTOR; } +inline f32 readF32(const u8 *data) +{ + u32 u = readU32(data); + + switch (g_serialize_f32_type) { + case FLOATTYPE_SYSTEM: { + f32 f; + memcpy(&f, &u, 4); + return f; + } + case FLOATTYPE_SLOW: + return u32Tof32Slow(u); + case FLOATTYPE_UNKNOWN: // First initialization + g_serialize_f32_type = getFloatSerializationType(); + return readF32(data); + } + throw SerializationError("readF32: Unreachable code"); +} + inline video::SColor readARGB8(const u8 *data) { video::SColor p(readU32(data)); @@ -245,6 +267,15 @@ inline v3f readV3F1000(const u8 *data) return p; } +inline v3f readV3F32(const u8 *data) +{ + v3f p; + p.X = (float)readF32(&data[0]); + p.Y = (float)readF32(&data[4]); + p.Z = (float)readF32(&data[8]); + return p; +} + /////////////// write routines //////////////// inline void writeU8(u8 *data, u8 i) @@ -259,7 +290,7 @@ inline void writeS8(u8 *data, s8 i) inline void writeS16(u8 *data, s16 i) { - writeU16(data, (u16)i); + writeU16(data, (u16)i); } inline void writeS32(u8 *data, s32 i) @@ -278,6 +309,23 @@ inline void writeF1000(u8 *data, f32 i) writeS32(data, i * FIXEDPOINT_FACTOR); } +inline void writeF32(u8 *data, f32 i) +{ + switch (g_serialize_f32_type) { + case FLOATTYPE_SYSTEM: { + u32 u; + memcpy(&u, &i, 4); + return writeU32(data, u); + } + case FLOATTYPE_SLOW: + return writeU32(data, f32Tou32Slow(i)); + case FLOATTYPE_UNKNOWN: // First initialization + g_serialize_f32_type = getFloatSerializationType(); + return writeF32(data, i); + } + throw SerializationError("writeF32: Unreachable code"); +} + inline void writeARGB8(u8 *data, video::SColor p) { writeU32(data, p.color); @@ -322,6 +370,13 @@ inline void writeV3F1000(u8 *data, v3f p) writeF1000(&data[8], p.Z); } +inline void writeV3F32(u8 *data, v3f p) +{ + writeF32(&data[0], p.X); + writeF32(&data[4], p.Y); + writeF32(&data[8], p.Z); +} + //// //// Iostream wrapper for data read/write //// @@ -351,12 +406,14 @@ MAKE_STREAM_READ_FXN(s16, S16, 2); MAKE_STREAM_READ_FXN(s32, S32, 4); MAKE_STREAM_READ_FXN(s64, S64, 8); MAKE_STREAM_READ_FXN(f32, F1000, 4); +MAKE_STREAM_READ_FXN(f32, F32, 4); MAKE_STREAM_READ_FXN(v2s16, V2S16, 4); MAKE_STREAM_READ_FXN(v3s16, V3S16, 6); MAKE_STREAM_READ_FXN(v2s32, V2S32, 8); MAKE_STREAM_READ_FXN(v3s32, V3S32, 12); MAKE_STREAM_READ_FXN(v2f, V2F1000, 8); MAKE_STREAM_READ_FXN(v3f, V3F1000, 12); +MAKE_STREAM_READ_FXN(v3f, V3F32, 12); MAKE_STREAM_READ_FXN(video::SColor, ARGB8, 4); MAKE_STREAM_WRITE_FXN(u8, U8, 1); @@ -368,12 +425,14 @@ MAKE_STREAM_WRITE_FXN(s16, S16, 2); MAKE_STREAM_WRITE_FXN(s32, S32, 4); MAKE_STREAM_WRITE_FXN(s64, S64, 8); MAKE_STREAM_WRITE_FXN(f32, F1000, 4); +MAKE_STREAM_WRITE_FXN(f32, F32, 4); MAKE_STREAM_WRITE_FXN(v2s16, V2S16, 4); MAKE_STREAM_WRITE_FXN(v3s16, V3S16, 6); MAKE_STREAM_WRITE_FXN(v2s32, V2S32, 8); MAKE_STREAM_WRITE_FXN(v3s32, V3S32, 12); MAKE_STREAM_WRITE_FXN(v2f, V2F1000, 8); MAKE_STREAM_WRITE_FXN(v3f, V3F1000, 12); +MAKE_STREAM_WRITE_FXN(v3f, V3F32, 12); MAKE_STREAM_WRITE_FXN(video::SColor, ARGB8, 4); //// -- cgit v1.2.3