From 7c33b2132ff45eed3beff9719bfb0419944b77cb Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Mon, 1 Feb 2021 12:28:54 +0000 Subject: [PATCH] Fixed some parsing issues in readDoubleValue --- BREAKING-CHANGES.txt | 25 ++ .../text/juce_CharacterFunctions.cpp | 397 ++++++++++++++---- .../juce_core/text/juce_CharacterFunctions.h | 97 +++-- 3 files changed, 418 insertions(+), 101 deletions(-) diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 8b3cdcbfc5..955d7ccc11 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -1,6 +1,31 @@ JUCE breaking changes ===================== +Develop +======= + +Change +------ +CharacterFunctions::readDoubleValue now returns values consistent with other +C++ number parsing libraries. Parsing values smaller than the minimum number +respresentable in a double will return (+/-)0.0 and parsing values larger than +the maximum number respresentable in a double will return (+/-)inf. + +Possible Issues +--------------- +Code reading very large or very small numbers may receive values of 0.0 and inf +rather than nan. + +Workaround +---------- +Where you may be using std::isnan to check the validity of the result you can +instead use std::isfinite. + +Rationale +--------- +The new behaviour is consistent with other string parsing libraries. + + Version 6.0.6 ============= diff --git a/modules/juce_core/text/juce_CharacterFunctions.cpp b/modules/juce_core/text/juce_CharacterFunctions.cpp index 8341a8cae7..a0e973e182 100644 --- a/modules/juce_core/text/juce_CharacterFunctions.cpp +++ b/modules/juce_core/text/juce_CharacterFunctions.cpp @@ -181,19 +181,209 @@ juce_wchar CharacterFunctions::getUnicodeCharFromWindows1252Codepage (const uint #define QUOTE(x) #x #define STR(value) QUOTE(value) -#define ASYM_STRING_DOUBLE_PAIR(str, value) std::pair (STR(str), value) -#define STRING_DOUBLE_PAIR(value) ASYM_STRING_DOUBLE_PAIR(value, value) -#define STRING_DOUBLE_PAIR_COMBOS(value) \ - STRING_DOUBLE_PAIR(value), \ - STRING_DOUBLE_PAIR(-value), \ - ASYM_STRING_DOUBLE_PAIR(+value, value), \ - ASYM_STRING_DOUBLE_PAIR(000000 ## value, value), \ - ASYM_STRING_DOUBLE_PAIR(+000 ## value, value), \ - ASYM_STRING_DOUBLE_PAIR(-0 ## value, -value) +#define ASYM_CHARPTR_DOUBLE_PAIR(str, value) std::pair (STR(str), value) +#define CHARPTR_DOUBLE_PAIR(value) ASYM_CHARPTR_DOUBLE_PAIR(value, value) +#define CHARPTR_DOUBLE_PAIR_COMBOS(value) \ + CHARPTR_DOUBLE_PAIR(value), \ + CHARPTR_DOUBLE_PAIR(-value), \ + ASYM_CHARPTR_DOUBLE_PAIR(+value, value), \ + ASYM_CHARPTR_DOUBLE_PAIR(000000 ## value, value), \ + ASYM_CHARPTR_DOUBLE_PAIR(+000 ## value, value), \ + ASYM_CHARPTR_DOUBLE_PAIR(-0 ## value, -value) +namespace characterFunctionsTests +{ + +template +MemoryBlock memoryBlockFromCharPtr (const typename CharPointerType::CharType* charPtr) +{ + using CharType = typename CharPointerType::CharType; + + MemoryBlock result; + CharPointerType source (charPtr); + + result.setSize (CharPointerType::getBytesRequiredFor (source) + sizeof (CharType)); + CharPointerType dest { (CharType*) result.getData() }; + dest.writeAll (source); + return result; +} + +template +MemoryBlock convert (const MemoryBlock& source, bool removeNullTerminator = false) +{ + using ToCharType = typename ToCharPointerType ::CharType; + using FromCharType = typename FromCharPointerType::CharType; + + FromCharPointerType sourcePtr { (FromCharType*) source.getData() }; + + std::vector sourceChars; + size_t requiredSize = 0; + juce_wchar c; + + while ((c = sourcePtr.getAndAdvance()) != '\0') + { + requiredSize += ToCharPointerType::getBytesRequiredFor (c); + sourceChars.push_back (c); + } + + if (! removeNullTerminator) + requiredSize += sizeof (ToCharType); + + MemoryBlock result; + result.setSize (requiredSize); + + ToCharPointerType dest { (ToCharType*) result.getData() }; + + for (auto wc : sourceChars) + dest.write (wc); + + if (! removeNullTerminator) + dest.writeNull(); + + return result; +} + +struct SeparatorStrings +{ + std::vector terminals, nulls; +}; + +template +SeparatorStrings getSeparators() +{ + jassertfalse; + return {}; +} + +template <> +SeparatorStrings getSeparators() +{ + SeparatorStrings result; + + const CharPointer_ASCII::CharType* terminalCharPtrs[] = { + "", "-", "+", "e", "e+", "E-", "f", " ", ",", ";", "<", "'", "\"", "_", "k", + " +", " -", " -e", "-In ", " +n", "n", " r" + }; + + for (auto ptr : terminalCharPtrs) + result.terminals.push_back (memoryBlockFromCharPtr (ptr)); + + const CharPointer_ASCII::CharType* nullCharPtrs[] = { "." }; + + result.nulls = result.terminals; + + for (auto ptr : nullCharPtrs) + result.nulls.push_back (memoryBlockFromCharPtr (ptr)); + + return result; +} + +template <> +SeparatorStrings getSeparators() +{ + auto result = getSeparators(); + + const CharPointer_UTF8::CharType* terminalCharPtrs[] = { + "\xe2\x82\xac", // € + "\xf0\x90\x90\xB7", // 𐐷 + "\xf0\x9f\x98\x83", // 😃 + "\xf0\x9f\x8f\x81\xF0\x9F\x9A\x97" // 🏁🚗 + }; + + for (auto ptr : terminalCharPtrs) + { + auto block = memoryBlockFromCharPtr (ptr); + + for (auto vec : { &result.terminals, &result.nulls }) + vec->push_back (block); + } + + return result; +} + +template +SeparatorStrings prefixWithAsciiSeparators (const std::vector>& terminalCharPtrs) +{ + auto asciiSeparators = getSeparators(); + + SeparatorStrings result; + + for (const auto& block : asciiSeparators.terminals) + result.terminals.push_back (convert (block)); + + for (const auto& block : asciiSeparators.nulls) + result.nulls.push_back (convert (block)); + + for (auto& t : terminalCharPtrs) + { + const auto block = memoryBlockFromCharPtr ((typename CharPointerType::CharType*) t.data()); + + for (auto vec : { &result.terminals, &result.nulls }) + vec->push_back (block); + } + + return result; +} + +template <> +SeparatorStrings getSeparators() +{ + const std::vector> terminalCharPtrs { + { 0x0 }, + { 0x0076, 0x0 }, // v + { 0x20ac, 0x0 }, // € + { 0xd801, 0xdc37, 0x0 }, // 𐐷 + { 0x0065, 0xd83d, 0xde03, 0x0 }, // e😃 + { 0xd83c, 0xdfc1, 0xd83d, 0xde97, 0x0 } // 🏁🚗 + }; + + return prefixWithAsciiSeparators (terminalCharPtrs); +} + +template <> +SeparatorStrings getSeparators() +{ + const std::vector> terminalCharPtrs = { + { 0x00000076, 0x0 }, // v + { 0x000020aC, 0x0 }, // € + { 0x00010437, 0x0 }, // 𐐷 + { 0x00000065, 0x0001f603, 0x0 }, // e😃 + { 0x0001f3c1, 0x0001f697, 0x0 } // 🏁🚗 + }; + + return prefixWithAsciiSeparators (terminalCharPtrs); +} + +template +void withAllPrefixesAndSuffixes (const std::vector& prefixes, + const std::vector& suffixes, + const std::vector& testValues, + TestFunction&& test) +{ + for (const auto& prefix : prefixes) + { + for (const auto& testValue : testValues) + { + MemoryBlock testBlock = prefix; + testBlock.append (testValue.getData(), testValue.getSize()); + + for (const auto& suffix : suffixes) + { + MemoryBlock data = testBlock; + data.append (suffix.getData(), suffix.getSize()); + + test (data, suffix); + } + } + } +} + +template class CharacterFunctionsTests : public UnitTest { public: + using CharType = typename CharPointerType::CharType; + CharacterFunctionsTests() : UnitTest ("CharacterFunctions", UnitTestCategories::text) {} @@ -202,96 +392,159 @@ public: { beginTest ("readDoubleValue"); - static const std::pair testValues[] = + const std::pair trials[] = { // Integers - STRING_DOUBLE_PAIR_COMBOS (0), - STRING_DOUBLE_PAIR_COMBOS (3), - STRING_DOUBLE_PAIR_COMBOS (4931), - STRING_DOUBLE_PAIR_COMBOS (5000), - STRING_DOUBLE_PAIR_COMBOS (9862097), + CHARPTR_DOUBLE_PAIR_COMBOS (0), + CHARPTR_DOUBLE_PAIR_COMBOS (3), + CHARPTR_DOUBLE_PAIR_COMBOS (4931), + CHARPTR_DOUBLE_PAIR_COMBOS (5000), + CHARPTR_DOUBLE_PAIR_COMBOS (9862097), // Floating point numbers - STRING_DOUBLE_PAIR_COMBOS (7.000), - STRING_DOUBLE_PAIR_COMBOS (0.2), - STRING_DOUBLE_PAIR_COMBOS (.298630), - STRING_DOUBLE_PAIR_COMBOS (1.118), - STRING_DOUBLE_PAIR_COMBOS (0.9000), - STRING_DOUBLE_PAIR_COMBOS (0.0000001), - STRING_DOUBLE_PAIR_COMBOS (500.0000001), - STRING_DOUBLE_PAIR_COMBOS (9862098.2398604), + CHARPTR_DOUBLE_PAIR_COMBOS (0.), + CHARPTR_DOUBLE_PAIR_COMBOS (9.), + CHARPTR_DOUBLE_PAIR_COMBOS (7.000), + CHARPTR_DOUBLE_PAIR_COMBOS (0.2), + CHARPTR_DOUBLE_PAIR_COMBOS (.298630), + CHARPTR_DOUBLE_PAIR_COMBOS (1.118), + CHARPTR_DOUBLE_PAIR_COMBOS (0.9000), + CHARPTR_DOUBLE_PAIR_COMBOS (0.0000001), + CHARPTR_DOUBLE_PAIR_COMBOS (500.0000001), + CHARPTR_DOUBLE_PAIR_COMBOS (9862098.2398604), // Exponents - STRING_DOUBLE_PAIR_COMBOS (0e0), - STRING_DOUBLE_PAIR_COMBOS (0.e0), - STRING_DOUBLE_PAIR_COMBOS (0.00000e0), - STRING_DOUBLE_PAIR_COMBOS (.0e7), - STRING_DOUBLE_PAIR_COMBOS (0e-5), - STRING_DOUBLE_PAIR_COMBOS (2E0), - STRING_DOUBLE_PAIR_COMBOS (4.E0), - STRING_DOUBLE_PAIR_COMBOS (1.2000000E0), - STRING_DOUBLE_PAIR_COMBOS (1.2000000E6), - STRING_DOUBLE_PAIR_COMBOS (.398e3), - STRING_DOUBLE_PAIR_COMBOS (10e10), - STRING_DOUBLE_PAIR_COMBOS (1.4962e+2), - STRING_DOUBLE_PAIR_COMBOS (3198693.0973e4), - STRING_DOUBLE_PAIR_COMBOS (10973097.2087E-4), - STRING_DOUBLE_PAIR_COMBOS (1.3986e00006), - STRING_DOUBLE_PAIR_COMBOS (2087.3087e+00006), - STRING_DOUBLE_PAIR_COMBOS (6.0872e-00006), + CHARPTR_DOUBLE_PAIR_COMBOS (0e0), + CHARPTR_DOUBLE_PAIR_COMBOS (0.e0), + CHARPTR_DOUBLE_PAIR_COMBOS (0.00000e0), + CHARPTR_DOUBLE_PAIR_COMBOS (.0e7), + CHARPTR_DOUBLE_PAIR_COMBOS (0e-5), + CHARPTR_DOUBLE_PAIR_COMBOS (2E0), + CHARPTR_DOUBLE_PAIR_COMBOS (4.E0), + CHARPTR_DOUBLE_PAIR_COMBOS (1.2000000E0), + CHARPTR_DOUBLE_PAIR_COMBOS (1.2000000E6), + CHARPTR_DOUBLE_PAIR_COMBOS (.398e3), + CHARPTR_DOUBLE_PAIR_COMBOS (10e10), + CHARPTR_DOUBLE_PAIR_COMBOS (1.4962e+2), + CHARPTR_DOUBLE_PAIR_COMBOS (3198693.0973e4), + CHARPTR_DOUBLE_PAIR_COMBOS (10973097.2087E-4), + CHARPTR_DOUBLE_PAIR_COMBOS (1.3986e00006), + CHARPTR_DOUBLE_PAIR_COMBOS (2087.3087e+00006), + CHARPTR_DOUBLE_PAIR_COMBOS (6.0872e-00006), + + CHARPTR_DOUBLE_PAIR_COMBOS (1.7976931348623157e+308), + CHARPTR_DOUBLE_PAIR_COMBOS (2.2250738585072014e-308), // Too many sig figs. The parsing routine on MinGW gets the last // significant figure wrong. - STRING_DOUBLE_PAIR_COMBOS (17654321098765432.9), - STRING_DOUBLE_PAIR_COMBOS (183456789012345678.9), - STRING_DOUBLE_PAIR_COMBOS (1934567890123456789.9), - STRING_DOUBLE_PAIR_COMBOS (20345678901234567891.9), - STRING_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752.000000), - STRING_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752e3), - STRING_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752e100), - STRING_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752.000000e-5), - STRING_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752.000000e-40), + CHARPTR_DOUBLE_PAIR_COMBOS (17654321098765432.9), + CHARPTR_DOUBLE_PAIR_COMBOS (183456789012345678.9), + CHARPTR_DOUBLE_PAIR_COMBOS (1934567890123456789.9), + CHARPTR_DOUBLE_PAIR_COMBOS (20345678901234567891.9), + CHARPTR_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752.000000), + CHARPTR_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752e3), + CHARPTR_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752e100), + CHARPTR_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752.000000e-5), + CHARPTR_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752.000005e-40), - STRING_DOUBLE_PAIR_COMBOS (1.23456789012345678901234567890), - STRING_DOUBLE_PAIR_COMBOS (1.23456789012345678901234567890e-111) - - // Limits. DBL_MAX may not exist on Linux. - #if ! JUCE_LINUX - , STRING_DOUBLE_PAIR (DBL_MAX), - STRING_DOUBLE_PAIR (-DBL_MAX), - STRING_DOUBLE_PAIR (DBL_MIN) - #endif + CHARPTR_DOUBLE_PAIR_COMBOS (1.23456789012345678901234567890), + CHARPTR_DOUBLE_PAIR_COMBOS (1.23456789012345678901234567890e-111), }; - for (auto trial : testValues) + auto asciiToMemoryBlock = [] (const char* asciiPtr, bool removeNullTerminator) { - auto charPtr = trial.first.getCharPointer(); - expectEquals (CharacterFunctions::readDoubleValue (charPtr), trial.second); - } + auto block = memoryBlockFromCharPtr (asciiPtr); + return convert (block, removeNullTerminator); + }; + const auto separators = getSeparators(); + + for (const auto& trial : trials) { - String nans[] = { "NaN", "-nan", "+NAN", "1.0E1024", "-1.0E-999", "1.23456789012345678901234567890e123456789"}; - - for (auto nan : nans) + for (const auto& terminal : separators.terminals) { - auto charPtr = nan.getCharPointer(); - expect (std::isnan (CharacterFunctions::readDoubleValue (charPtr))); + MemoryBlock data { asciiToMemoryBlock (trial.first, true) }; + data.append (terminal.getData(), terminal.getSize()); + + CharPointerType charPtr { (CharType*) data.getData() }; + expectEquals (CharacterFunctions::readDoubleValue (charPtr), trial.second); + expect (*charPtr == *(CharPointerType ((CharType*) terminal.getData()))); } } + auto asciiToMemoryBlocks = [&] (const std::vector& asciiPtrs, bool removeNullTerminator) { - String infs[] = { "Inf", "-inf", "INF"}; + std::vector result; - for (auto inf : infs) + for (auto* ptr : asciiPtrs) + result.push_back (asciiToMemoryBlock (ptr, removeNullTerminator)); + + return result; + }; + + std::vector prefixCharPtrs = { "" , "+", "-" }; + const auto prefixes = asciiToMemoryBlocks (prefixCharPtrs, true); + + { + std::vector nanCharPtrs = { "NaN", "nan", "NAN", "naN" }; + auto nans = asciiToMemoryBlocks (nanCharPtrs, true); + + withAllPrefixesAndSuffixes (prefixes, separators.terminals, nans, [this] (const MemoryBlock& data, + const MemoryBlock& suffix) { - auto charPtr = inf.getCharPointer(); - expect (std::isinf (CharacterFunctions::readDoubleValue (charPtr))); + CharPointerType charPtr { (CharType*) data.getData() }; + expect (std::isnan (CharacterFunctions::readDoubleValue (charPtr))); + expect (*charPtr == *(CharPointerType ((CharType*) suffix.getData()))); + }); + } + + { + std::vector infCharPtrs = { "Inf", "inf", "INF", "InF", "1.0E1024", "1.23456789012345678901234567890e123456789" }; + auto infs = asciiToMemoryBlocks (infCharPtrs, true); + + withAllPrefixesAndSuffixes (prefixes, separators.terminals, infs, [this] (const MemoryBlock& data, + const MemoryBlock& suffix) + { + CharPointerType charPtr { (CharType*) data.getData() }; + auto expected = charPtr[0] == '-' ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + expectEquals (CharacterFunctions::readDoubleValue (charPtr), expected); + expect (*charPtr == *(CharPointerType ((CharType*) suffix.getData()))); + }); + } + + { + std::vector zeroCharPtrs = { "1.0E-400", "1.23456789012345678901234567890e-123456789" }; + auto zeros = asciiToMemoryBlocks (zeroCharPtrs, true); + + withAllPrefixesAndSuffixes (prefixes, separators.terminals, zeros, [this] (const MemoryBlock& data, + const MemoryBlock& suffix) + { + CharPointerType charPtr { (CharType*) data.getData() }; + auto expected = charPtr[0] == '-' ? -0.0 : 0.0; + expectEquals (CharacterFunctions::readDoubleValue (charPtr), expected); + expect (*charPtr == *(CharPointerType ((CharType*) suffix.getData()))); + }); + } + + { + for (const auto& n : separators.nulls) + { + MemoryBlock data { n.getData(), n.getSize() }; + CharPointerType charPtr { (CharType*) data.getData() }; + expectEquals (CharacterFunctions::readDoubleValue (charPtr), 0.0); + expect (charPtr == CharPointerType { (CharType*) data.getData() }.findEndOfWhitespace()); } } } }; -static CharacterFunctionsTests characterFunctionsTests; +static CharacterFunctionsTests characterFunctionsTestsAscii; +static CharacterFunctionsTests characterFunctionsTestsUtf8; +static CharacterFunctionsTests characterFunctionsTestsUtf16; +static CharacterFunctionsTests characterFunctionsTestsUtf32; + +} #endif diff --git a/modules/juce_core/text/juce_CharacterFunctions.h b/modules/juce_core/text/juce_CharacterFunctions.h index 76c9c5c7bb..fad54c93d9 100644 --- a/modules/juce_core/text/juce_CharacterFunctions.h +++ b/modules/juce_core/text/juce_CharacterFunctions.h @@ -146,25 +146,27 @@ public: template static double readDoubleValue (CharPointerType& text) noexcept { - #if JUCE_MINGW + constexpr auto inf = std::numeric_limits::infinity(); + bool isNegative = false; - #else + #if ! JUCE_MINGW constexpr const int maxSignificantDigits = 17 + 1; // An additional digit for rounding constexpr const int bufferSize = maxSignificantDigits + 7 + 1; // -.E-XXX and a trailing null-terminator char buffer[(size_t) bufferSize] = {}; - char* currentCharacter = &(buffer[0]); + char* writePtr = &(buffer[0]); #endif - text = text.findEndOfWhitespace(); + const auto endOfWhitspace = text.findEndOfWhitespace(); + text = endOfWhitspace; + auto c = *text; switch (c) { case '-': - #if JUCE_MINGW isNegative = true; - #else - *currentCharacter++ = '-'; + #if ! JUCE_MINGW + *writePtr++ = '-'; #endif JUCE_FALLTHROUGH case '+': @@ -178,15 +180,29 @@ public: { case 'n': case 'N': + { if ((text[1] == 'a' || text[1] == 'A') && (text[2] == 'n' || text[2] == 'N')) + { + text += 3; return std::numeric_limits::quiet_NaN(); - break; + } + + text = endOfWhitspace; + return 0.0; + } case 'i': case 'I': + { if ((text[1] == 'n' || text[1] == 'N') && (text[2] == 'f' || text[2] == 'F')) - return std::numeric_limits::infinity(); - break; + { + text += 3; + return isNegative ? -inf : inf; + } + + text = endOfWhitspace; + return 0.0; + } default: break; @@ -299,9 +315,8 @@ public: #else // ! JUCE_MINGW - int numSigFigs = 0; - bool decimalPointFound = false; - int extraExponent = 0; + int numSigFigs = 0, extraExponent = 0; + bool decimalPointFound = false, leadingZeros = false; for (;;) { @@ -323,16 +338,19 @@ public: } if (numSigFigs == 0 && digit == 0) + { + leadingZeros = true; continue; + } } - *currentCharacter++ = (char) ('0' + (char) digit); + *writePtr++ = (char) ('0' + (char) digit); numSigFigs++; } else if ((! decimalPointFound) && *text == '.') { ++text; - *currentCharacter++ = '.'; + *writePtr++ = '.'; decimalPointFound = true; } else @@ -341,7 +359,11 @@ public: } } - c = *text; + if ((! leadingZeros) && (numSigFigs == 0)) + { + text = endOfWhitspace; + return 0.0; + } auto writeExponentDigits = [] (int exponent, char* destination) { @@ -358,19 +380,28 @@ public: *destination++ = (char) ('0' + (char) exponent); }; - if ((c == 'e' || c == 'E') && numSigFigs > 0) + c = *text; + + if (c == 'e' || c == 'E') { - *currentCharacter++ = 'e'; + const auto startOfExponent = text; + *writePtr++ = 'e'; bool parsedExponentIsPositive = true; switch (*++text) { - case '-': parsedExponentIsPositive = false; JUCE_FALLTHROUGH - case '+': ++text; break; - default: break; + case '-': + parsedExponentIsPositive = false; + JUCE_FALLTHROUGH + case '+': + ++text; + break; + default: + break; } int exponent = 0; + const auto startOfExponentDigits = text; while (text.isDigit()) { @@ -380,22 +411,30 @@ public: exponent = (exponent * 10) + digit; } + if (text == startOfExponentDigits) + text = startOfExponent; + exponent = extraExponent + (parsedExponentIsPositive ? exponent : -exponent); if (exponent < 0) - *currentCharacter++ = '-'; + { + if (exponent < std::numeric_limits::min_exponent10 - 1) + return isNegative ? -0.0 : 0.0; - exponent = std::abs (exponent); + *writePtr++ = '-'; + exponent = -exponent; + } + else if (exponent > std::numeric_limits::max_exponent10 + 1) + { + return isNegative ? -inf : inf; + } - if (exponent > std::numeric_limits::max_exponent10) - return std::numeric_limits::quiet_NaN(); - - writeExponentDigits (exponent, currentCharacter); + writeExponentDigits (exponent, writePtr); } else if (extraExponent > 0) { - *currentCharacter++ = 'e'; - writeExponentDigits (extraExponent, currentCharacter); + *writePtr++ = 'e'; + writeExponentDigits (extraExponent, writePtr); } #if JUCE_WINDOWS