Skip to content

Commit

Permalink
locale-aware byte size formatter
Browse files Browse the repository at this point in the history
Char-based number formatting (using std::stringstream)
is broken for locales with non-ascii thousand separator
(at least with gcc 8.3.0), for that reason, we should
provide custom locale aware number-to-string conversion
for float numbers and use it in ByteSizeToString utility.
  • Loading branch information
Karry committed Dec 23, 2023
1 parent bc2caa9 commit fdbc474
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 21 deletions.
13 changes: 13 additions & 0 deletions Tests/src/StringUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,21 @@ TEST_CASE("Local aware number to string")
{
osmscout::Locale locale;
locale.SetThousandsSeparator(" ");
locale.SetDecimalSeparator(".");
REQUIRE(osmscout::NumberToString(1002030, locale) == "1 002 030");
REQUIRE(osmscout::NumberToString(-1002030, locale) == "-1 002 030");

REQUIRE(osmscout::FloatToString(-1002030.123456, locale, 6) == "-1 002 030.123 456");
REQUIRE(osmscout::FloatToString(-1002030.125, locale, 2) == "-1 002 030.13");
}

TEST_CASE("Byte size to string")
{
osmscout::Locale locale;
locale.SetThousandsSeparator("'");
locale.SetDecimalSeparator(",");
locale.SetUnitsSeparator(" ");
REQUIRE(osmscout::ByteSizeToString(1063256064.0, locale) == "1'014,0 MiB");
}

TEST_CASE("Trim string")
Expand Down
2 changes: 1 addition & 1 deletion libosmscout-client/src/osmscoutclient/MapManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ CancelableFuture<bool> MapManager::LookupDatabases()
// https://en.cppreference.com/w/cpp/filesystem/is_directory
// https://en.cppreference.com/w/cpp/filesystem/status
if (!std::filesystem::exists(lookupDir) || !std::filesystem::is_directory(lookupDir)) {
osmscout::log.Warn() << "Lookup dir" << lookupDir.string() << "doesn't exist or isn't a directory";
osmscout::log.Warn() << "Lookup dir " << lookupDir.string() << " doesn't exist or isn't a directory";
continue;
}

Expand Down
20 changes: 17 additions & 3 deletions libosmscout/include/osmscout/util/String.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,20 @@ namespace osmscout {
*
* @param value
* @param locale
* @return
* @return UTF-8 string
*/
extern OSMSCOUT_API std::string NumberToString(long value, const Locale &locale);

/**
* Returns locale-aware string representation of number
*
* @param value
* @param locale
* @param precision
* @return UTF-8 string
*/
extern OSMSCOUT_API std::string FloatToString(double value, const Locale &locale, uint32_t precision = 3);

/**
* \ingroup Util
* Returns the numerical value of the given character, if the character
Expand Down Expand Up @@ -403,9 +413,13 @@ namespace osmscout {
/**
* \ingroup Util
*
* Prints byte size with short, human readable form by ISO/IEC 80000 standard.
* It means that KiB stands for 1024 bytes, MiB for 1024^2, GiB 1024^3...
*
* Returned string is locale aware, UTF-8 encoded
*/
extern OSMSCOUT_API std::string ByteSizeToString(FileOffset size);
extern OSMSCOUT_API std::string ByteSizeToString(double size);
extern OSMSCOUT_API std::string ByteSizeToString(FileOffset size, const Locale &locale = Locale::ByEnvironment());
extern OSMSCOUT_API std::string ByteSizeToString(double size, const Locale &locale = Locale::ByEnvironment());

/**
* \ingroup Util
Expand Down
50 changes: 33 additions & 17 deletions libosmscout/src/osmscout/util/String.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ namespace osmscout {
std::string NumberToString(long value, const Locale &locale)
{
std::stringstream ss;
if (std::abs(value) < 1000 || locale.GetThousandsSeparator().empty()){
ss.imbue(std::locale("C"));
if (locale.GetThousandsSeparator().empty()){
ss << value;
}else{
long mag=1000;
Expand All @@ -214,6 +215,25 @@ namespace osmscout {
return ss.str();
}

extern OSMSCOUT_API std::string FloatToString(double value, const Locale &locale, uint_2 precision)
{
std::stringstream ss;
ss << NumberToString(static_cast<long>(value), locale);

if (precision > 0) {
ss << locale.GetDecimalSeparator();
double fractionNum = std::abs(value - std::ceil(value));
std::string fraction = NumberToString(std::round(fractionNum * std::pow(10, precision)), Locale());
for (size_t i = 0; i < fraction.size(); i++) {
if (i > 0 && i % 3 == 0 && i < fraction.size() - 1) {
ss << locale.GetThousandsSeparator();
}
ss << fraction[i];
}
}
return ss.str();
}

bool GetDigitValue(char digit, size_t& result)
{
switch (digit) {
Expand Down Expand Up @@ -329,36 +349,32 @@ namespace osmscout {
return wordCount;
}

std::string ByteSizeToString(FileOffset value)
std::string ByteSizeToString(FileOffset value, const Locale &locale)
{
return ByteSizeToString((double)value);
return ByteSizeToString(static_cast<double>(value), locale);
}

std::string ByteSizeToString(double value)
std::string ByteSizeToString(double value, const Locale &locale)
{
std::stringstream buffer;

buffer.setf(std::ios::fixed);
buffer << std::setprecision(1);

if (value<1.0 && value>-1) {
buffer << "0 B";
buffer << "0" << locale.GetUnitsSeparator() << "B";
}
else if (ceil(value)>=1024.0*1024*1024*1024) {
buffer << value/(1024.0*1024*1024*1024) << " TiB";
else if (ceil(value)>=std::pow(1024.0, 4.0)) {
buffer << FloatToString(value/std::pow(1024.0, 4.0), locale, 1) << locale.GetUnitsSeparator() << "TiB";
}
else if (ceil(value)>=1024.0*1024*1024) {
buffer << value/(1024.0*1024*1024) << " GiB";
else if (ceil(value)>=std::pow(1024.0, 3.0)) {
buffer << FloatToString(value/std::pow(1024.0, 3.0), locale, 1) << locale.GetUnitsSeparator() << "GiB";
}
else if (ceil(value)>=1024.0*1024) {
buffer << value/(1024.0*1024) << " MiB";
else if (ceil(value)>=std::pow(1024.0, 2.0)) {
buffer << FloatToString(value/std::pow(1024.0, 2.0), locale, 1) << locale.GetUnitsSeparator() << "MiB";
}
else if (ceil(value)>=1024.0) {
buffer << value/1024.0 << " KiB";
buffer << FloatToString(value/1024.0, locale, 1) << locale.GetUnitsSeparator() << "KiB";
}
else {
buffer << std::setprecision(0);
buffer << value << " B";
buffer << FloatToString(value, locale, 0) << locale.GetUnitsSeparator() << "B";
}

return buffer.str();
Expand Down

0 comments on commit fdbc474

Please sign in to comment.