From dae5c595b767b94fc2a4d5177815ef22dd1f3f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Tue, 27 Oct 2026 17:17:04 -0700 Subject: [PATCH] Made snprintf %e, %f, %g follow C, sped up fromString(double), strFind and strRFind. (#476) --- include/bx/inline/simd64_ref.inl | 12 +- include/bx/simd_t.h | 2 + src/bitarray.cpp | 1 - src/dtoa.cpp | 402 ++++++++++++++++++++++++++++++- src/string.cpp | 251 +++++++++++++++---- tests/simd_test.cpp | 19 ++ tests/string_test.cpp | 190 +++++++++++++++ tests/vsnprintf_test.cpp | 240 +++++++++++++----- 8 files changed, 1007 insertions(+), 110 deletions(-) diff --git a/include/bx/inline/simd64_ref.inl b/include/bx/inline/simd64_ref.inl index fb13a275..e181c71d 100644 --- a/include/bx/inline/simd64_ref.inl +++ b/include/bx/inline/simd64_ref.inl @@ -458,11 +458,13 @@ namespace bx inline BX_CONSTEXPR_FUNC int simd64_x8_signbitsmask(simd64_t _a) { - int result = 0; - for (int ii = 0; ii < 8; ++ii) - { - result |= (int((_a.u64 >> (ii * 8 + 7)) & 1) << ii); - } + const simd64_t lsb = simd64_splat(uint8_t(1) ); + const simd64_t gather = simd64_splat(UINT64_C(0x0102040810204080) ); + const simd64_t shifted = simd64_x64_srl(_a, 7); + const simd64_t msbs = simd64_and(shifted, lsb); + const simd64_t gathered = simd64_u64_mul(msbs, gather); + const simd64_t top = simd64_x64_srl(gathered, 56); + const int result = int(top.u64); return result; } diff --git a/include/bx/simd_t.h b/include/bx/simd_t.h index dd75e9f9..f46e468c 100644 --- a/include/bx/simd_t.h +++ b/include/bx/simd_t.h @@ -166,6 +166,8 @@ namespace bx typedef simd32_ref_t simd32_t; typedef simd64_ref_t simd64_t; + constexpr bool kSimdSupported = 0 != BX_SIMD_SUPPORTED; + // These deduce register width from the type parameter. Call with // simd128_t for 128-bit, simd256_t for 256-bit, etc. diff --git a/src/bitarray.cpp b/src/bitarray.cpp index 20417ac9..e12c5c4f 100644 --- a/src/bitarray.cpp +++ b/src/bitarray.cpp @@ -20,7 +20,6 @@ namespace bx constexpr uint32_t kNumSimdWords = sizeof(SimdWideT)/sizeof(uint64_t); constexpr uint32_t kNumSimdDwords = sizeof(SimdWideT)/sizeof(uint32_t); constexpr uint32_t kSimdDwordMask = (1u << kNumSimdDwords) - 1; - constexpr bool kSimdSupported = 0 != BX_SIMD_SUPPORTED; struct Match { diff --git a/src/dtoa.cpp b/src/dtoa.cpp index d9f04501..661e828a 100644 --- a/src/dtoa.cpp +++ b/src/dtoa.cpp @@ -585,6 +585,26 @@ namespace bx constexpr uint32_t kFromStringNumLimbs = 96; + constexpr uint32_t kFromStringPow5[] = + { + 1, + 5, + 25, + 125, + 625, + 3125, + 15625, + 78125, + 390625, + 1953125, + 9765625, + 48828125, + 244140625, + 1220703125, + }; + + constexpr int32_t kFromStringPow5Max = BX_COUNTOF(kFromStringPow5) - 1; + struct FloatBig { uint32_t num; @@ -821,6 +841,377 @@ namespace bx return _negative ? -scaled : scaled; } + static void floatBigShiftRight(FloatBig& _big, uint32_t _bits, bool& _sticky) + { + const uint32_t words = _bits / 64; + const uint32_t bits = _bits % 64; + + for (uint32_t ii = 0; ii < words && ii < _big.num; ++ii) + { + _sticky = _sticky || 0 != _big.limb[ii]; + } + + if (_big.num <= words) + { + _big.num = 0; + return; + } + + const uint32_t num = _big.num - words; + + for (uint32_t ii = 0; ii < num; ++ii) + { + _big.limb[ii] = _big.limb[ii + words]; + } + + if (0 != bits) + { + _sticky = _sticky || 0 != (_big.limb[0] & ( (UINT64_C(1) << bits) - 1) ); + + for (uint32_t ii = 0; ii < num; ++ii) + { + const uint64_t next = ii + 1 < num ? _big.limb[ii + 1] : 0; + _big.limb[ii] = (_big.limb[ii] >> bits) | (next << (64 - bits) ); + } + } + + _big.num = num; + + while (0 != _big.num + && 0 == _big.limb[_big.num - 1]) + { + --_big.num; + } + } + + static bool floatBigIsOdd(const FloatBig& _big) + { + return 0 != _big.num + && 0 != (_big.limb[0] & 1) + ; + } + + static int32_t floatBigScaledDigits(char* _out, int32_t _max, double _value, int32_t _scale) + { + const uint64_t bits = doubleToBits(_value); + const uint64_t frac = bits & kDoubleMantissaMask; + const int32_t biased = int32_t( (bits & kDoubleExponentMask) >> kDoubleExponentShift); + + const uint64_t mantissa = 0 == biased ? frac : frac | (UINT64_C(1) << kDoubleMantissaNumBits); + const int32_t binExp = (0 == biased ? 1 : biased) - int32_t(kDoubleExponentBias) - kDoubleMantissaNumBits; + + FloatBig big; + big.num = 1; + big.limb[0] = mantissa; + + if (0 == mantissa) + { + big.num = 0; + } + + bool sticky = false; + + for (int32_t left = _scale; 0 < left; left -= kFromStringPow5Max) + { + if (!floatBigMulAdd(big, kFromStringPow5[min(left, kFromStringPow5Max)], 0) ) + { + return -1; + } + } + + const int32_t shift = binExp + _scale + 1; + + if (0 < shift + && !floatBigShiftLeft(big, uint32_t(shift) ) ) + { + return -1; + } + + for (int32_t left = -_scale; 0 < left; left -= kFromStringPow5Max) + { + const uint32_t rem = floatBigDivSmall(big, kFromStringPow5[min(left, kFromStringPow5Max)]); + + sticky = sticky || 0 != rem; + } + + if (0 > shift) + { + floatBigShiftRight(big, uint32_t(-shift), sticky); + } + + const bool half = floatBigIsOdd(big); + + bool halfOut = false; + floatBigShiftRight(big, 1, halfOut); + + if (half + && (sticky || floatBigIsOdd(big) ) ) + { + if (0 == big.num) + { + big.num = 1; + big.limb[0] = 0; + } + + floatBigMulAdd(big, 1, 1); + } + + constexpr uint32_t kChunk = 1000000000; + constexpr int32_t kChunkDigits = 9; + + uint32_t chunks[kFromStringNumLimbs*64/29 + 1]; + int32_t numChunks = 0; + + while (0 != big.num) + { + chunks[numChunks++] = floatBigDivSmall(big, kChunk); + } + + if (0 == numChunks) + { + chunks[numChunks++] = 0; + } + + char top[kChunkDigits]; + int32_t topLen = 0; + + for (uint32_t value = chunks[numChunks - 1]; 0 == topLen || 0 != value; value /= 10) + { + top[topLen++] = char('0' + value % 10); + } + + const int32_t len = topLen + (numChunks - 1)*kChunkDigits; + + if (len > _max) + { + return -1; + } + + int32_t pos = 0; + + for (int32_t ii = topLen; ii > 0; --ii) + { + _out[pos++] = top[ii - 1]; + } + + for (int32_t ii = numChunks - 1; ii > 0; --ii) + { + uint32_t value = chunks[ii - 1]; + + for (int32_t jj = kChunkDigits; jj > 0; --jj) + { + _out[pos + jj - 1] = char('0' + value % 10); + value /= 10; + } + + pos += kChunkDigits; + } + + return len; + } + + static int32_t decimalExponentEstimate(double _value) + { + const uint64_t bits = doubleToBits(_value); + const uint64_t frac = bits & kDoubleMantissaMask; + const int32_t biased = int32_t( (bits & kDoubleExponentMask) >> kDoubleExponentShift); + + const int32_t topBit = 0 == biased + ? 63 - int32_t(countLeadingZeros(frac) ) + 1 - int32_t(kDoubleExponentBias) - kDoubleMantissaNumBits + : biased - int32_t(kDoubleExponentBias) + ; + + return int32_t(floor(double(topBit) * 0.30102999566398119521) ); + } + + static int32_t floatSignificantDigits(char* _out, int32_t _max, double _value, int32_t _count, int32_t& _exp) + { + int32_t exp = decimalExponentEstimate(_value); + + int32_t len = floatBigScaledDigits(_out, _max, _value, _count - 1 - exp); + + if (len > _count) + { + ++exp; + len = floatBigScaledDigits(_out, _max, _value, _count - 1 - exp); + } + + if (len > _count) + { + ++exp; + len = _count; + } + + _exp = exp; + + return len; + } + + static int32_t writeExponent(char* _out, int32_t _exp) + { + int32_t pos = 0; + _out[pos++] = 'e'; + _out[pos++] = 0 > _exp ? '-' : '+'; + + const int32_t value = 0 > _exp ? -_exp : _exp; + + if (100 <= value) + { + _out[pos++] = char('0' + value / 100); + } + + _out[pos++] = char('0' + value / 10 % 10); + _out[pos++] = char('0' + value % 10); + + return pos; + } + + int32_t toStringFormatted(char* _out, int32_t _max, double _value, char _fmt, int32_t _prec, bool _alt) + { + constexpr int32_t kMaxDigits = 1100; + + if (!isFinite(_value) ) + { + return toString(_out, _max, _value); + } + + char digits[kMaxDigits]; + char tmp[kMaxDigits + 32]; + int32_t pos = 0; + + if (0 != (doubleToBits(_value) & kDoubleSignMask) ) + { + tmp[pos++] = '-'; + } + + const bool zero = 0.0 == _value; + int32_t prec = min(_prec, kMaxDigits - 2); + char fmt = _fmt; + bool strip = false; + + if ('g' == fmt) + { + const int32_t count = max(prec, 1); + + int32_t exp = 0; + + if (!zero + && 0 > floatSignificantDigits(digits, kMaxDigits, _value, count, exp) ) + { + return toString(_out, _max, _value); + } + + if (count > exp + && -4 <= exp) + { + fmt = 'f'; + prec = count - 1 - exp; + } + else + { + fmt = 'e'; + prec = count - 1; + } + + strip = !_alt; + } + + if ('e' == fmt) + { + int32_t exp = 0; + int32_t len = 1; + digits[0] = '0'; + + if (!zero) + { + len = floatSignificantDigits(digits, kMaxDigits, _value, prec + 1, exp); + + if (0 > len) + { + return toString(_out, _max, _value); + } + } + + tmp[pos++] = digits[0]; + + if (0 < prec + || _alt) + { + tmp[pos++] = '.'; + } + + for (int32_t ii = 1; ii <= prec; ++ii) + { + tmp[pos++] = ii < len ? digits[ii] : '0'; + } + + if (strip + && 0 < prec) + { + while ('0' == tmp[pos - 1]) + { + --pos; + } + + if ('.' == tmp[pos - 1]) + { + --pos; + } + } + + pos += writeExponent(&tmp[pos], exp); + } + else + { + const int32_t len = floatBigScaledDigits(digits, kMaxDigits, _value, prec); + + if (0 > len) + { + return toString(_out, _max, _value); + } + + const int32_t intLen = len - prec; + + if (0 >= intLen) + { + tmp[pos++] = '0'; + } + + for (int32_t ii = 0; ii < intLen; ++ii) + { + tmp[pos++] = digits[ii]; + } + + if (0 < prec + || _alt) + { + tmp[pos++] = '.'; + } + + for (int32_t ii = 0; ii < prec; ++ii) + { + const int32_t at = intLen + ii; + tmp[pos++] = 0 > at ? '0' : digits[at]; + } + + if (strip + && 0 < prec) + { + while ('0' == tmp[pos - 1]) + { + --pos; + } + + if ('.' == tmp[pos - 1]) + { + --pos; + } + } + } + + return int32_t(strCopy(_out, _max, StringView(tmp, pos) ) ); + } + int32_t toString(char* _out, int32_t _max, bool _value) { StringView str(_value ? "true" : "false"); @@ -1059,9 +1450,11 @@ namespace bx if (0 <= decExp) { - for (int32_t ii = 0; ii < decExp; ++ii) + for (int32_t left = decExp; 0 < left; left -= kFromStringPow5Max) { - if (!floatBigMulAdd(big, 5, 0) ) + const int32_t step = min(left, kFromStringPow5Max); + + if (!floatBigMulAdd(big, kFromStringPow5[step], 0) ) { *_out = negative ? -kDoubleInfinity : kDoubleInfinity; return true; @@ -1081,9 +1474,10 @@ namespace bx binExp -= int32_t(prescale); - for (int32_t ii = 0; ii < count; ++ii) + for (int32_t left = count; 0 < left; left -= kFromStringPow5Max) { - const uint32_t rem = floatBigDivSmall(big, 5); + const int32_t step = min(left, kFromStringPow5Max); + const uint32_t rem = floatBigDivSmall(big, kFromStringPow5[step]); sticky = sticky || (0 != rem); } diff --git a/src/string.cpp b/src/string.cpp index bdf9a2be..127e2f0e 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include namespace bx @@ -130,8 +132,109 @@ namespace bx return strCat(_dst, _dstSize, _str.getPtr(), min(_str.getLength(), _num) ); } + inline uint32_t matchBytes(const char* _str, simd64_t _pattern) + { + const uint64_t word = loadUnaligned(_str); + const simd64_t chunk = simd64_splat(word); + const simd64_t low7 = simd64_splat(uint8_t(0x7f) ); + const simd64_t diff = simd64_xor(chunk, _pattern); + const simd64_t lowBits = simd64_and(diff, low7); + const simd64_t carry = simd64_u64_add(lowBits, low7); + const simd64_t nonZero = simd64_or(carry, diff); + const simd64_t equal = simd64_not(nonZero); + const int mask = simd64_x8_signbitsmask(equal); + return uint32_t(mask); + } + + inline uint32_t matchBytes(const char* _str, simd128_t _pattern) + { + const simd128_t chunk = simd_ldu(_str); + const simd128_t equal = simd_i8_cmpeq(chunk, _pattern); + const int mask = simd_x8_signbitsmask(equal); + return uint32_t(mask); + } + + inline bool matchAny(const char* _str, simd128_t _pattern) + { + const simd128_t chunk0 = simd_ldu(&_str[ 0]); + const simd128_t chunk1 = simd_ldu(&_str[16]); + const simd128_t chunk2 = simd_ldu(&_str[32]); + const simd128_t chunk3 = simd_ldu(&_str[48]); + const simd128_t equal0 = simd_i8_cmpeq(chunk0, _pattern); + const simd128_t equal1 = simd_i8_cmpeq(chunk1, _pattern); + const simd128_t equal2 = simd_i8_cmpeq(chunk2, _pattern); + const simd128_t equal3 = simd_i8_cmpeq(chunk3, _pattern); + const simd128_t any01 = simd_or(equal0, equal1); + const simd128_t any23 = simd_or(equal2, equal3); + const simd128_t any = simd_or(any01, any23); + const int mask = simd_x8_signbitsmask(any); + return 0 != mask; + } + inline const char* strFindUnsafe(const char* _str, int32_t _len, char _ch) { + if constexpr (kSimdSupported) + { + if (16 <= _len) + { + const uint32_t splat = uint32_t(uint8_t(_ch) ) * 0x01010101u; + const simd128_t pattern = simd_splat(splat); + + int32_t ii = 0; + + for (; ii + 64 <= _len && !matchAny(&_str[ii], pattern); ii += 64) + { + } + + for (; ii + 16 <= _len; ii += 16) + { + const uint32_t mask = matchBytes(&_str[ii], pattern); + + if (0 != mask) + { + return &_str[ii + countTrailingZeros(mask)]; + } + } + + if (ii == _len) + { + return NULL; + } + + const int32_t at = _len - 16; + const uint32_t mask = matchBytes(&_str[at], pattern); + + return 0 != mask ? &_str[at + countTrailingZeros(mask)] : NULL; + } + } + + if (8 <= _len) + { + const simd64_t pattern = simd64_splat(uint8_t(_ch) ); + + int32_t ii = 0; + + for (; ii + 8 <= _len; ii += 8) + { + const uint32_t mask = matchBytes(&_str[ii], pattern); + + if (0 != mask) + { + return &_str[ii + countTrailingZeros(mask)]; + } + } + + if (ii == _len) + { + return NULL; + } + + const int32_t at = _len - 8; + const uint32_t mask = matchBytes(&_str[at], pattern); + + return 0 != mask ? &_str[at + countTrailingZeros(mask)] : NULL; + } + for (int32_t ii = 0; ii < _len; ++ii) { if (_str[ii] == _ch) @@ -161,6 +264,66 @@ namespace bx inline const char* strRFindUnsafe(const char* _str, int32_t _len, char _ch) { + if constexpr (kSimdSupported) + { + if (16 <= _len) + { + const uint32_t splat = uint32_t(uint8_t(_ch) ) * 0x01010101u; + const simd128_t pattern = simd_splat(splat); + + int32_t end = _len; + + for (; 64 <= end && !matchAny(&_str[end - 64], pattern); end -= 64) + { + } + + for (; 16 <= end; end -= 16) + { + const uint32_t mask = matchBytes(&_str[end - 16], pattern); + + if (0 != mask) + { + return &_str[end - 16 + 31 - countLeadingZeros(mask)]; + } + } + + if (0 == end) + { + return NULL; + } + + const uint32_t mask = matchBytes(_str, pattern); + + return 0 != mask ? &_str[31 - countLeadingZeros(mask)] : NULL; + } + } + + if (8 <= _len) + { + const simd64_t pattern = simd64_splat(uint8_t(_ch) ); + + int32_t end = _len; + + for (; 8 <= end; end -= 8) + { + const uint32_t mask = matchBytes(&_str[end - 8], pattern); + + if (0 != mask) + { + return &_str[end - 8 + 31 - countLeadingZeros(mask)]; + } + } + + if (0 == end) + { + return NULL; + } + + const uint32_t mask = matchBytes(_str, pattern); + + return 0 != mask ? &_str[31 - countLeadingZeros(mask)] : NULL; + } + for (int32_t ii = _len-1; 0 <= ii; --ii) { if (_str[ii] == _ch) @@ -215,11 +378,41 @@ namespace bx return NULL; } + inline const char* strFindExact(const char* _str, int32_t _strLen, const char* _find, int32_t _findLen) + { + if (0 >= _findLen + || _findLen > _strLen) + { + return NULL; + } + + const char* term = _str + _strLen; + const char* last = term - _findLen; + + for (const char* ptr = _str; ptr <= last; ++ptr) + { + ptr = strFindUnsafe(ptr, int32_t(term - ptr), _find[0]); + + if (NULL == ptr + || ptr > last) + { + return NULL; + } + + if (0 == memCmp(ptr, _find, _findLen) ) + { + return ptr; + } + } + + return NULL; + } + StringView strFind(const StringView& _str, const StringView& _find, int32_t _num) { int32_t len = min(_find.getLength(), _num); - const char* ptr = strFind( + const char* ptr = strFindExact( _str.getPtr() , _str.getLength() , _find.getPtr() @@ -535,6 +728,8 @@ namespace bx return StringView(_str.getTerm(), _str.getTerm() ); } + int32_t toStringFormatted(char* _out, int32_t _max, double _value, char _fmt, int32_t _prec, bool _alt); + namespace { struct Param @@ -801,60 +996,24 @@ namespace bx static int32_t write(WriterI* _writer, double _d, const Param& _param, Error* _err) { - char str[1024]; - int32_t len = toString(str, sizeof(str), _d); + char str[1152]; + const int32_t prec = INT32_MAX == _param.prec ? 6 : _param.prec; + const int32_t len = toStringFormatted(str, sizeof(str), _d, _param.fmt, prec, _param.spec); if (len == 0) { return 0; } - const char* dot = strFind(str, INT32_MAX, '.'); - if (NULL != dot) + Param param = _param; + param.prec = INT32_MAX; + + if (!isFinite(_d) ) { - const int32_t defaultPrec = ('g' == _param.fmt) ? 6 : 6; - int32_t prec = INT32_MAX == _param.prec ? defaultPrec : _param.prec; - - const char* strEnd = str + len; - const char* exponent = strFind(str, INT32_MAX, 'e'); - const char* fracEnd = NULL != exponent ? exponent : strEnd; - - char* fracBegin = &str[dot - str + min(prec + _param.spec, 1)]; - const int32_t curPrec = int32_t(fracEnd - fracBegin); - - const int32_t exponentLen = int32_t(strEnd - fracEnd); - const int32_t maxPrec = int32_t(sizeof(str) ) - 1 - - int32_t(fracBegin - str) - - exponentLen - ; - - const int32_t reqPrec = 'g' == _param.fmt ? min(prec, curPrec) : prec; - const int32_t outPrec = min(reqPrec, maxPrec); - - // Move exponent to its final location after trimming or adding extra 0s. - if (fracEnd != strEnd) - { - char* finalExponentPtr = &fracBegin[outPrec]; - memMove(finalExponentPtr, fracEnd, exponentLen); - - finalExponentPtr[exponentLen] = '\0'; - len = int32_t(&finalExponentPtr[exponentLen] - str); - } - else - { - len = (int32_t)(fracBegin + outPrec - str); - } - - if (curPrec < outPrec) - { - for (int32_t ii = curPrec; ii < outPrec; ++ii) - { - fracBegin[ii] = '0'; - } - } + param.fill = ' '; } - return write(_writer, str, len, _param, _err); + return write(_writer, str, len, param, _err); } static int32_t write(WriterI* _writer, const void* _ptr, const Param& _param, Error* _err) diff --git a/tests/simd_test.cpp b/tests/simd_test.cpp index 81314b88..7d600c70 100644 --- a/tests/simd_test.cpp +++ b/tests/simd_test.cpp @@ -2234,6 +2234,25 @@ TEST_CASE("simd128_orc_orx_sels", "[simd]") check_u32("sels", simd128_sels(neg, sa, sb), 1, 2, 1, 2); } +TEST_CASE("simd64_x8_signbitsmask", "[simd]") +{ + bx::RngMwc rng; + + for (uint32_t ii = 0; ii < 256; ++ii) + { + uint64_t value = 0; + + for (uint32_t jj = 0; jj < 8; ++jj) + { + const uint64_t low = rng.gen() & 0x7f; + const uint64_t sign = (ii >> jj) & 1; + value |= (low | (sign << 7) ) << (jj * 8); + } + + REQUIRE(int(ii) == simd64_x8_signbitsmask(simd64_splat(value) ) ); + } +} + TEST_CASE("simd128_signbits", "[simd]") { REQUIRE(simd128_x32_signbitsmask(simd128_ld(0x80000000u, 0u, 0x80000000u, 0u)) == 0x5); diff --git a/tests/string_test.cpp b/tests/string_test.cpp index 6f97820e..94a4420e 100644 --- a/tests/string_test.cpp +++ b/tests/string_test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -325,6 +326,98 @@ TEST_CASE("strFind", "[string]") } } +TEST_CASE("strFind edges", "[string]") +{ + const char* test = "0123456789abcdefXYZ0123456789abcdefghij"; + const bx::StringView view(test); + + REQUIRE(&test[0] == bx::strFind(view, "0123").getPtr() ); + REQUIRE(&test[35] == bx::strFind(view, "ghij").getPtr() ); + REQUIRE(&test[38] == bx::strFind(view, 'j').getPtr() ); + REQUIRE(&test[19] == bx::strRFind(view, '0').getPtr() ); + REQUIRE(&test[15] == bx::strFind(view, "fXY").getPtr() ); + REQUIRE(&test[16] == bx::strFind(view, 'X').getPtr() ); + REQUIRE(&test[16] == bx::strRFind(view, 'X').getPtr() ); + + REQUIRE(bx::strFind(bx::StringView(test, 4), "01234").isEmpty() ); + REQUIRE(bx::strFind(view, "").isEmpty() ); + REQUIRE(bx::strFind(bx::StringView(), "a").isEmpty() ); + REQUIRE(bx::strFind(view, 'q').isEmpty() ); + REQUIRE(bx::strRFind(view, 'q').isEmpty() ); + + REQUIRE(&test[16] == bx::strFind(view, "XYQ", 2).getPtr() ); + REQUIRE(2 == bx::strFind(view, "XYQ", 2).getLength() ); + REQUIRE(bx::strFind(view, "XYQ").isEmpty() ); + + REQUIRE(&test[21] == bx::strFind(bx::StringView(&test[3], 36), "2345").getPtr() ); + REQUIRE(&test[3] == bx::strFind(bx::StringView(&test[3], 36), "3456").getPtr() ); +} + +static const char* strFindRef(const char* _str, int32_t _strLen, const char* _find, int32_t _findLen) +{ + for (int32_t ii = 0; 0 < _findLen && ii + _findLen <= _strLen; ++ii) + { + if (0 == bx::memCmp(&_str[ii], _find, _findLen) ) + { + return &_str[ii]; + } + } + + return NULL; +} + +static const char* strRFindRef(const char* _str, int32_t _strLen, char _ch) +{ + for (int32_t ii = _strLen - 1; 0 <= ii; --ii) + { + if (_ch == _str[ii]) + { + return &_str[ii]; + } + } + + return NULL; +} + +TEST_CASE("strFind matches a byte loop", "[string]") +{ + bx::RngMwc rng; + + char buffer[176]; + + for (uint32_t ii = 0; ii < 100000; ++ii) + { + const int32_t offset = int32_t(rng.gen() % 16); + const int32_t len = int32_t(rng.gen() % 151); + const int32_t findLen = int32_t(rng.gen() % 5); + + for (int32_t jj = 0; jj < len; ++jj) + { + buffer[offset + jj] = char('a' + rng.gen() % 3); + } + + char find[4]; + for (int32_t jj = 0; jj < findLen; ++jj) + { + find[jj] = char('a' + rng.gen() % 3); + } + + const char* str = &buffer[offset]; + const bx::StringView view(str, len); + + const char* expected = strFindRef(str, len, find, findLen); + const bx::StringView found = bx::strFind(view, bx::StringView(find, findLen) ); + REQUIRE( (NULL == expected ? view.getTerm() : expected) == found.getPtr() ); + + const char ch = char('a' + rng.gen() % 4); + const char* expectedCh = strFindRef(str, len, &ch, 1); + REQUIRE( (NULL == expectedCh ? view.getTerm() : expectedCh) == bx::strFind(view, ch).getPtr() ); + + const char* expectedRCh = strRFindRef(str, len, ch); + REQUIRE( (NULL == expectedRCh ? view.getTerm() : expectedRCh) == bx::strRFind(view, ch).getPtr() ); + } +} + TEST_CASE("strSkip", "[string]") { const bx::StringView t0(" test X"); @@ -663,6 +756,32 @@ TEST_CASE("fromString long digit strings", "[string]") REQUIRE(d == 1.0); } +TEST_CASE("fromString round trip across every exponent", "[string]") +{ + bx::RngMwc rng; + + for (uint32_t ii = 0; ii < 100000; ++ii) + { + const uint64_t hi = rng.gen(); + const uint64_t lo = rng.gen(); + const uint64_t bits = (hi << 32) | lo; + const double value = bx::bitsToDouble(bits); + + if (!bx::isFinite(value) ) + { + continue; + } + + char tmp[64]; + bx::toString(tmp, BX_COUNTOF(tmp), value); + INFO(tmp); + + double read = 0.0; + REQUIRE(bx::fromString(&read, tmp) ); + REQUIRE(bx::doubleToBits(read) == bits); + } +} + TEST_CASE("fromString hex wide mantissa", "[string]") { double d = 0.0; @@ -1192,3 +1311,74 @@ TEST_CASE("UTF-16 conversion", "[string][utf8]") REQUIRE(0 == bx::fromUtf16(utf8, BX_COUNTOF(utf8), kLoneHigh) ); REQUIRE(0 == bx::fromUtf16(utf8, BX_COUNTOF(utf8), kLoneLow) ); } + +TEST_CASE("strFind-bench", "[.][!benchmark]") +{ + char haystack[1025]; + bx::memSet(haystack, 'a', sizeof(haystack) - 1); + haystack[sizeof(haystack) - 1] = '\0'; + + for (int32_t len : { 12, 16, 1024 }) + { + haystack[1] = 'W'; + haystack[len - 2] = 'X'; + haystack[len - 1] = 'Y'; + + const bx::StringView view(haystack, len); + const std::string str(haystack, len); + + char name[64]; + + bx::snprintf(name, sizeof(name), "bx::strFind %d", len); + BENCHMARK(name) + { + return bx::strFind(view, "XY").getPtr(); + }; + + bx::snprintf(name, sizeof(name), "std::string::find %d", len); + BENCHMARK(name) + { + return str.find("XY"); + }; + + bx::snprintf(name, sizeof(name), "bx::strFind not found %d", len); + BENCHMARK(name) + { + return bx::strFind(view, "ZY").getPtr(); + }; + + bx::snprintf(name, sizeof(name), "std::string::find not found %d", len); + BENCHMARK(name) + { + return str.find("ZY"); + }; + + bx::snprintf(name, sizeof(name), "bx::strFind char %d", len); + BENCHMARK(name) + { + return bx::strFind(view, 'Y').getPtr(); + }; + + bx::snprintf(name, sizeof(name), "std::string::find char %d", len); + BENCHMARK(name) + { + return str.find('Y'); + }; + + bx::snprintf(name, sizeof(name), "bx::strRFind char %d", len); + BENCHMARK(name) + { + return bx::strRFind(view, 'W').getPtr(); + }; + + bx::snprintf(name, sizeof(name), "std::string::rfind char %d", len); + BENCHMARK(name) + { + return str.rfind('W'); + }; + + haystack[1] = 'a'; + haystack[len - 2] = 'a'; + haystack[len - 1] = 'a'; + } +} diff --git a/tests/vsnprintf_test.cpp b/tests/vsnprintf_test.cpp index b59982a5..f4a44689 100644 --- a/tests/vsnprintf_test.cpp +++ b/tests/vsnprintf_test.cpp @@ -6,6 +6,7 @@ #include "test.h" #include #include +#include #include #include @@ -111,7 +112,7 @@ static bool test(const char* _expected, const char* _format, ...) { va_list argList; va_start(argList, _format); - const bool result = test(_expected, _format, argList); + const bool result = test(_expected, _format, argList); va_end(argList); return result; @@ -143,7 +144,7 @@ TEST_CASE("Format %f", "[string][printf]") REQUIRE(test("nan ", "%-8f", kDoubleNan) ); REQUIRE(test(" nan", "%8f", kDoubleNan) ); - REQUIRE(test("-NAN ", "%-8F", -kDoubleNan) ); + REQUIRE(testNotStdCompliant("-NAN ", "%-8F", -kDoubleNan) ); REQUIRE(test(" inf", "%8f", bx::kDoubleInfinity) ); REQUIRE(test("inf ", "%-8f", bx::kDoubleInfinity) ); @@ -157,63 +158,63 @@ TEST_CASE("Format %f", "[string][printf]") REQUIRE(test("0.0039", "%.4f", 0.00390625) ); REQUIRE(test("0.003906", "%f", 0.00390625) ); - REQUIRE(test("-1.234567e-9", "%f", -1.234567e-9) ); + REQUIRE(test("-0.000000", "%f", -1.234567e-9) ); - REQUIRE(test("-1e-9", "%.0f", -1.234567e-9) ); - REQUIRE(test("-1.2e-9", "%.1f", -1.234567e-9) ); - REQUIRE(test("-1.23e-9", "%.2f", -1.234567e-9) ); - REQUIRE(test("-1.234e-9", "%.3f", -1.234567e-9) ); - REQUIRE(test("-1.2345e-9", "%.4f", -1.234567e-9) ); - REQUIRE(test("-1.23456e-9", "%.5f", -1.234567e-9) ); - REQUIRE(test("-1.234567e-9", "%.6f", -1.234567e-9) ); - REQUIRE(test("-1.2345670e-9", "%.7f", -1.234567e-9) ); - REQUIRE(test("-1.23456700e-9", "%.8f", -1.234567e-9) ); - REQUIRE(test("-1.234567000e-9", "%.9f", -1.234567e-9) ); - REQUIRE(test("-1.2345670000e-9", "%.10f", -1.234567e-9) ); + REQUIRE(test("-0", "%.0f", -1.234567e-9) ); + REQUIRE(test("-0.0", "%.1f", -1.234567e-9) ); + REQUIRE(test("-0.00", "%.2f", -1.234567e-9) ); + REQUIRE(test("-0.000", "%.3f", -1.234567e-9) ); + REQUIRE(test("-0.0000", "%.4f", -1.234567e-9) ); + REQUIRE(test("-0.00000", "%.5f", -1.234567e-9) ); + REQUIRE(test("-0.000000", "%.6f", -1.234567e-9) ); + REQUIRE(test("-0.0000000", "%.7f", -1.234567e-9) ); + REQUIRE(test("-0.00000000", "%.8f", -1.234567e-9) ); + REQUIRE(test("-0.000000001", "%.9f", -1.234567e-9) ); + REQUIRE(test("-0.0000000012", "%.10f", -1.234567e-9) ); - REQUIRE(test("3.141592", "%f", 3.1415926535897932) ); - REQUIRE(test("3.141592", "%F", 3.1415926535897932) ); + REQUIRE(test("3.141593", "%f", 3.1415926535897932) ); + REQUIRE(test("3.141593", "%F", 3.1415926535897932) ); REQUIRE(test("3", "%.0f", 3.1415926535897932) ); REQUIRE(test("3.1", "%.1f", 3.1415926535897932) ); REQUIRE(test("3.14", "%.2f", 3.1415926535897932) ); - REQUIRE(test("3.141", "%.3f", 3.1415926535897932) ); - REQUIRE(test("3.1415", "%.4f", 3.1415926535897932) ); + REQUIRE(test("3.142", "%.3f", 3.1415926535897932) ); + REQUIRE(test("3.1416", "%.4f", 3.1415926535897932) ); REQUIRE(test("3.14159", "%.5f", 3.1415926535897932) ); - REQUIRE(test("3.141592", "%.6f", 3.1415926535897932) ); - REQUIRE(test("3.1415926", "%.7f", 3.1415926535897932) ); + REQUIRE(test("3.141593", "%.6f", 3.1415926535897932) ); + REQUIRE(test("3.1415927", "%.7f", 3.1415926535897932) ); REQUIRE(test("3.14159265", "%.8f", 3.1415926535897932) ); - REQUIRE(test("3.141592653", "%.9f", 3.1415926535897932) ); - REQUIRE(test("3.1415926535", "%.10f", 3.1415926535897932) ); - REQUIRE(test("3.14159265358", "%.11f", 3.1415926535897932) ); - REQUIRE(test("3.141592653589", "%.12f", 3.1415926535897932) ); - REQUIRE(test("3.1415926535897", "%.13f", 3.1415926535897932) ); + REQUIRE(test("3.141592654", "%.9f", 3.1415926535897932) ); + REQUIRE(test("3.1415926536", "%.10f", 3.1415926535897932) ); + REQUIRE(test("3.14159265359", "%.11f", 3.1415926535897932) ); + REQUIRE(test("3.141592653590", "%.12f", 3.1415926535897932) ); + REQUIRE(test("3.1415926535898", "%.13f", 3.1415926535897932) ); REQUIRE(test("3.14159265358979", "%.14f", 3.1415926535897932) ); REQUIRE(test("3.141592653589793", "%.15f", 3.1415926535897932) ); - REQUIRE(test("3.1415926535897930", "%.16f", 3.1415926535897932) ); - REQUIRE(test("3.1415926535897930", "%.16F", 3.1415926535897932) ); + REQUIRE(test("3.1415926535897931", "%.16f", 3.1415926535897932) ); + REQUIRE(test("3.1415926535897931", "%.16F", 3.1415926535897932) ); - REQUIRE(test("-3.141592e-9", "%f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.141592E-9", "%F", -3.1415926535897932e-9) ); - REQUIRE(test("-3e-9", "%.0f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.1e-9", "%.1f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.14e-9", "%.2f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.141e-9", "%.3f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.1415e-9", "%.4f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.14159e-9", "%.5f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.141592e-9", "%.6f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.1415926e-9", "%.7f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.14159265e-9", "%.8f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.141592653e-9", "%.9f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.1415926535e-9", "%.10f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.14159265358e-9", "%.11f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.141592653589e-9", "%.12f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.1415926535897e-9", "%.13f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.14159265358979e-9", "%.14f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.141592653589793e-9", "%.15f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.1415926535897930e-9", "%.16f", -3.1415926535897932e-9) ); - REQUIRE(test("-3.1415926535897930E-9", "%.16F", -3.1415926535897932e-9) ); + REQUIRE(test("-0.000000", "%f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.000000", "%F", -3.1415926535897932e-9) ); + REQUIRE(test("-0", "%.0f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.0", "%.1f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.00", "%.2f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.000", "%.3f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.0000", "%.4f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.00000", "%.5f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.000000", "%.6f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.0000000", "%.7f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.00000000", "%.8f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.000000003", "%.9f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.0000000031", "%.10f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.00000000314", "%.11f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.000000003142", "%.12f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.0000000031416", "%.13f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.00000000314159", "%.14f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.000000003141593", "%.15f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.0000000031415927", "%.16f", -3.1415926535897932e-9) ); + REQUIRE(test("-0.0000000031415927", "%.16F", -3.1415926535897932e-9) ); - REQUIRE(test("1e-12", "%f", 1e-12)); + REQUIRE(test("0.000000", "%f", 1e-12)); REQUIRE(test("0.00390625", "%.8f", 0.00390625) ); REQUIRE(test("-0.00390625", "%.8f", -0.00390625) ); @@ -278,7 +279,7 @@ TEST_CASE("Format %d, %i, %o, %u, %x", "[string][printf]") REQUIRE(test(" 1", "% 4i", 1) ); REQUIRE(test(" 1", "% 4o", 1) ); REQUIRE(test(" +1", "%+4i", 1) ); - REQUIRE(test(" +1", "%+4o", 1) ); + REQUIRE(testNotStdCompliant(" +1", "%+4o", 1) ); REQUIRE(test(" +0", "%+4i", 0) ); REQUIRE(test(" -1", "%+4i", -1) ); REQUIRE(test("0001", "%04i", 1) ); @@ -329,8 +330,8 @@ TEST_CASE("Format modifiers", "[string][printf]") TEST_CASE("Format %p", "[string][printf]") { - REQUIRE(test("0xbadc0de", "%p", (void*)0xbadc0de) ); - REQUIRE(test("0xbadc0de ", "%-20p", (void*)0xbadc0de) ); + REQUIRE(testNotStdCompliant("0xbadc0de", "%p", (void*)0xbadc0de) ); + REQUIRE(testNotStdCompliant("0xbadc0de ", "%-20p", (void*)0xbadc0de) ); } TEST_CASE("Format %s", "[string][printf]") @@ -369,9 +370,85 @@ TEST_CASE("Format %g", "[string][printf]") { REQUIRE(test(" 0.01", "%7.2g", .01) ); REQUIRE(test(" 0.0123", "%7.4G", .0123) ); -// REQUIRE(test("1.23e+05", "%.3g", 123000.25) ); -// REQUIRE(test("1e+05", "%.0g", 123000.25) ); + REQUIRE(test("1.23e+05", "%.3g", 123000.25) ); + REQUIRE(test("1e+05", "%.0g", 123000.25) ); REQUIRE(test("1", "%.0g", 1.2) ); + + REQUIRE(test("100000", "%g", 100000.0) ); + REQUIRE(test("1e+06", "%g", 1000000.0) ); + REQUIRE(test("0.0001", "%g", 0.0001) ); + REQUIRE(test("1e-05", "%g", 0.00001) ); + REQUIRE(test("1.00000", "%#g", 1.0) ); + REQUIRE(test("0", "%g", 0.0) ); + REQUIRE(test("3.14159", "%g", 3.1415926535897932) ); + REQUIRE(test("0.10000000000000001", "%.17g", 0.1) ); + REQUIRE(test("1E-10", "%G", 1e-10) ); +} + +TEST_CASE("Format %e", "[string][printf]") +{ + REQUIRE(test("1.337000e+00", "%e", 1.337) ); + REQUIRE(test("-1.234567e-09", "%e", -1.234567e-9) ); + REQUIRE(test("1.500000E+00", "%E", 1.5) ); + REQUIRE(test("0.000000e+00", "%e", 0.0) ); + REQUIRE(test("1.798e+308", "%.3e", 1.7976931348623157e308) ); + REQUIRE(test("4.941e-324", "%.3e", 4.9406564584124654e-324) ); + REQUIRE(test("1e+01", "%.0e", 9.5) ); + REQUIRE(test("3.e+00", "%#.0e", 3.0) ); + REQUIRE(test(" 1.234e+03", "%12.3e", 1234.5) ); +} + +TEST_CASE("Format %e, %f, %g of random doubles match the CRT", "[string][printf]") +{ + static const char* s_formats[] = + { + "%e", "%f", "%g", "%.0e", "%.3e", "%.17e", "%.40e", "%.0f", "%.3f", "%.20f", + "%.1g", "%.17g", "%#g", "%+.5e", "% 12.4g", "%-14.6e", "%010.3f", "%#.0f", + }; + + bx::RngMwc rng; + + for (uint32_t ii = 0; ii < 20000; ++ii) + { + const uint64_t hi = rng.gen(); + const uint64_t lo = rng.gen(); + const double value = bx::bitsToDouble( (hi << 32) | lo); + + if (!bx::isFinite(value) ) + { + continue; + } + + const char* format = s_formats[ii % BX_COUNTOF(s_formats)]; + + char bxTemp[1024]; + const int32_t bxLen = bx::snprintf(bxTemp, BX_COUNTOF(bxTemp), format, value); + + char crtTemp[1024]; + const int32_t crtLen = ::snprintf(crtTemp, BX_COUNTOF(crtTemp), format, value); + + INFO(format << " " << crtTemp); + REQUIRE(bxLen == crtLen); + REQUIRE(0 == bx::strCmp(bxTemp, crtTemp) ); + } + + for (uint32_t ii = 0; ii < 20000; ++ii) + { + const uint32_t odd = rng.gen() | 1; + const int32_t shift = 1 + int32_t(rng.gen() % 40); + const double value = bx::ldexp(double(odd), -shift); + const int32_t digits = shift - 1; + + char bxTemp[1024]; + const int32_t bxLen = bx::snprintf(bxTemp, BX_COUNTOF(bxTemp), "%.*f", digits, value); + + char crtTemp[1024]; + const int32_t crtLen = ::snprintf(crtTemp, BX_COUNTOF(crtTemp), "%.*f", digits, value); + + INFO(digits << " " << crtTemp); + REQUIRE(bxLen == crtLen); + REQUIRE(0 == bx::strCmp(bxTemp, crtTemp) ); + } } TEST_CASE("Format %c, %s, %S", "[string][printf]") @@ -557,6 +634,61 @@ TEST_CASE("Format %c edge cases", "[string][printf]") REQUIRE(test("A ", "%-10c", 'A') ); } +TEST_CASE("Format flags, lengths and precision", "[string][printf]") +{ + constexpr double kDoubleNan = bx::bitsToDouble(bx::kDoubleExponentMask | bx::kDoubleMantissaMask); + + REQUIRE(test("x yz 42", "%c %s %d", 'x', "yz", 42) ); + REQUIRE(test("wxy ", "%-9.3s", "wxyz12345") ); + REQUIRE(test("+9", "%+2d", 9) ); + REQUIRE(test(" 4", "% 3i", 4) ); + REQUIRE(test("-3 ", "%-5d", -3) ); + REQUIRE(test("+0", "%+i", 0) ); + REQUIRE(test(" 00042: -00317", "%8.5d:%9.5d", 42, -317) ); + REQUIRE(test("-204060801", "%d", -204060801) ); + REQUIRE(test("35 0035", "%u %04u", 35u, 35u) ); + REQUIRE(test("17 2a 7F", "%o %x %X", 15u, 42u, 127u) ); + REQUIRE(test(" 17 2a 7F ", "%3o %2x %-3X", 15u, 42u, 127u) ); + REQUIRE(test("017 0x2a 0X7F", "%#o %#x %#X", 15u, 42u, 127u) ); + REQUIRE(test("71 812", "%hi %ld", short(71), 812l) ); + REQUIRE(test("7123456789", "%llu", 7123456789llu) ); + REQUIRE(test("-4 5 -6", "%ji %zi %ti", intmax_t(-4), ptrdiff_t(5), ptrdiff_t(-6) ) ); + + REQUIRE(testNotStdCompliant("5 6 7", "%I64d %I32d %Id", int64_t(5), int32_t(6), ptrdiff_t(7) ) ); + + REQUIRE(test("-7.000000", "%f", -7.0) ); + REQUIRE(test("-6.1234567800", "%.10f", -6.12345678) ); + REQUIRE(test("512.0625000000", "%.10f", 512.0625) ); + REQUIRE(test("2.5", "%.1f", 2.5) ); + REQUIRE(test(" 0", "% .0f", 0.3) ); + REQUIRE(test("0.00", "%.2f", 3e-4) ); + REQUIRE(test("-7.30", "%+5.2f", -7.3) ); + REQUIRE(test("0.0 ", "%-11.1f", 0.0) ); + REQUIRE(test("-0.000", "%.3f", -0.0) ); + REQUIRE(test("0.000001", "%f", 7.5e-7) ); + REQUIRE(test("1237940039285380274899124224.0", "%.1f", 1237940039285380274899124224.0) ); + REQUIRE(test("0.0000002999999999999999864", "%.25f", 3e-7) ); + REQUIRE(test("0.000000000000000040000000", "%.24f", 4e-17) ); + REQUIRE(test("0.0000000300 300000000", "%.10f %.0f", 3e-8, 3e+8) ); + REQUIRE(test("200046813.0", "%.1f", 200046813.0) ); + REQUIRE(test(" 2.72 %", "%*.*f %%", 6, 2, 2.718) ); + REQUIRE(test("-7.000000e+00", "%e", -7.0) ); + REQUIRE(test("2.5E+00", "%.1E", 2.5) ); + REQUIRE(test("-7.30e+00", "%+5.2e", -7.3) ); + REQUIRE(test("+0.7 -5", "%+g %+g", 0.7, -5.0) ); + REQUIRE(test("3", "%.1G", 3.2) ); + REQUIRE(test("-7.3", "%+4.2g", -7.3) ); + REQUIRE(test("7e-250", "%g", 7e-250) ); + REQUIRE(test(" 4.2 4.21", "% .3g %.3g", 4.203, 4.206) ); + REQUIRE(test("3e-316:2e+307", "%g:%g", 3e-316, 2e+307) ); + REQUIRE(test("inf -INF nan", "%f %F %g", bx::kDoubleInfinity, -bx::kDoubleInfinity, kDoubleNan) ); + REQUIRE(test("nan", "%.3g", kDoubleNan) ); + + int32_t len = 0; + REQUIRE(testNotStdCompliant("bbbb ", "%.4s %n", "bbbbbbbbb", &len) ); + REQUIRE(5 == len); +} + TEST_CASE("misc", "[string][printf]") { REQUIRE(test("53000atest-20 bit", "%u%u%ctest%d %s", 5, 3000, 'a', -20, "bit") ); @@ -566,7 +698,7 @@ TEST_CASE("misc", "[string][printf]") REQUIRE(test(" 00004", "%10.5d", 4) ); REQUIRE(test("hi x", "%*sx", -3, "hi") ); REQUIRE(test("0.33", "%.*g", 2, 0.33333333) ); -// REQUIRE(test("3.33e-01", "%.*e", 2, 0.33333333) ); + REQUIRE(test("3.33e-01", "%.*e", 2, 0.33333333) ); REQUIRE(test("", "") ); }