Made snprintf %e, %f, %g follow C, sped up fromString(double), strFind and strRFind. (#476)

This commit is contained in:
Branimir Karadžić
2026-10-27 17:17:04 -07:00
committed by GitHub
parent 0e5bbca687
commit dae5c595b7
8 changed files with 1007 additions and 110 deletions
+7 -5
View File
@@ -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;
}
+2
View File
@@ -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.
-1
View File
@@ -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
{
+398 -4
View File
@@ -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);
}
+205 -46
View File
@@ -6,6 +6,8 @@
#include <bx/allocator.h>
#include <bx/file.h>
#include <bx/hash.h>
#include <bx/math.h>
#include <bx/simd_t.h>
#include <bx/string.h>
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<uint64_t>(_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<simd128_t>(_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<simd128_t>(&_str[ 0]);
const simd128_t chunk1 = simd_ldu<simd128_t>(&_str[16]);
const simd128_t chunk2 = simd_ldu<simd128_t>(&_str[32]);
const simd128_t chunk3 = simd_ldu<simd128_t>(&_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<simd128_t>(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<simd128_t>(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<toNoop>(
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)
+19
View File
@@ -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<simd128_t>(0x80000000u, 0u, 0x80000000u, 0u)) == 0x5);
+190
View File
@@ -8,6 +8,7 @@
#include <bx/filepath.h>
#include <bx/string.h>
#include <bx/handlealloc.h>
#include <bx/rng.h>
#include <bx/sort.h>
#include <string>
#include <tinystl/string.h>
@@ -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';
}
}
+186 -54
View File
@@ -6,6 +6,7 @@
#include "test.h"
#include <bx/string.h>
#include <bx/readerwriter.h>
#include <bx/rng.h>
#include <limits>
#include <inttypes.h>
@@ -111,7 +112,7 @@ static bool test(const char* _expected, const char* _format, ...)
{
va_list argList;
va_start(argList, _format);
const bool result = test<false>(_expected, _format, argList);
const bool result = test<true>(_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("", "") );
}