1
0
Fork 0

Codechange: let number formatting use StringBuilder

pull/10927/head
Rubidium 2023-05-11 21:34:21 +02:00 committed by rubidium42
parent ed51cf117a
commit c384d829fe
1 changed files with 77 additions and 66 deletions

View File

@ -43,6 +43,8 @@
#include "table/strings.h" #include "table/strings.h"
#include "table/control_codes.h" #include "table/control_codes.h"
#include "strings_internal.h"
#include "safeguards.h" #include "safeguards.h"
std::string _config_language_file; ///< The file (name) stored in the configuration. std::string _config_language_file; ///< The file (name) stored in the configuration.
@ -315,16 +317,15 @@ void SetDParamStr(uint n, const std::string &str)
/** /**
* Format a number into a string. * Format a number into a string.
* @param buff the buffer to write to * @param builder the string builder to write to
* @param number the number to write down * @param number the number to write down
* @param last the last element in the buffer * @param last the last element in the buffer
* @param separator the thousands-separator to use * @param separator the thousands-separator to use
* @param zerofill minimum number of digits to print for the integer part. The number will be filled with zeros at the front if necessary. * @param zerofill minimum number of digits to print for the integer part. The number will be filled with zeros at the front if necessary.
* @param fractional_digits number of fractional digits to display after a decimal separator. The decimal separator is inserted * @param fractional_digits number of fractional digits to display after a decimal separator. The decimal separator is inserted
* in front of the \a fractional_digits last digit of \a number. * in front of the \a fractional_digits last digit of \a number.
* @return till where we wrote
*/ */
static char *FormatNumber(char *buff, int64 number, const char *last, const char *separator, int zerofill = 1, int fractional_digits = 0) static void FormatNumber(StringBuilder &builder, int64 number, const char *separator, int zerofill = 1, int fractional_digits = 0)
{ {
static const int max_digits = 20; static const int max_digits = 20;
uint64 divisor = 10000000000000000000ULL; uint64 divisor = 10000000000000000000ULL;
@ -332,7 +333,7 @@ static char *FormatNumber(char *buff, int64 number, const char *last, const char
int thousands_offset = (max_digits - fractional_digits - 1) % 3; int thousands_offset = (max_digits - fractional_digits - 1) % 3;
if (number < 0) { if (number < 0) {
if (buff != last) *buff++ = '-'; builder += '-';
number = -number; number = -number;
} }
@ -342,7 +343,7 @@ static char *FormatNumber(char *buff, int64 number, const char *last, const char
if (i == max_digits - fractional_digits) { if (i == max_digits - fractional_digits) {
const char *decimal_separator = _settings_game.locale.digit_decimal_separator.c_str(); const char *decimal_separator = _settings_game.locale.digit_decimal_separator.c_str();
if (StrEmpty(decimal_separator)) decimal_separator = _langpack.langpack->digit_decimal_separator; if (StrEmpty(decimal_separator)) decimal_separator = _langpack.langpack->digit_decimal_separator;
buff = strecpy(buff, decimal_separator, last); builder += decimal_separator;
} }
uint64 quot = 0; uint64 quot = 0;
@ -351,48 +352,42 @@ static char *FormatNumber(char *buff, int64 number, const char *last, const char
num = num % divisor; num = num % divisor;
} }
if ((tot |= quot) || i >= max_digits - zerofill) { if ((tot |= quot) || i >= max_digits - zerofill) {
if (buff != last) *buff++ = '0' + quot; // quot is a single digit builder += '0' + quot; // quot is a single digit
if ((i % 3) == thousands_offset && i < max_digits - 1 - fractional_digits) buff = strecpy(buff, separator, last); if ((i % 3) == thousands_offset && i < max_digits - 1 - fractional_digits) builder += separator;
} }
divisor /= 10; divisor /= 10;
} }
*buff = '\0';
return buff;
} }
static char *FormatCommaNumber(char *buff, int64 number, const char *last, int fractional_digits = 0) static void FormatCommaNumber(StringBuilder &builder, int64 number, int fractional_digits = 0)
{ {
const char *separator = _settings_game.locale.digit_group_separator.c_str(); const char *separator = _settings_game.locale.digit_group_separator.c_str();
if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator; if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator;
return FormatNumber(buff, number, last, separator, 1, fractional_digits); FormatNumber(builder, number, separator, 1, fractional_digits);
} }
static char *FormatNoCommaNumber(char *buff, int64 number, const char *last) static void FormatNoCommaNumber(StringBuilder &builder, int64 number)
{ {
return FormatNumber(buff, number, last, ""); FormatNumber(builder, number, "");
} }
static char *FormatZerofillNumber(char *buff, int64 number, int64 count, const char *last) static void FormatZerofillNumber(StringBuilder &builder, int64 number, int64 count)
{ {
return FormatNumber(buff, number, last, "", count); FormatNumber(builder, number, "", count);
} }
static char *FormatHexNumber(char *buff, uint64 number, const char *last) static void FormatHexNumber(StringBuilder &builder, uint64 number)
{ {
return strecpy(buff, fmt::format("0x{:X}", number).c_str(), last); fmt::format_to(builder, "0x{:X}", number);
} }
/** /**
* Format a given number as a number of bytes with the SI prefix. * Format a given number as a number of bytes with the SI prefix.
* @param buff the buffer to write to * @param builder the string builder to write to
* @param number the number of bytes to write down * @param number the number of bytes to write down
* @param last the last element in the buffer
* @return till where we wrote
*/ */
static char *FormatBytes(char *buff, int64 number, const char *last) static void FormatBytes(StringBuilder &builder, int64 number)
{ {
assert(number >= 0); assert(number >= 0);
@ -409,20 +404,18 @@ static char *FormatBytes(char *buff, int64 number, const char *last)
if (number < 1024) { if (number < 1024) {
id = 0; id = 0;
buff += seprintf(buff, last, "%i", (int)number); fmt::format_to(builder, "{}", number);
} else if (number < 1024 * 10) { } else if (number < 1024 * 10) {
buff += seprintf(buff, last, "%i%s%02i", (int)number / 1024, decimal_separator, (int)(number % 1024) * 100 / 1024); fmt::format_to(builder, "{}{}{:02}", number / 1024, decimal_separator, (number % 1024) * 100 / 1024);
} else if (number < 1024 * 100) { } else if (number < 1024 * 100) {
buff += seprintf(buff, last, "%i%s%01i", (int)number / 1024, decimal_separator, (int)(number % 1024) * 10 / 1024); fmt::format_to(builder, "{}{}{:01}", number / 1024, decimal_separator, (number % 1024) * 10 / 1024);
} else { } else {
assert(number < 1024 * 1024); assert(number < 1024 * 1024);
buff += seprintf(buff, last, "%i", (int)number / 1024); fmt::format_to(builder, "{}", number / 1024);
} }
assert(id < lengthof(iec_prefixes)); assert(id < lengthof(iec_prefixes));
buff += seprintf(buff, last, NBSP "%sB", iec_prefixes[id]); fmt::format_to(builder, NBSP "{}B", iec_prefixes[id]);
return buff;
} }
static char *FormatYmdString(char *buff, TimerGameCalendar::Date date, const char *last, uint case_index) static char *FormatYmdString(char *buff, TimerGameCalendar::Date date, const char *last, uint case_index)
@ -456,7 +449,7 @@ static char *FormatTinyOrISODate(char *buff, TimerGameCalendar::Date date, Strin
return FormatString(buff, GetStringPtr(str), &tmp_params, last); return FormatString(buff, GetStringPtr(str), &tmp_params, last);
} }
static char *FormatGenericCurrency(char *buff, const CurrencySpec *spec, Money number, bool compact, const char *last) static void FormatGenericCurrency(StringBuilder &builder, const CurrencySpec *spec, Money number, bool compact)
{ {
/* We are going to make number absolute for printing, so /* We are going to make number absolute for printing, so
* keep this piece of data as we need it later on */ * keep this piece of data as we need it later on */
@ -467,18 +460,16 @@ static char *FormatGenericCurrency(char *buff, const CurrencySpec *spec, Money n
/* convert from negative */ /* convert from negative */
if (number < 0) { if (number < 0) {
if (buff + Utf8CharLen(SCC_PUSH_COLOUR) > last) return buff; if (!builder.Utf8Encode(SCC_PUSH_COLOUR)) return;
buff += Utf8Encode(buff, SCC_PUSH_COLOUR); if (!builder.Utf8Encode(SCC_RED)) return;
if (buff + Utf8CharLen(SCC_RED) > last) return buff; builder += '-';
buff += Utf8Encode(buff, SCC_RED);
buff = strecpy(buff, "-", last);
number = -number; number = -number;
} }
/* Add prefix part, following symbol_pos specification. /* Add prefix part, following symbol_pos specification.
* Here, it can can be either 0 (prefix) or 2 (both prefix and suffix). * Here, it can can be either 0 (prefix) or 2 (both prefix and suffix).
* The only remaining value is 1 (suffix), so everything that is not 1 */ * The only remaining value is 1 (suffix), so everything that is not 1 */
if (spec->symbol_pos != 1) buff = strecpy(buff, spec->prefix.c_str(), last); if (spec->symbol_pos != 1) builder += spec->prefix;
/* for huge numbers, compact the number into k or M */ /* for huge numbers, compact the number into k or M */
if (compact) { if (compact) {
@ -496,21 +487,17 @@ static char *FormatGenericCurrency(char *buff, const CurrencySpec *spec, Money n
const char *separator = _settings_game.locale.digit_group_separator_currency.c_str(); const char *separator = _settings_game.locale.digit_group_separator_currency.c_str();
if (StrEmpty(separator)) separator = _currency->separator.c_str(); if (StrEmpty(separator)) separator = _currency->separator.c_str();
if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator_currency; if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator_currency;
buff = FormatNumber(buff, number, last, separator); FormatNumber(builder, number, separator);
buff = strecpy(buff, multiplier, last); builder += multiplier;
/* Add suffix part, following symbol_pos specification. /* Add suffix part, following symbol_pos specification.
* Here, it can can be either 1 (suffix) or 2 (both prefix and suffix). * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
* The only remaining value is 1 (prefix), so everything that is not 0 */ * The only remaining value is 1 (prefix), so everything that is not 0 */
if (spec->symbol_pos != 0) buff = strecpy(buff, spec->suffix.c_str(), last); if (spec->symbol_pos != 0) builder += spec->suffix;
if (negative) { if (negative) {
if (buff + Utf8CharLen(SCC_POP_COLOUR) > last) return buff; builder.Utf8Encode(SCC_POP_COLOUR);
buff += Utf8Encode(buff, SCC_POP_COLOUR);
*buff = '\0';
} }
return buff;
} }
/** /**
@ -1109,34 +1096,50 @@ static char *FormatString(char *buff, const char *str_arg, StringParameters *arg
break; break;
} }
case SCC_COMMA: // {COMMA} case SCC_COMMA: { // {COMMA}
buff = FormatCommaNumber(buff, args->GetInt64(SCC_COMMA), last); StringBuilder builder(buff, last);
break; FormatCommaNumber(builder, args->GetInt64(SCC_COMMA));
buff = builder.GetEnd();
case SCC_DECIMAL: {// {DECIMAL}
int64 number = args->GetInt64(SCC_DECIMAL);
int digits = args->GetInt32(SCC_DECIMAL);
buff = FormatCommaNumber(buff, number, last, digits);
break; break;
} }
case SCC_NUM: // {NUM} case SCC_DECIMAL: { // {DECIMAL}
buff = FormatNoCommaNumber(buff, args->GetInt64(SCC_NUM), last); int64 number = args->GetInt64(SCC_DECIMAL);
int digits = args->GetInt32(SCC_DECIMAL);
StringBuilder builder(buff, last);
FormatCommaNumber(builder, number, digits);
buff = builder.GetEnd();
break; break;
}
case SCC_NUM: { // {NUM}
StringBuilder builder(buff, last);
FormatNoCommaNumber(builder, args->GetInt64(SCC_NUM));
buff = builder.GetEnd();
break;
}
case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM} case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM}
int64 num = args->GetInt64(); int64 num = args->GetInt64();
buff = FormatZerofillNumber(buff, num, args->GetInt64(), last); StringBuilder builder(buff, last);
FormatZerofillNumber(builder, num, args->GetInt64());
buff = builder.GetEnd();
break; break;
} }
case SCC_HEX: // {HEX} case SCC_HEX: { // {HEX}
buff = FormatHexNumber(buff, (uint64)args->GetInt64(SCC_HEX), last); StringBuilder builder(buff, last);
FormatHexNumber(builder, (uint64)args->GetInt64(SCC_HEX));
buff = builder.GetEnd();
break; break;
}
case SCC_BYTES: // {BYTES} case SCC_BYTES: { // {BYTES}
buff = FormatBytes(buff, args->GetInt64(), last); StringBuilder builder(buff, last);
FormatBytes(builder, args->GetInt64());
buff = builder.GetEnd();
break; break;
}
case SCC_CARGO_TINY: { // {CARGO_TINY} case SCC_CARGO_TINY: { // {CARGO_TINY}
/* Tiny description of cargotypes. Layout: /* Tiny description of cargotypes. Layout:
@ -1162,7 +1165,9 @@ static char *FormatString(char *buff, const char *str_arg, StringParameters *arg
} }
} }
buff = FormatCommaNumber(buff, amount, last); StringBuilder builder(buff, last);
FormatCommaNumber(builder, amount);
buff = builder.GetEnd();
break; break;
} }
@ -1242,13 +1247,19 @@ static char *FormatString(char *buff, const char *str_arg, StringParameters *arg
break; break;
} }
case SCC_CURRENCY_SHORT: // {CURRENCY_SHORT} case SCC_CURRENCY_SHORT: { // {CURRENCY_SHORT}
buff = FormatGenericCurrency(buff, _currency, args->GetInt64(), true, last); StringBuilder builder(buff, last);
FormatGenericCurrency(builder, _currency, args->GetInt64(), true);
buff = builder.GetEnd();
break; break;
}
case SCC_CURRENCY_LONG: // {CURRENCY_LONG} case SCC_CURRENCY_LONG: { // {CURRENCY_LONG}
buff = FormatGenericCurrency(buff, _currency, args->GetInt64(SCC_CURRENCY_LONG), false, last); StringBuilder builder(buff, last);
FormatGenericCurrency(builder, _currency, args->GetInt64(SCC_CURRENCY_LONG), false);
buff = builder.GetEnd();
break; break;
}
case SCC_DATE_TINY: // {DATE_TINY} case SCC_DATE_TINY: // {DATE_TINY}
buff = FormatTinyOrISODate(buff, args->GetInt32(SCC_DATE_TINY), STR_FORMAT_DATE_TINY, last); buff = FormatTinyOrISODate(buff, args->GetInt32(SCC_DATE_TINY), STR_FORMAT_DATE_TINY, last);