Формальные ограничения
Насколько я могу судить, ни Стандарт 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
) и других более общих вопросов.о том, что составляет действительный языковой стандарт:
- Реально или целесообразно иметь одного или двух членов тройки с обозначением CHAR_MAX, покаe Другие члены имеют значения в нормальном диапазоне (0-1, 0-1, 0-4)?
- Если это разумно, как следует интерпретировать комбинации?
- Является лиПравильно ли сформирован языковой стандарт, если тройки определены, но соответствующий символ валюты нет?
- Правильно ли сформирован языковой стандарт, если не задана десятичная точка в денежном выражении, но определен символ валюты.
- Если позиция знака не равна 0 (указывает на то, что значение заключено в круглые скобки), правильно ли сформирован языковой стандарт, если установлен символ валюты, но строки как положительного, так и отрицательного знака пусты?
- имеет смысл определить положительную тройку, если отрицательная тройка не является?
Оригинальные, наброски ответов были:
- Нет;либо все, либо ни один из членов тройки не должны быть установлены в CHAR_MAX.
- Не применимо с учетом ответа на (1).
- Нет.
- Нет (но естьявляется пограничным случаем для старой итальянской валюты (лиры), где не было дробей, и поэтому десятичная точка не требовалась, что может быть обработано при условии, что денежная десятичная точка нужна только в том случае, если
frac_digits
или int_frac_digits
большечем ноль). - Нет.
- Нет.
Потратив некоторое время на реализацию кода для обработки такого форматирования, мои первоначальные ответы остаются в значительной степени правильными, на мой взгляд.
Код, который я в итоге реализовал для проверки локалей, был:
/* 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;это немного сложно для кода, хотя!