| @@ -4,6 +4,30 @@ JUCE breaking changes | |||||
| Develop | 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 | Version 5.4.3 | ||||
| ============= | ============= | ||||
| @@ -175,7 +175,7 @@ public: | |||||
| int toInt (const ValueUnion& data) const noexcept override { return (int) data.doubleValue; } | int toInt (const ValueUnion& data) const noexcept override { return (int) data.doubleValue; } | ||||
| int64 toInt64 (const ValueUnion& data) const noexcept override { return (int64) 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; } | 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 toBool (const ValueUnion& data) const noexcept override { return data.doubleValue != 0.0; } | ||||
| bool isDouble() const noexcept override { return true; } | bool isDouble() const noexcept override { return true; } | ||||
| bool isComparable() const noexcept override { return true; } | bool isComparable() const noexcept override { return true; } | ||||
| @@ -352,8 +352,7 @@ struct JSONFormatter | |||||
| if (juce_isfinite (d)) | if (juce_isfinite (d)) | ||||
| { | { | ||||
| String doubleString (d, maximumDecimalPlaces, true); | |||||
| out << minimiseLengthOfFloatString (doubleString); | |||||
| out << serialiseDouble (d); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -664,12 +663,17 @@ public: | |||||
| tests[1] = "1"; | tests[1] = "1"; | ||||
| tests[1.1] = "1.1"; | tests[1.1] = "1.1"; | ||||
| tests[1.01] = "1.01"; | 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[-3.7e-27] = "-3.7e-27"; | ||||
| tests[1e+40] = "1e40"; | 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) | for (auto& test : tests) | ||||
| expectEquals (JSON::toString (test.first), test.second); | expectEquals (JSON::toString (test.first), test.second); | ||||
| @@ -2258,6 +2258,48 @@ static String minimiseLengthOfFloatString (const String& input) | |||||
| return 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 | #if JUCE_UNIT_TESTS | ||||
| @@ -2851,6 +2893,34 @@ public: | |||||
| expectEquals (minimiseLengthOfFloatString (String (test.first, 15, true)), test.second); | expectEquals (minimiseLengthOfFloatString (String (test.first, 15, true)), test.second); | ||||
| } | } | ||||
| } | } | ||||
| { | |||||
| beginTest ("Serialisation"); | |||||
| std::map <double, String> 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); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -580,8 +580,7 @@ void XmlElement::setAttribute (const Identifier& attributeName, const int number | |||||
| void XmlElement::setAttribute (const Identifier& attributeName, const double 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 | void XmlElement::removeAttribute (const Identifier& attributeName) noexcept | ||||
| @@ -946,12 +945,17 @@ public: | |||||
| tests[1] = "1"; | tests[1] = "1"; | ||||
| tests[1.1] = "1.1"; | tests[1.1] = "1.1"; | ||||
| tests[1.01] = "1.01"; | 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[-3.7e-27] = "-3.7e-27"; | ||||
| tests[1e+40] = "1e40"; | 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) | for (auto& test : tests) | ||||
| { | { | ||||
| @@ -1198,12 +1198,17 @@ public: | |||||
| tests[1] = "1"; | tests[1] = "1"; | ||||
| tests[1.1] = "1.1"; | tests[1.1] = "1.1"; | ||||
| tests[1.01] = "1.01"; | 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[-3.7e-27] = "-3.7e-27"; | ||||
| tests[1e+40] = "1e40"; | 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) | for (auto& test : tests) | ||||
| { | { | ||||