Каковы формальные и практические ограничения на значения в 'struct lconv', описывающие локаль? - PullRequest
13 голосов
/ 24 ноября 2011

Фон

Стандарт C99, раздел 7.11, описывает заголовок <locale.h> и его содержимое. В частности, он определяет struct lconv и говорит, что:

[...] В "C" локаль члены должны иметь значения указаны в комментариях.

char *decimal_point;     // "."
char *thousands_sep;     // ""
char *grouping;          // ""
char *mon_decimal_point; // ""
char *mon_thousands_sep; // ""
char *mon_grouping;      // ""
char *positive_sign;     // ""
char *negative_sign;     // ""
char *currency_symbol;   // ""
char frac_digits;        // CHAR_MAX
char p_cs_precedes;      // CHAR_MAX
char n_cs_precedes;      // CHAR_MAX
char p_sep_by_space;     // CHAR_MAX
char n_sep_by_space;     // CHAR_MAX
char p_sign_posn;        // CHAR_MAX
char n_sign_posn;        // CHAR_MAX
char *int_curr_symbol;   // ""
char int_frac_digits;    // CHAR_MAX
char int_p_cs_precedes;  // CHAR_MAX
char int_n_cs_precedes;  // CHAR_MAX
char int_p_sep_by_space; // CHAR_MAX
char int_n_sep_by_space; // CHAR_MAX
char int_p_sign_posn;    // CHAR_MAX
char int_n_sign_posn;    // CHAR_MAX

Раздел 7.11.2.1 «Функция localeconv ()» продолжает:

Члены структуры с типом char * являются указателями на строки, любая из которых (кроме decimal_point) может указывать на "", чтобы указать, что значение недоступно в текущая локаль или имеет нулевую длину. [...] Члены с типом char являются неотрицательные числа, любое из которых может быть CHAR_MAX, чтобы указать, что значение не доступно в текущей локали.

Это продолжает обсуждать каждого из членов. Вы можете увидеть 4 группы по 3 участника, одна из которых представляет p_cs_precedes, p_sep_by_space и p_sign_posn.

char p_cs_precedes
Установите 1 или 0, если currency_symbol соответственно предшествует или заменяет значение неотрицательного денежного количества в местном формате.

char p_sep_by_space
Установите значение, обозначающее разделение currency_symbol, строка знака и значение неотрицательного денежного количество.

char p_sign_posn Установите в значение, указывающее положение положительного знака для неотрицательное локально отформатированное денежное количество.

Приведены подробности толкования p_sign_posn; они не являются существенными для этого вопроса.

Стандарт также дает некоторые примеры того, как интерпретировать эти типы.

Если вы найдете оригинальный стандарт C99 (ISO / IEC 9899: 1999), имейте в виду, что как TC1 (Международный стандарт ISO / IEC 9899: 1999 Техническое исправление 1, опубликовано в 2001-09-01), так и TC2 (Международный стандарт ISO / IEC 9899: 1999 Техническое исправление 2, опубликованное 2004-11-15) вносит изменения в §7.11.2.1 (но TC3 не делает). Однако изменения не затрагивают и не влияют на ответы на вопросы, которые я собираюсь задать.


Вопросы

Мои первые два вопроса касаются четырех троек (cs_precedes, sep_by_space и sign_posn) и других более общих вопросов о том, что составляет действительный языковой стандарт:

  1. Реально или целесообразно иметь один или два члена тройки с обозначением CHAR_MAX, в то время как другие члены имеют значения в нормальном диапазоне (0-1, 0-1, 0-4)?
  2. Если это разумно, как следует толковать комбинации?

    Определены две комбинации (все значения установлены на CHAR_MAX, как в "C", и все значения установлены правильно); мне интересны остальные 6 гибридных настроек.

  3. Правильно ли сформирован языковой стандарт, если тройки определены, а соответствующий символ валюты - нет?

  4. Правильно ли сформирован языковой стандарт, если десятичная точка в денежном выражении не определена, но определен символ валюты.
  5. Если позиция знака не равна 0 (указывает на то, что значение заключено в круглые скобки), правильно ли сформирован языковой стандарт, если установлен символ валюты, но строки как положительного, так и отрицательного знака пусты?
  6. Имеет ли смысл определять положительную тройку, когда отрицательной тройки нет?

Я склонен ответить:

  1. Нет; либо все, либо ни один из членов тройки не должны быть установлены в CHAR_MAX.
  2. Не применимо с учетом ответа на (1).
  3. номер
  4. Нет (но есть граничный случай для старой итальянской валюты (лиры), где не было дробей, и поэтому не требовалось десятичной точки; это можно было бы обработать с условием, что денежная десятичная точка нужна только, если frac_digits или int_frac_digits больше нуля).
  5. номер
  6. номер

Реализация может затем применять эти правила, но возможно, что другая реализация будет интерпретировать правила по-разному и придет к другому выводу.

Что вы скажете?

1 Ответ

0 голосов
/ 12 декабря 2011

Формальные ограничения

Насколько я могу судить, ни Стандарт C, ни POSIX не устанавливают никаких правил относительно того, что является и не является действительным в struct lconv.Одной из вероятных причин этого является то, что ни одна функция в стандарте C или POSIX не принимает struct lconv в качестве аргумента;только функция localeconv() возвращает структуру:

 struct lconv *localeconv(void);

Следовательно, поскольку реализация является номинально единственным источником значений struct lconv, то, что бы реализация ни выполняла, должно быть вглаза реализации.В общем, это все еще прирожденная особенность;это обеспечивает функциональность, которую ничто не использует напрямую.За кулисами, однако, есть поддержка частей этой информации (подумайте printf() и scanf() и др., Для начала).Денежная информация не используется никакими стандартными функциями C.Они (заголовок <locale.h> и функции localeconv() и setlocale()) были добавлены комитетом в C89, частично для того, чтобы гарантировать, что может быть единый стандарт ISO для C, который будет таким же, как стандарт ANSI дляC.

Книга Plauger ' Стандартная библиотека C ' (которая реализует стандартную библиотеку C89) предоставляет функцию под названием _Fmtval(), которую можно использовать для форматирования международной валюты, национальной (местной)валюта и числа с использованием соглашений текущей локали, но, опять же, используемая структура определяется реализацией и не предоставляется пользователем.

POSIX предоставляет пару функций strfmon() и strfmon_l(), последний из которых принимает locale_t в качестве одного из аргументов.

ssize_t strfmon(char *restrict s, size_t maxsize, const char *restrict format, ...);
ssize_t strfmon_l(char *restrict s, size_t maxsize, locale_t locale,
                  const char *restrict format, ...);

Однако POSIX вообще ничего не говорит о содержимомтипа locale_t, хотя он предоставляет следующие функции для управления ими ограниченным образом:

Тем не менее, они обеспечивают минимальный и практический подход к манипулированию локалями,и определенно не вдаваться в подробности о том, что может или не может быть приемлемым в struct lconv.Есть также функции nl_langinfo():

#include <langinfo.h>

char *nl_langinfo(nl_item item);
char *nl_langinfo_l(nl_item item, locale_t locale);

Они позволяют вам узнать, по одному элементу за раз, значения частей локали, используя такие имена, какABDAY_1, чтобы узнать сокращенное название дня 1, которое является «Солнцем» в англоязычных локалях.В <langinfo.h> есть около 55 таких имен.Интересно, что набор не является полным;Вы не можете найти международный символ валюты таким образом.

Практические ограничения

Учитывая, что два основных соответствующих стандарта ничего не говорят об ограничениях на содержание struct lconv, мы остаемся спытаясь определить практические ограничения.

( в сторону : учитывая симметрию информации о национальном и международном форматировании в стандарте C99, в некоторых отношениях очень жаль, что структура не использоваласьдля кодирования информации; это позволяет аккуратному коду выбирать правильные биты и куски в общие функции. Некоторые поля (cs_precedes, sep_by_space) тоже могут быть логическими, но <stdbool.h> не было в C89.)

Повторяющиеся вопросы:

Мои первые два вопроса касаются четырех тройок (cs_precedes, sep_by_space и sign_posn) и других более общих вопросов.о том, что составляет действительный языковой стандарт:

  1. Реально или целесообразно иметь одного или двух членов тройки с обозначением CHAR_MAX, покаe Другие члены имеют значения в нормальном диапазоне (0-1, 0-1, 0-4)?
  2. Если это разумно, как следует интерпретировать комбинации?
  3. Является лиПравильно ли сформирован языковой стандарт, если тройки определены, но соответствующий символ валюты нет?
  4. Правильно ли сформирован языковой стандарт, если не задана десятичная точка в денежном выражении, но определен символ валюты.
  5. Если позиция знака не равна 0 (указывает на то, что значение заключено в круглые скобки), правильно ли сформирован языковой стандарт, если установлен символ валюты, но строки как положительного, так и отрицательного знака пусты?
  6. имеет смысл определить положительную тройку, если отрицательная тройка не является?

Оригинальные, наброски ответов были:

  1. Нет;либо все, либо ни один из членов тройки не должны быть установлены в CHAR_MAX.
  2. Не применимо с учетом ответа на (1).
  3. Нет.
  4. Нет (но естьявляется пограничным случаем для старой итальянской валюты (лиры), где не было дробей, и поэтому десятичная точка не требовалась, что может быть обработано при условии, что денежная десятичная точка нужна только в том случае, если frac_digits или int_frac_digits большечем ноль).
  5. Нет.
  6. Нет.

Потратив некоторое время на реализацию кода для обработки такого форматирования, мои первоначальные ответы остаются в значительной степени правильными, на мой взгляд.

Код, который я в итоге реализовал для проверки локалей, был:

/* Locale validation */
#define VALUE_IN_RANGE(v, mn, mx) ((v) >= (mn) && (v) <= (mx))
#define ASSERT(condition)           do { assert(condition); \
                                         if (!(condition)) \
                                             return false; \
                                       } while (0)
#define ASSERT_RANGE(v, mn, mx)     ASSERT(VALUE_IN_RANGE(v, mn, mx))

static bool check_decpt_thous_group(bool decpt_is_opt, const char *decpt,
                                    const char *thous, const char *group)
{
    /* Decimal point must be defined; monetary decimal point might not be */
    ASSERT(decpt != 0);
    ASSERT(decpt_is_opt || *decpt != '\0');
    /* Thousands separator and grouping must be valid (non-null) pointers */
    ASSERT(thous != 0 && group != 0);
    /* Thousands separator should be set iff grouping is set and vice versa */
    ASSERT((*thous != '\0' && *group != '\0') ||
           (*thous == '\0' && *group == '\0'));
    /* Thousands separator, if set, should be different from decimal point */
    ASSERT(*thous == '\0' || decpt_is_opt ||
          (*decpt != '\0' && strcmp(thous, decpt) != 0));
    return true;
}

static bool currency_valid(const char *currency_symbol, char frac_digits,
                           char p_cs_precedes, char p_sep_by_space, char p_sign_posn,
                           char n_cs_precedes, char n_sep_by_space, char n_sign_posn)
{
    ASSERT(currency_symbol != 0);
    if (*currency_symbol == '\0')
    {
        ASSERT(frac_digits    == CHAR_MAX);
        ASSERT(p_cs_precedes  == CHAR_MAX);
        ASSERT(p_sep_by_space == CHAR_MAX);
        ASSERT(p_sign_posn    == CHAR_MAX);
        ASSERT(n_cs_precedes  == CHAR_MAX);
        ASSERT(n_sep_by_space == CHAR_MAX);
        ASSERT(n_sign_posn    == CHAR_MAX);
    }
    else
    {
        ASSERT_RANGE(frac_digits,    0, 9);     // 9 dp of currency is a lot!
        ASSERT_RANGE(p_cs_precedes,  0, 1);
        ASSERT_RANGE(p_sep_by_space, 0, 2);
        ASSERT_RANGE(p_sign_posn,    0, 4);
        ASSERT_RANGE(n_cs_precedes,  0, 1);
        ASSERT_RANGE(n_sep_by_space, 0, 2);
        ASSERT_RANGE(n_sign_posn,    0, 4);
    }
    return true;
}

static bool locale_is_consistent(const struct lconv *loc)
{
    if (!check_decpt_thous_group(false, loc->decimal_point, loc->thousands_sep, loc->grouping))
        return false;
    if (!check_decpt_thous_group((loc->frac_digits == 0 || loc->frac_digits == CHAR_MAX),
                    loc->mon_decimal_point, loc->mon_thousands_sep, loc->mon_grouping))
        return false;
    /* Signs must be valid (non-null) strings */
    ASSERT(loc->positive_sign != 0 && loc->negative_sign != 0);
    /* Signs must be different or both must be empty string (and probably n_sign_posn == 0) */
    ASSERT(strcmp(loc->positive_sign, loc->negative_sign) != 0 || *loc->negative_sign == '\0');
    if (!currency_valid(loc->currency_symbol, loc->frac_digits,
                        loc->p_cs_precedes, loc->p_sep_by_space, loc->p_sign_posn,
                        loc->n_cs_precedes, loc->n_sep_by_space, loc->n_sign_posn))
        return false;
    if (!currency_valid(loc->int_curr_symbol, loc->int_frac_digits,
                        loc->int_p_cs_precedes, loc->int_p_sep_by_space, loc->int_p_sign_posn,
                        loc->int_n_cs_precedes, loc->int_n_sep_by_space, loc->int_n_sign_posn))
        return false;
    /*
    ** If set, international currency symbol must be 3 (upper-case)
    ** alphabetic characters plus non-alphanum separator
    */
    if (*loc->int_curr_symbol != '\0')
    {
        ASSERT(strlen(loc->int_curr_symbol) == 4);
        ASSERT(isupper(loc->int_curr_symbol[0]));
        ASSERT(isupper(loc->int_curr_symbol[1]));
        ASSERT(isupper(loc->int_curr_symbol[2]));
        ASSERT(!isalnum(loc->int_curr_symbol[3]));
    }
    return true;
}

Стандарт гласит, что loc->int_curr_symbol[3] используется в качестве символа «пробел» при форматировании международноговалюты, и не имеет смысла разрешать алфавитный символ, а также международный код валюты ISO 4217, который представляет собой три заглавные буквы из основного алфавита.Допуск цифры может привести к путанице, если знак тоже отдельный, поэтому я считаю, что утверждение !isalnum(loc->int_curr_symbol[3]) разумно.Строгая проверка подтвердила бы, что международный символ валюты является одним из тех, которые перечислены в ISO 4217;это немного сложно для кода, хотя!

...