Added: bx::formatHumanNumber. (#368)

This commit is contained in:
Branimir Karadžić
2026-01-31 18:45:04 -08:00
committed by GitHub
parent 016167548e
commit 36ad6131f4
3 changed files with 114 additions and 3 deletions

View File

@@ -450,6 +450,16 @@ namespace bx
template <typename Ty>
void stringPrintf(Ty& _out, const char* _format, ...);
/// Format number to human readable representation.
///
/// @param[out] _out Output string.
/// @param[in] _count Maximum output string count.
/// @param[in] _value Value.
/// @param[in] _numFrac Number of fraction digits.
/// @returns Length of output string.
///
int32_t formatHumanNumber(char* _out, int32_t _count, double _value, int32_t _numFrac);
/// Convert size in bytes to human readable string kibi units.
int32_t prettify(char* _out, int32_t _count, uint64_t _value, Units::Enum _units = Units::Kibi);

View File

@@ -1280,6 +1280,70 @@ namespace bx
return total;
}
int32_t formatHumanNumber(char* _out, int32_t _count, double _value, int32_t _numFrac)
{
char temp[64];
int32_t len = snprintf(temp, sizeof(temp), "%.*f", _numFrac, _value);
int32_t intPartLen = len;
if (len >= _numFrac+1
&& '.' == temp[len-_numFrac-1])
{
intPartLen = len-_numFrac-1;
bool zero = true;
for (int32_t ii = _numFrac; 0 < ii && zero; --ii)
{
zero &= temp[len-ii] == '0';
}
if (zero)
{
temp[len-_numFrac-1] = '\0';
len = intPartLen;
}
}
const int32_t fracPartLen = len - intPartLen;
const int32_t commas = (intPartLen > 3) ? (intPartLen - 1) / 3 : 0;
const int32_t total = intPartLen + fracPartLen + commas;
if (_count < total)
{
if (0 < _count)
{
_out[0] = '\0';
}
return 0;
}
char* out = _out + total;
*out = '\0';
if (0 != fracPartLen)
{
out -= fracPartLen;
memCopy(out, &temp[intPartLen], fracPartLen);
}
int32_t group = 0;
for (int32_t ii = intPartLen - 1; ii >= 0; --ii)
{
*--out = temp[ii];
if (3 == ++group
&& 0 < ii)
{
*--out = ',';
group = 0;
}
}
return total;
}
static const char s_units[] = { 'B', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
template<uint32_t Kilo, char KiloCh0, char KiloCh1, CharFn fn>
@@ -1295,7 +1359,10 @@ namespace bx
++idx;
}
return snprintf(_out, _count, "%0.2f %c%c%c", value
char human[32];
formatHumanNumber(human, sizeof(human), value, 2);
return snprintf(_out, _count, "%s %c%c%c", human
, fn(s_units[idx])
, idx > 0 ? KiloCh0 : '\0'
, KiloCh1

View File

@@ -24,10 +24,10 @@ TEST_CASE("prettify", "[string]")
{
char tmp[1024];
prettify(tmp, BX_COUNTOF(tmp), 4000, bx::Units::Kilo);
REQUIRE(0 == bx::strCmp(tmp, "4.00 kB") );
REQUIRE(0 == bx::strCmp(tmp, "4 kB") );
prettify(tmp, BX_COUNTOF(tmp), 4096, bx::Units::Kibi);
REQUIRE(0 == bx::strCmp(tmp, "4.00 KiB") );
REQUIRE(0 == bx::strCmp(tmp, "4 KiB") );
}
TEST_CASE("chars", "[string]")
@@ -762,3 +762,37 @@ TEST(tinystl_string_assign)
CHECK( other.size() == 0 );
}
}
bool testFormatHumanNumber(bx::StringView _expected, double _value, int32_t _numFrac, int32_t _bufferSize = 32)
{
char* tmp = (char*)BX_STACK_ALLOC(_bufferSize);
int32_t total = bx::formatHumanNumber(tmp, _bufferSize, _value, _numFrac);
bx::StringView human(tmp, total);
const bool result = 0 == bx::strCmp(human, _expected);
if (!result)
{
DBG(
"expected: '%S' (len: %d), human: '%S' (len: %d)"
, &_expected
, _expected.getLength()
, &human
, human.getLength()
);
}
return result;
}
TEST_CASE("formatHumanNumber", "[string]")
{
REQUIRE(testFormatHumanNumber("1,389,983,113.89", 1389983113.891389, 2) );
REQUIRE(testFormatHumanNumber("13,899,831.1389", 13899831.1389, 4) );
REQUIRE(testFormatHumanNumber("1,389.10", 1389.1, 2) );
REQUIRE(testFormatHumanNumber("1,389", 1389.0, 8) );
REQUIRE(testFormatHumanNumber("0", 0.0, 2) );
REQUIRE(testFormatHumanNumber("", 1389983113.891389, 2, 4) );
}