From 88a37010d6cfa808ddfa9559a15285b8291ad187 Mon Sep 17 00:00:00 2001
From: Niels Lohmann <mail@nlohmann.me>
Date: Wed, 17 Jun 2020 21:14:23 +0200
Subject: [PATCH] :bug: serialize 32-bit floating-point numbers as float 32 in
 MessagePack (0xCA) #2196

---
 .../features/binary_formats/messagepack.md    |  7 ++--
 .../nlohmann/detail/output/binary_writer.hpp  | 14 ++++++--
 include/nlohmann/json.hpp                     |  6 ++--
 single_include/nlohmann/json.hpp              | 20 +++++++----
 test/src/unit-msgpack.cpp                     | 34 +++++++++++++++++++
 5 files changed, 64 insertions(+), 17 deletions(-)

diff --git a/doc/mkdocs/docs/features/binary_formats/messagepack.md b/doc/mkdocs/docs/features/binary_formats/messagepack.md
index ed061056..3e041bb7 100644
--- a/doc/mkdocs/docs/features/binary_formats/messagepack.md
+++ b/doc/mkdocs/docs/features/binary_formats/messagepack.md
@@ -31,7 +31,8 @@ number_unsigned | 128..255                          | uint 8           | 0xCC
 number_unsigned | 256..65535                        | uint 16          | 0xCD
 number_unsigned | 65536..4294967295                 | uint 32          | 0xCE
 number_unsigned | 4294967296..18446744073709551615  | uint 64          | 0xCF
-number_float    | *any value*                       | float 64         | 0xCB
+number_float    | *any value representable by a float*     | float 32  | 0xCA
+number_float    | *any value NOT representable by a float* | float 64  | 0xCB
 string          | *length*: 0..31                   | fixstr           | 0xA0..0xBF
 string          | *length*: 32..255                 | str 8            | 0xD9
 string          | *length*: 256..65535              | str 16           | 0xDA
@@ -61,10 +62,6 @@ binary          | *size*: 65536..4294967295         | bin 32           | 0xC6
 	  - arrays with more than 4294967295 elements
 	  - objects with more than 4294967295 elements
 
-!!! info "Unused MessagePack types"
-
-	The following MessagePack types are not used in the conversion: float 32 (0xCA)
-
 !!! info "NaN/infinity handling"
 
 	If NaN or Infinity are stored inside a JSON number, they are serialized properly. function which serializes NaN or Infinity to `null`.
diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp
index 269df0d5..f3221191 100644
--- a/include/nlohmann/detail/output/binary_writer.hpp
+++ b/include/nlohmann/detail/output/binary_writer.hpp
@@ -504,8 +504,18 @@ class binary_writer
 
             case value_t::number_float:
             {
-                oa->write_character(get_msgpack_float_prefix(j.m_value.number_float));
-                write_number(j.m_value.number_float);
+                if (static_cast<double>(j.m_value.number_float) >= static_cast<double>(std::numeric_limits<float>::lowest()) and
+                        static_cast<double>(j.m_value.number_float) <= static_cast<double>((std::numeric_limits<float>::max)()) and
+                        static_cast<double>(static_cast<float>(j.m_value.number_float)) == static_cast<double>(j.m_value.number_float))
+                {
+                    oa->write_character(get_msgpack_float_prefix(static_cast<float>(j.m_value.number_float)));
+                    write_number(static_cast<float>(j.m_value.number_float));
+                }
+                else
+                {
+                    oa->write_character(get_msgpack_float_prefix(j.m_value.number_float));
+                    write_number(j.m_value.number_float);
+                }
                 break;
             }
 
diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp
index 409a6e79..790ecd4b 100644
--- a/include/nlohmann/json.hpp
+++ b/include/nlohmann/json.hpp
@@ -7040,7 +7040,8 @@ class basic_json
     number_unsigned | 256..65535                        | uint 16          | 0xCD
     number_unsigned | 65536..4294967295                 | uint 32          | 0xCE
     number_unsigned | 4294967296..18446744073709551615  | uint 64          | 0xCF
-    number_float    | *any value*                       | float 64         | 0xCB
+    number_float    | *any value representable by a float*     | float 32 | 0xCA
+    number_float    | *any value NOT representable by a float* | float 64 | 0xCB
     string          | *length*: 0..31                   | fixstr           | 0xA0..0xBF
     string          | *length*: 32..255                 | str 8            | 0xD9
     string          | *length*: 256..65535              | str 16           | 0xDA
@@ -7064,9 +7065,6 @@ class basic_json
           - arrays with more than 4294967295 elements
           - objects with more than 4294967295 elements
 
-    @note The following MessagePack types are not used in the conversion:
-          - float 32 (0xCA)
-
     @note Any MessagePack output created @ref to_msgpack can be successfully
           parsed by @ref from_msgpack.
 
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index cc822a54..85fffc7c 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -12714,8 +12714,18 @@ class binary_writer
 
             case value_t::number_float:
             {
-                oa->write_character(get_msgpack_float_prefix(j.m_value.number_float));
-                write_number(j.m_value.number_float);
+                if (static_cast<double>(j.m_value.number_float) >= static_cast<double>(std::numeric_limits<float>::lowest()) and
+                        static_cast<double>(j.m_value.number_float) <= static_cast<double>((std::numeric_limits<float>::max)()) and
+                        static_cast<double>(static_cast<float>(j.m_value.number_float)) == static_cast<double>(j.m_value.number_float))
+                {
+                    oa->write_character(get_msgpack_float_prefix(static_cast<float>(j.m_value.number_float)));
+                    write_number(static_cast<float>(j.m_value.number_float));
+                }
+                else
+                {
+                    oa->write_character(get_msgpack_float_prefix(j.m_value.number_float));
+                    write_number(j.m_value.number_float);
+                }
                 break;
             }
 
@@ -22821,7 +22831,8 @@ class basic_json
     number_unsigned | 256..65535                        | uint 16          | 0xCD
     number_unsigned | 65536..4294967295                 | uint 32          | 0xCE
     number_unsigned | 4294967296..18446744073709551615  | uint 64          | 0xCF
-    number_float    | *any value*                       | float 64         | 0xCB
+    number_float    | *any value representable by a float*     | float 32 | 0xCA
+    number_float    | *any value NOT representable by a float* | float 64 | 0xCB
     string          | *length*: 0..31                   | fixstr           | 0xA0..0xBF
     string          | *length*: 32..255                 | str 8            | 0xD9
     string          | *length*: 256..65535              | str 16           | 0xDA
@@ -22845,9 +22856,6 @@ class basic_json
           - arrays with more than 4294967295 elements
           - objects with more than 4294967295 elements
 
-    @note The following MessagePack types are not used in the conversion:
-          - float 32 (0xCA)
-
     @note Any MessagePack output created @ref to_msgpack can be successfully
           parsed by @ref from_msgpack.
 
diff --git a/test/src/unit-msgpack.cpp b/test/src/unit-msgpack.cpp
index 2744a67b..5baecf02 100644
--- a/test/src/unit-msgpack.cpp
+++ b/test/src/unit-msgpack.cpp
@@ -783,6 +783,40 @@ TEST_CASE("MessagePack")
                     CHECK(json::from_msgpack(result) == v);
                     CHECK(json::from_msgpack(result, true, false) == j);
                 }
+
+                SECTION("1.0")
+                {
+                    double v = 1.0;
+                    json j = v;
+                    std::vector<uint8_t> expected =
+                    {
+                        0xca, 0x3f, 0x80, 0x00, 0x00
+                    };
+                    const auto result = json::to_msgpack(j);
+                    CHECK(result == expected);
+
+                    // roundtrip
+                    CHECK(json::from_msgpack(result) == j);
+                    CHECK(json::from_msgpack(result) == v);
+                    CHECK(json::from_msgpack(result, true, false) == j);
+                }
+
+                SECTION("128.128")
+                {
+                    double v = 128.1280059814453125;
+                    json j = v;
+                    std::vector<uint8_t> expected =
+                    {
+                        0xca, 0x43, 0x00, 0x20, 0xc5
+                    };
+                    const auto result = json::to_msgpack(j);
+                    CHECK(result == expected);
+
+                    // roundtrip
+                    CHECK(json::from_msgpack(result) == j);
+                    CHECK(json::from_msgpack(result) == v);
+                    CHECK(json::from_msgpack(result, true, false) == j);
+                }
             }
         }