1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Fixed some parsing issues in readDoubleValue

This commit is contained in:
Tom Poole 2021-02-01 12:28:54 +00:00
parent 837ab64dbd
commit 7c33b2132f
3 changed files with 418 additions and 101 deletions

View file

@ -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
=============

View file

@ -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<String, double> (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<const char*, double> (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 <typename CharPointerType>
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 <typename FromCharPointerType, typename ToCharPointerType>
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<juce_wchar> 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<MemoryBlock> terminals, nulls;
};
template <typename CharPointerType>
SeparatorStrings getSeparators()
{
jassertfalse;
return {};
}
template <>
SeparatorStrings getSeparators<CharPointer_ASCII>()
{
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<CharPointer_ASCII> (ptr));
const CharPointer_ASCII::CharType* nullCharPtrs[] = { "." };
result.nulls = result.terminals;
for (auto ptr : nullCharPtrs)
result.nulls.push_back (memoryBlockFromCharPtr<CharPointer_ASCII> (ptr));
return result;
}
template <>
SeparatorStrings getSeparators<CharPointer_UTF8>()
{
auto result = getSeparators<CharPointer_ASCII>();
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<CharPointer_UTF8> (ptr);
for (auto vec : { &result.terminals, &result.nulls })
vec->push_back (block);
}
return result;
}
template <typename CharPointerType, typename StorageType>
SeparatorStrings prefixWithAsciiSeparators (const std::vector<std::vector<StorageType>>& terminalCharPtrs)
{
auto asciiSeparators = getSeparators<CharPointer_ASCII>();
SeparatorStrings result;
for (const auto& block : asciiSeparators.terminals)
result.terminals.push_back (convert<CharPointer_ASCII, CharPointerType> (block));
for (const auto& block : asciiSeparators.nulls)
result.nulls.push_back (convert<CharPointer_ASCII, CharPointerType> (block));
for (auto& t : terminalCharPtrs)
{
const auto block = memoryBlockFromCharPtr<CharPointerType> ((typename CharPointerType::CharType*) t.data());
for (auto vec : { &result.terminals, &result.nulls })
vec->push_back (block);
}
return result;
}
template <>
SeparatorStrings getSeparators<CharPointer_UTF16>()
{
const std::vector<std::vector<char16_t>> terminalCharPtrs {
{ 0x0 },
{ 0x0076, 0x0 }, // v
{ 0x20ac, 0x0 }, // €
{ 0xd801, 0xdc37, 0x0 }, // 𐐷
{ 0x0065, 0xd83d, 0xde03, 0x0 }, // e😃
{ 0xd83c, 0xdfc1, 0xd83d, 0xde97, 0x0 } // 🏁🚗
};
return prefixWithAsciiSeparators<CharPointer_UTF16> (terminalCharPtrs);
}
template <>
SeparatorStrings getSeparators<CharPointer_UTF32>()
{
const std::vector<std::vector<char32_t>> terminalCharPtrs = {
{ 0x00000076, 0x0 }, // v
{ 0x000020aC, 0x0 }, // €
{ 0x00010437, 0x0 }, // 𐐷
{ 0x00000065, 0x0001f603, 0x0 }, // e😃
{ 0x0001f3c1, 0x0001f697, 0x0 } // 🏁🚗
};
return prefixWithAsciiSeparators<CharPointer_UTF32> (terminalCharPtrs);
}
template <typename TestFunction>
void withAllPrefixesAndSuffixes (const std::vector<MemoryBlock>& prefixes,
const std::vector<MemoryBlock>& suffixes,
const std::vector<MemoryBlock>& 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 <typename CharPointerType>
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<String, double> testValues[] =
const std::pair<const char*, double> 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<CharPointer_ASCII> (asciiPtr);
return convert<CharPointer_ASCII, CharPointerType> (block, removeNullTerminator);
};
const auto separators = getSeparators<CharPointerType>();
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<const char*>& asciiPtrs, bool removeNullTerminator)
{
String infs[] = { "Inf", "-inf", "INF"};
std::vector<MemoryBlock> result;
for (auto inf : infs)
for (auto* ptr : asciiPtrs)
result.push_back (asciiToMemoryBlock (ptr, removeNullTerminator));
return result;
};
std::vector<const char*> prefixCharPtrs = { "" , "+", "-" };
const auto prefixes = asciiToMemoryBlocks (prefixCharPtrs, true);
{
std::vector<const char*> 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<const char*> 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<double>::infinity()
: std::numeric_limits<double>::infinity();
expectEquals (CharacterFunctions::readDoubleValue (charPtr), expected);
expect (*charPtr == *(CharPointerType ((CharType*) suffix.getData())));
});
}
{
std::vector<const char*> 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<CharPointer_ASCII> characterFunctionsTestsAscii;
static CharacterFunctionsTests<CharPointer_UTF8> characterFunctionsTestsUtf8;
static CharacterFunctionsTests<CharPointer_UTF16> characterFunctionsTestsUtf16;
static CharacterFunctionsTests<CharPointer_UTF32> characterFunctionsTestsUtf32;
}
#endif

View file

@ -146,25 +146,27 @@ public:
template <typename CharPointerType>
static double readDoubleValue (CharPointerType& text) noexcept
{
#if JUCE_MINGW
constexpr auto inf = std::numeric_limits<double>::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<double>::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<double>::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<double>::min_exponent10 - 1)
return isNegative ? -0.0 : 0.0;
exponent = std::abs (exponent);
*writePtr++ = '-';
exponent = -exponent;
}
else if (exponent > std::numeric_limits<double>::max_exponent10 + 1)
{
return isNegative ? -inf : inf;
}
if (exponent > std::numeric_limits<double>::max_exponent10)
return std::numeric_limits<double>::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