Как разобрать двойное форматирование в соответствии с настройками локали в C ++ - PullRequest
4 голосов
/ 11 октября 2010

Я обнаружил проблему с методом 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 ]

1 голос
/ 11 октября 2010

Что касается неверных результатов, я вижу проблему из-за пробелов в цифрах. В соответствии с документацией 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

0 голосов
/ 11 октября 2010

Кажется, что синтаксический разбор с учетом локали должен выглядеть следующим образом:

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;
0 голосов
/ 11 октября 2010

Не уверен, что в соответствии со стандартом, применимым к стандарту, но MSDN утверждает, что strtod , пока atof реагирует на настройки, установленные в вызове на setlocale (см. LC_NUMERIC ).

Для дела

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

... верно, что никакие символы, кроме цифр, не могут следовать за дополнительным знаком минус (или плюс).

0 голосов
/ 11 октября 2010

Чувствительность локали - не особенность strtod(), к сожалению.Документация предполагает, что он всегда использует соглашения США.

Очевидное решение - завершить переназначение ожидаемых символов локали, в вашем случае исключить все пробелы и . (группировка цифр), а затем заменить , на . (десятичный), чтобы получить желаемую функциональность.Не нужно ничего делать с - и + (насколько я знаю, они имеют одинаковое значение на всех романтических языках).

...