How to parse a double formatted according to a locale settings in C++
I've found a problem with the strtod method which I've used all this time. First of all it doesn't understand non-point decimal separator, so I've forced to use this:
std::replace(sSource.begin(), sSource.end(), getDecimalSeparator(), '.');
But no I've found another problem and didn't found how to resolve it yet. If the value is negative and the thousands separator is a point ("."), strtod return 0 and _EndPtr points to the start of the string:
// PRECONDITIONS:
// * digit grouping symbol (thousands separator) = "."
// * decimal symbol = ","
// * digital grouping = "123.456.789"
// * negative sign symbol = "-"
// * negative number format = "- 1,1"
// * OS WinXP SP2
// * the rest doesn't matter
double parse(const char* cszValue, char** szSt开发者_高级运维op)
{
// NOTE: error handling code has been removed to simplify the sample
return strtod(sSource.c_str(), szStop);
}
//...
char* szStop = NULL;
double dVal = 0.0;
dVal = parse("123.45", &szStop); // works!
// All the next DON'T WORK!
dVal = parse("123,45", &szStop); // dVal == 123.0 szStop == ",45"
dVal = parse("- 123.45", &szStop); // dVal == 0.0 szStop == "- 123.45"
// the same for "- 123,45"
dVal = parse("1.123.45", &szStop); // dVal == 1.123 szStop == ".45"
// the same for "1.123,45"
dVal = parse("1 123.45", &szStop); // dVal == 1 szStop == " 123.45"
// the same for "1 123,45"
dVal = parse("- 1 123,45", &szStop); // dVal == 0 szStop == start of the string
// the same for "- 1 123.45", "- 1.123,45", "- 1.123.45"
There are the questions:
What I doing wrong? (answered)
Why strtod fails if the decimal separator formatted according to local settings? (answered)
Even if I'll replace current decimal separator with a point (".") and remove all thousand separators, how to parse a negative value? Detect a negative sing, remove it, parse the value as a positive number and reverse the negative sign after that?
Regarding incorrect results I can see the problem due to white space(s) in the digits. As per documentation strtod() this function will remove leading white spaces and stop reading when it finds the space in the mid of the digits and return the value converted to double. For example,
In case given below strtod finds the space after minus sign and minus only is not a valid numeric value so it returns 0.0.
dVal = parse("- 123.45", &szStop); // dVal == 0.0 szStop == "- 123.45"
Similarly in case of line below, when it finds second decimal, it assumes it end of the value because two decimals are not possible in numeric value.
dVal = parse("1.123.45", &szStop); // dVal == 1.123 szStop == ".45"
In case of line give below it finds the space after 1 and stops further processing and return the 1 as parsed double.
dVal = parse("1 123.45", &szStop); // dVal == 1 szStop == " 123.45"
The case below resembles the first case that I have mentioned above.
dVal = parse("- 1 123,45", &szStop); // dVal == 0 szStop == start of the string
I hope this helps.
Regards, Azher Iqbal
Locale sensitivity is not a feature of strtod()
, sadly. The documentation suggests it always uses U.S. conventions.
The obvious solution is to complete the remapping of expected locale characters, in your case eliminating all spaces and .
(digit grouping) and then replacing ,
with .
(decimal) to get the functionality desired. There's no need to do anything with -
and +
(as far as I know, those have the same meaning in all the romantic languages).
Not sure following consideration applicable across standard but MSDN states that strtod as long as atof react to the settings set up in a call to setlocale (see LC_NUMERIC).
For the case
dVal = parse("- 123.45", &szStop); // dVal == 0.0 szStop == "- 123.45"
...it's true that no symbols except digits may follow minus (or plus) optional sign.
It seems the locale-sensitive parsing should looks like this:
bool bNegative = false;
switch (getNegativeOrder())
{
// enumeration from MSDN
case 0: // Left parenthesis, number, right parenthesis; for example, (1.1)
if (sSource[0] == '(' && sSource[sSource.size() - 1] == ')')
{
bNegative = true;
sSource = sSource.substr(1, sSource.size() - 2);
}
break;
case 1: // Negative sign, number; for example, -1.1
if (sSource[0] == '-')
{
bNegative = true;
sSource = sSource.substr(1, sSource.size() - 1);
}
break;
case 2: // Negative sign, space, number; for example, - 1.1
if (sSource.size() > 1 && sSource[0] == '-' && sSource[1] == ' ')
{
bNegative = true;
sSource = sSource.substr(2, sSource.size() - 2);
}
break;
case 3: // Number, negative sign; for example, 1.1-
if (sSource[sSource.size() - 1] == '-')
{
bNegative = true;
sSource = sSource.substr(0, sSource.size() - 1);
}
break;
case 4: // Number, space, negative sign; for example, 1.1 -
if (sSource.size() > 1 && sSource[sSource.size() - 1] == '-' && sSource[sSource.size() - 2] == ' ')
{
bNegative = true;
sSource = sSource.substr(0, sSource.size() - 2);
}
break;
}
// Remove thousand separator, because strtod will fail to parse them.
sSource.erase(std::remove(sSource.begin(), sSource.end(), getThousandSeparator()), sSource.end());
// strtod expects nptr to point to a string of the following form:
// [whitespace] [sign] [digits] [.digits] [ {d | D | e | E}[sign]digits]
std::replace(sSource.begin(), sSource.end(), getDecimalSeparator(), '.');
char *szStop = NULL;
double dValue = strtod(sSource.c_str(), &szStop);
if (bNegative)
dValue *= -1;
精彩评论