如何根据C ++中的语言环境设置解析格式化的double

时间:2010-10-11 07:59:47

标签: c++ parsing double

我发现strtod方法存在问题,我一直都在使用它。 首先,它不理解非点小数分隔符,所以我不得不使用它:

std::replace(sSource.begin(), sSource.end(), getDecimalSeparator(), '.');

但是,我没有发现另一个问题,也没有找到解决方法。如果值为负且千位分隔符是一个点(“。”),则strtod返回0并且_EndPtr指向字符串的开头:

// 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** szStop)
{
    // 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"

有问题:

  • 我做错了什么?(已回答)

  • 如果根据本地设置格式化小数分隔符,为什么strtod会失败?(已回答)

  • 即使我用一个点(“。”)替换当前的小数点分隔符并删除所有千位分隔符,如何解析一个负值?检测否定唱,删除它,将值解析为正数并在此之后反转负号?

4 个答案:

答案 0 :(得分:1)

关于不正确的结果,我可以看到由于数字中的空格而引起的问题。根据文档strtod(),此函数将删除前导空格并在找到数字中间的空格时停止读取并返回转换为double的值。例如,

如果在下面给出的情况下,strtod在减号后面找到空格,而减号只是一个有效的数值,所以它返回0.0。

dVal = parse("- 123.45",   &szStop); // dVal == 0.0   szStop == "- 123.45"

类似于下面的行,当它找到第二个小数时,它假定它的值结束,因为数值中不可能有两位小数。

dVal = parse("1.123.45",   &szStop); // dVal == 1.123 szStop == ".45"

如果下面的行给出,它会在1之后找到空格并停止进一步处理并将1作为已解析的双精度返回。

dVal = parse("1 123.45",   &szStop); // dVal == 1     szStop == " 123.45"

以下案例类似于我上面提到的第一个案例。

dVal = parse("- 1 123,45", &szStop); // dVal == 0     szStop == start of the string

我希望这会有所帮助。

此致 Azher Iqbal

答案 1 :(得分:0)

遗憾的是,区域设置敏感度不是strtod()的一项功能。文档表明它总是使用美国惯例。

显而易见的解决方案是完成重新映射预期的区域设置字符,在您的情况下删除所有空格并.(数字分组),然后将,替换为.(十进制)获得所需的功能。没有必要对-+做任何事情(据我所知,这些在所有浪漫语言中具有相同的含义)。

答案 2 :(得分:0)

不确定以下考虑是否适用于标准,但MSDN声明 strtod 只要 atof {{3}的调用中设置的设置作出反应(参见 LC_NUMERIC )。

案例

dVal = parse("- 123.45",   &szStop); // dVal == 0.0   szStop == "- 123.45"

......除了数字之外,没有任何符号可以跟随减号(或加号)可选符号。

答案 3 :(得分:0)

似乎对语言环境敏感的解析应如下所示:

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;