【问题标题】:How to parse a double formatted according to a locale settings in C++如何根据 C++ 中的语言环境设置解析双格式
【发布时间】:2010-10-11 07:59:47
【问题描述】:

我发现我一直使用的 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 会失败?(已回答)

  • 即使我将当前的小数点分隔符替换为点(“.”)并删除所有千位分隔符,如何解析负值?检测到负号,将其删除,将值解析为正数,然后反转负号?

【问题讨论】:

  • 为什么转了,和.? , 是千位分隔符,并且 .是小数。
  • 一些语言环境(如西班牙语)正好相反:“。”是千位分隔符,“,”是小数。
  • 有多少程序员不知道语言环境和语言的含义,这总是令人惊讶 ;-)

标签: c++ parsing double


【解决方案1】:

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

如果 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

我希望这会有所帮助。

问候, 阿泽尔·伊克巴尔

【讨论】:

  • 现在,当你提到这一点时,我明白了。我做了一个小实验,结果是:strtod(至少是 MS 实现)从字符串中删除所有数字分组符号。如果带有空格符号“-”的负号和数字分组符号是“”(或“-.”和“.”),则它运行良好,否则失败。非常感谢。这就是为什么它在某些情况下使用“-”最让我困惑的原因。
【解决方案2】:

很遗憾,语言环境敏感性不是strtod() 的一个功能。文档表明它始终使用美国惯例。

明显的解决方案是完成预期语言环境字符的重新映射,在您的情况下消除所有空格和.(数字分组),然后将, 替换为.(十进制)以获得所需的功能。没有必要对-+ 做任何事情(据我所知,它们在所有浪漫语言中具有相同的含义)。

【讨论】:

  • 转换不需要看到任何数字分组字符,因此将它们全部删除是有意义的。 (我在瑞士从未遇到过'。)
  • 确实,这里需要删除数字分组字符,但在一般情况下删除空格和. 会失败:-)。抱歉,由于我错过了问题中给出的确切先决条件,因此我的批评是没有根据的,因此我撤回了该评论。
【解决方案3】:

不确定以下考虑是否适用于整个标准,但 MSDN 声明 strtod 只要 atof 对调用 setlocale(请参阅 LC_NUMERIC)。

案例

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

...确实,除了数字之外的任何符号都不能跟在减号(或加号)可选符号之后。

【讨论】:

    【解决方案4】:

    似乎区域敏感的解析应该是这样的:

    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;
    

    【讨论】:

      猜你喜欢
      • 2015-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-22
      • 1970-01-01
      • 2017-12-08
      相关资源
      最近更新 更多