diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index f90978df1c..910e52ad5b 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -4,6 +4,30 @@ JUCE breaking changes Develop ======= +Change +------ +The formatting of floating point numbers written to XML and JSON files has +changed. + +Note that there is no change in precision - the XML and JSON files containing +the new format numbers will parse in exactly the same way, it is only the +string representation that has changed. + +Possible Issues +--------------- +If you rely upon exactly reproducing XML or JSON files then the new files may +be different. + +Workaround +---------- +Update any reference XML or JSON files to use the new format. + +Rationale +--------- +The new format retains full precision, provides a human friendly representation +of values near 1, and uses scientific notation for small and large numbers. +This prevents needless file size bloat from numbers like 0.00000000000000001. + Version 5.4.3 ============= diff --git a/modules/juce_core/containers/juce_Variant.cpp b/modules/juce_core/containers/juce_Variant.cpp index 1426f43f2d..cd30816170 100644 --- a/modules/juce_core/containers/juce_Variant.cpp +++ b/modules/juce_core/containers/juce_Variant.cpp @@ -175,7 +175,7 @@ public: int toInt (const ValueUnion& data) const noexcept override { return (int) data.doubleValue; } int64 toInt64 (const ValueUnion& data) const noexcept override { return (int64) data.doubleValue; } double toDouble (const ValueUnion& data) const noexcept override { return data.doubleValue; } - String toString (const ValueUnion& data) const override { return minimiseLengthOfFloatString (String (data.doubleValue, 15, true)); } + String toString (const ValueUnion& data) const override { return serialiseDouble (data.doubleValue); } bool toBool (const ValueUnion& data) const noexcept override { return data.doubleValue != 0.0; } bool isDouble() const noexcept override { return true; } bool isComparable() const noexcept override { return true; } diff --git a/modules/juce_core/javascript/juce_JSON.cpp b/modules/juce_core/javascript/juce_JSON.cpp index 08082ae098..020c74edc3 100644 --- a/modules/juce_core/javascript/juce_JSON.cpp +++ b/modules/juce_core/javascript/juce_JSON.cpp @@ -352,8 +352,7 @@ struct JSONFormatter if (juce_isfinite (d)) { - String doubleString (d, maximumDecimalPlaces, true); - out << minimiseLengthOfFloatString (doubleString); + out << serialiseDouble (d); } else { @@ -664,12 +663,17 @@ public: tests[1] = "1"; tests[1.1] = "1.1"; tests[1.01] = "1.01"; - tests[0.76378] = "7.6378e-1"; - tests[-10] = "-1e1"; - tests[10.01] = "1.001e1"; - tests[0.0123] = "1.23e-2"; + tests[0.76378] = "0.76378"; + tests[-10] = "-10"; + tests[10.01] = "10.01"; + tests[0.0123] = "0.0123"; tests[-3.7e-27] = "-3.7e-27"; tests[1e+40] = "1e40"; + tests[-12345678901234567.0] = "-1.234567890123457e16"; + tests[192000] = "192000"; + tests[1234567] = "1.234567e6"; + tests[0.00006] = "0.00006"; + tests[0.000006] = "6e-6"; for (auto& test : tests) expectEquals (JSON::toString (test.first), test.second); diff --git a/modules/juce_core/text/juce_String.cpp b/modules/juce_core/text/juce_String.cpp index 0a72b49274..c334f2a93e 100644 --- a/modules/juce_core/text/juce_String.cpp +++ b/modules/juce_core/text/juce_String.cpp @@ -2258,6 +2258,48 @@ static String minimiseLengthOfFloatString (const String& input) return input; } +static String serialiseDouble (double input) +{ + auto absInput = std::abs (input); + + if (absInput >= 1.0e6 || absInput <= 1.0e-5) + return minimiseLengthOfFloatString ({ input, 15, true }); + + int intInput = (int) input; + + if ((double) intInput == input) + return minimiseLengthOfFloatString ({ input, 1 }); + + auto numberOfDecimalPlaces = [absInput] + { + if (absInput < 1.0) + { + if (absInput >= 1.0e-3) + { + if (absInput >= 1.0e-1) return 16; + if (absInput >= 1.0e-2) return 17; + return 18; + } + + if (absInput >= 1.0e-4) return 19; + return 20; + } + + if (absInput < 1.0e3) + { + if (absInput < 1.0e1) return 15; + if (absInput < 1.0e2) return 14; + return 13; + } + + if (absInput < 1.0e4) return 12; + if (absInput < 1.0e5) return 11; + return 10; + }(); + + return minimiseLengthOfFloatString (String (input, numberOfDecimalPlaces)); +} + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS @@ -2851,6 +2893,34 @@ public: expectEquals (minimiseLengthOfFloatString (String (test.first, 15, true)), test.second); } } + + { + beginTest ("Serialisation"); + + std::map tests; + tests[1234567890123456.7] = "1.234567890123457e15"; + tests[12345678.901234567] = "1.234567890123457e7"; + tests[1234567.8901234567] = "1.234567890123457e6"; + tests[123456.78901234567] = "123456.7890123457"; + tests[12345.678901234567] = "12345.67890123457"; + tests[1234.5678901234567] = "1234.567890123457"; + tests[123.45678901234567] = "123.4567890123457"; + tests[12.345678901234567] = "12.34567890123457"; + tests[1.2345678901234567] = "1.234567890123457"; + tests[0.12345678901234567] = "0.1234567890123457"; + tests[0.012345678901234567] = "0.01234567890123457"; + tests[0.0012345678901234567] = "0.001234567890123457"; + tests[0.00012345678901234567] = "0.0001234567890123457"; + tests[0.000012345678901234567] = "0.00001234567890123457"; + tests[0.0000012345678901234567] = "1.234567890123457e-6"; + tests[0.00000012345678901234567] = "1.234567890123457e-7"; + + for (auto& test : tests) + { + expectEquals (serialiseDouble (test.first), test.second); + expectEquals (serialiseDouble (-test.first), "-" + test.second); + } + } } }; diff --git a/modules/juce_core/xml/juce_XmlElement.cpp b/modules/juce_core/xml/juce_XmlElement.cpp index 0c745e2a79..ff46dcf8ff 100644 --- a/modules/juce_core/xml/juce_XmlElement.cpp +++ b/modules/juce_core/xml/juce_XmlElement.cpp @@ -580,8 +580,7 @@ void XmlElement::setAttribute (const Identifier& attributeName, const int number void XmlElement::setAttribute (const Identifier& attributeName, const double number) { - String doubleString (number, 15, true); - setAttribute (attributeName, minimiseLengthOfFloatString (doubleString)); + setAttribute (attributeName, serialiseDouble (number)); } void XmlElement::removeAttribute (const Identifier& attributeName) noexcept @@ -946,12 +945,17 @@ public: tests[1] = "1"; tests[1.1] = "1.1"; tests[1.01] = "1.01"; - tests[0.76378] = "7.6378e-1"; - tests[-10] = "-1e1"; - tests[10.01] = "1.001e1"; - tests[0.0123] = "1.23e-2"; + tests[0.76378] = "0.76378"; + tests[-10] = "-10"; + tests[10.01] = "10.01"; + tests[0.0123] = "0.0123"; tests[-3.7e-27] = "-3.7e-27"; tests[1e+40] = "1e40"; + tests[-12345678901234567.0] = "-1.234567890123457e16"; + tests[192000] = "192000"; + tests[1234567] = "1.234567e6"; + tests[0.00006] = "0.00006"; + tests[0.000006] = "6e-6"; for (auto& test : tests) { diff --git a/modules/juce_data_structures/values/juce_ValueTree.cpp b/modules/juce_data_structures/values/juce_ValueTree.cpp index 8ff2983e9c..5f43a5d0a1 100644 --- a/modules/juce_data_structures/values/juce_ValueTree.cpp +++ b/modules/juce_data_structures/values/juce_ValueTree.cpp @@ -1198,12 +1198,17 @@ public: tests[1] = "1"; tests[1.1] = "1.1"; tests[1.01] = "1.01"; - tests[0.76378] = "7.6378e-1"; - tests[-10] = "-1e1"; - tests[10.01] = "1.001e1"; - tests[0.0123] = "1.23e-2"; + tests[0.76378] = "0.76378"; + tests[-10] = "-10"; + tests[10.01] = "10.01"; + tests[0.0123] = "0.0123"; tests[-3.7e-27] = "-3.7e-27"; tests[1e+40] = "1e40"; + tests[-12345678901234567.0] = "-1.234567890123457e16"; + tests[192000] = "192000"; + tests[1234567] = "1.234567e6"; + tests[0.00006] = "0.00006"; + tests[0.000006] = "6e-6"; for (auto& test : tests) {