mirror of
https://github.com/bkaradzic/bx.git
synced 2026-10-29 20:15:38 +00:00
Made snprintf %e, %f, %g follow C, sped up fromString(double), strFind and strRFind. (#476)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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("", "") );
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user