Parsing a currency formatted string to number

Is there a way to parse a currency string to a number using the Haiku API in C++? I want to convert a string like 1.234.567,89 € to a number, but taking into account that the string can be formatted in any of the formatting conventions known by Haiku.

This is the code I use to format a number as currency (fAccountLocale is the ID of the current language):

status_t
Locale::CurrencyToString(const Fixed& amount, BString& string)
{
	BFormattingConventions conv(fAccountLocale.String());
	BLanguage* language = new BLanguage(fAccountLocale.String());
	BLocale* locale = new BLocale(language, &conv);
	BNumberFormat numberFormatter(locale);
	BString curstr;

	return numberFormatter.FormatMonetary(string, amount.AsDouble());
}

This works correctly, but I want to go the other way, converting the a string to a number.

Doing it manually works for some formats, but this fails for languages using . as thousand-separator.

status_t
Locale::StringToCurrency(const char* string, Fixed& amount)
{
	if (!string)
		return B_ERROR;

	std::string input(string);
	std::string digits;
	bool decimalPointSeen = false;

	for (char ch : input) {
		if (std::isdigit(ch)) {
			digits += ch;
		} else if ((ch == ',' || ch == '.') && !decimalPointSeen) {
			decimalPointSeen = true;
			digits += '.';
		}
	}

	if (digits.empty())
		return B_ERROR;

	double value = std::stod(digits) * 100;
	long premultipliedValue = static_cast<long>(std::round(value));

	if (premultipliedValue < 0)
		premultipliedValue = -premultipliedValue;

	amount.SetPremultiplied(premultipliedValue);

	return B_OK;
}

BNumberFormat has methods for getting the separators used:

BNumberFormat::GetSeparator(B_DECIMAL_SEPARATOR) returns the decimal separator,

BNumberFormat::GetSeparator(B_GROUPING_SEPARATOR) returns the “thousands” separator.

Just be aware that they are BStrings as a separator could be more than 1 character in some locales.

You could look at how the DeskCalc parses these in the haiku src.

1 Like

If a workaround is OK, we may coach BNumberFormat.Parse to work. It discards non-numeric postfix already, the problem comes when the currency is before the number. I don’t know if there’s a better way to get it, but we can format say 5.00 and 6.00 and find out where they differ:

status_t
StringToCurrency(double& amount, BString& string)
{
	BFormattingConventions conv(fAccountLocale.String());
	BLanguage* language = new BLanguage(fAccountLocale.String());
	BLocale* locale = new BLocale(language, &conv);
	BNumberFormat numberFormatter(locale);

	status_t status = numberFormatter.Parse(string, amount);
	if (status != B_OK) {
		BString format5, format6;
		numberFormatter.FormatMonetary(format5, 5.0);
		numberFormatter.FormatMonetary(format6, 6.0);
		int i = 0;
		while (i < format5.Length() && format5[i] == format6[i])
			i++;
		while (i >= 0 && (format5[i] & 0xc0) == 0x80)
			i--;
		format5.Truncate(i);
		BString clear(string);
		clear.RemoveFirst(format5);
		status = numberFormatter.Parse(clear, amount);
	}
	return status;
}

You may use the character accessors if you don’t like the UTF8 bytes dance.

Works with postivie and negative values, whatever separators the locale uses, prefix or postfix units and even with different number charactes, like arabic.

1 Like

Great, I’ll look for a way to use these suggestions.

BFormattingConventions has some usefull methods (GetNumericFormat() and GetMonetaryFormat()), but they are still WIP.