Ориентация главы

Взаимные превращения чисел и текста кажутся мелочью, на деле это зона частых аварий: atoi на нечисловом входе молча возвращает 0, strtoll «разбирает, сколько получится» и оставляет полуконечный мусор, %.17g выводит избыточно длинное, а %.6f теряет точность, граница INT64_MIN ломает реализации «сперва в положительное, потом отрицание». Модуль number библиотеки XRT отвечает на эти проблемы тремя решениями: строгий разбор (весь вход легален — или отказ целиком; снисходительность включается явно флагами), кратчайший круговой проход (вывод числа с плавающей точкой несёт ровно столько цифр, чтобы разбор вернул исходное значение) и строки форматирования (стиль Python — группировка, система счисления, проценты объявляются разом). Глава разбирает все три линии; заодно разворачивается идея тестирования граничных значений — зеркало «внедрения отказов» главы 6 в мире чисел: и то и другое сознательно создаёт экстремальные условия и проверяет, надёжен ли результат вне маленьких данных.

26.1引入

Три места аварии. Первое: в конфигурационном файле порт записан как "8080x" — atoi возвращает 8080, хвостовой x молча отброшен, сервис поднялся на «правдоподобном» порту. Второе: числа с плавающей точкой в журнале печатаются %.6f, при чтении теряются три разряда, регрессионное сравнение не проходит никогда; смена на %.17g даёт 0.10000000000000001 — то, что никто не хочет читать. Третье: разобрать " -1_234.567_890e-2 " — краевые пробелы, группировка цифр подчёркиванием, научная нотация: тройка, на которую стандартная библиотека не согласна ни в чём, а ручная реализация съедает вечер.

Общий корень всех трёх сцен: функции преобразования стандартной библиотеки родились в снисходительную эпоху и сделали «терпимость к ошибкам» поведением по умолчанию — инженерии же нужно ровно обратное: строгость по умолчанию, снисходительность явно. Функции разбора XRT не останавливаются на полпути: значение возвращается, только если входное представление легально целиком; любая примесь — отказ с точной категорией в слоте ошибки; чтобы разрешить пробелы и подчёркивания — объявите это флагами явно.

26.2概念

Строгий разбор: всё или отказ

1Входное представлениевесь диапазон должен образовывать одно легальное число
2Снисходительность опциональнапробелы/подчёркивания разрешаются явно флагами разбора, по умолчанию отказ
3Корректность границINT64_MIN и другие граничные значения обрабатываются точно, без промежуточных переполнений
4Диагностика отказаотказ целиком + запись в слот ошибки, полрезультата не остаётся

Строгость xrtIntParse(视图, 基数, 标志, 出参) (представление, основание, флаги, выходной параметр) трёхслойна: ни краевых пробелов (кроме XNUMBER_PARSE_SPACE), ни подчёркиваний внутри цифр (кроме XNUMBER_PARSE_SEPARATOR), успех — только при полном потреблении входа — "8080x" выбывает уже на первом слое. Версия для чисел с плавающей точкой xrtNumParse разделяет ту же семантику, добавляя явную обработку научной нотации и специальных значений (inf/nan). Дисциплина граничных значений заслуживает отдельного акцента: в тесты ставьте INT64_MIN — любая реализация «сперва перевести в положительное, потом отрицать» ошибается именно здесь (положительное 9223372036854775808 переполняется); проходит только накопление в отрицательном на всём пути. Запись граничных значений в тесты — жёсткое требование упражнений главы.

Кратчайший круговой проход: фактический стандарт сериализации чисел с плавающей точкой

xrtNumString(值, 精度) (значение, точность) по умолчанию (точность 0) выводит кратчайшее круговое представление: ровно столько десятичных цифр, чтобы Parse вернул double, побитово равный исходному. Оно куда короче %.17g (0.1 выводится как 0.1, а не 0.10000000000000001) и, в отличие от %.6f, ничего не теряет — контур сериализация-десериализация замыкается с нулевой ошибкой. Когда задавать точность руками? При показе человеку (два знака в отчёте); машинный круговой проход — всегда кратчайший.

Строки форматирования: все пожелания объявляются разом

ФорматЭффектЭквивалент в стандартной библиотеке
,dдесятичное с разделителями тысяч: -123,456,789нет (цикл вручную)
#_Xпрефикс 0X + группировка подчёркиваниями + верхний регистр hexкомбинация нескольких флагов
,.2fтысячи + два знака после точкинет
.1%проценты (авто ×100 и знак %)посчитать руками и склеить
cкодовая точка целого как символ UTF-8конвертация wctomb

Три входа xrtIntFormat / xrtUIntFormat / xrtNumFormat покрывают знаковые, беззнаковые и плавающие; продукты все владеющие (освобождение xrtFree). Стиль строк форматирования повторяет спецификацию format в Python — писавшие на Python платят ноль стоимости обучения, остальным всё равно спокойнее, чем запоминать, какие из %08X и %'d printf на какой платформе поддерживаются.

Разделение труда с семейством printf

xrtFormat (из главы 2) и семейство Format этой главы не конкуренты: первый собирает произвольную смешанную строку («заказ %s, %d штук»), вторые специализируются на показательных форматах чисел (тысячи, система счисления, проценты). Правило большого пальца: текстовые шаблоны — Format, показ чисел — семейство Format этой главы, оба возвращают владеющие продукты и оба принимают подмножество синтаксиса в стиле printf (с отказом от %n — дисциплина безопасности из главы 4).

26.3示例

Полная программа: строгий разбор и вывод в нескольких системах счисления

Из образца репозитория examples/number/integer/main.c — двойная проверка на граничном INT64_MIN:

examples/number/integer/main.c源码 ↗



















 */

#include <stdio.h>

#include <xrt.h>



int main(void)
{
	int64 iValue;
	str sDecimal;
	str sHex;

	




	 */
	if ( !xrtIntParse(XRT_STR_LITERAL(" -9_223_372_036_854_775_808 "),
		10, (uint32)XNUMBER_PARSE_SPACE |
		(uint32)XNUMBER_PARSE_SEPARATOR, &iValue) ) {
		return 1;
	}

	/* 十进制原样输出;十六进制加 0X 前缀并大写。 */
	sDecimal = xrtIntString(iValue, 10, 0);
	sHex = xrtIntString(iValue, 16,
		(uint32)XNUMBER_PREFIX | (uint32)XNUMBER_UPPER);
	if ( (sDecimal == NULL) || (sHex == NULL) ) {
		xrtFree(sHex);
		xrtFree(sDecimal);
		return 1;
	}
	printf("%s\n%s\n", sDecimal, sHex);
	xrtFree(sHex);
	xrtFree(sDecimal);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/number/integer/main.c -lws2_32 -liphlpapi-9223372036854775808-0X8000000000000000

Что только что произошло. (1) Вход разбора — десятичная запись INT64_MIN; образец двумя флагами явно разрешил пробелы и подчёркивания (и те и другие есть во входе) — снисходительность прописана чёрным по белому. (2) Эмпирическое доказательство корректности границ: разобрано -9223372036854775808 и отозвано обратно — будь реализация «сперва в положительное, потом отрицание», положительная сторона переполнилась бы на 9223372036854775808 и результат разошёлся бы на единицу; этот вывод и есть сигнал (не)соответствия. (3) xrtIntString выводит в системе счисления 16 с префиксом 0X и верхним регистром — формат выхода объявлен флагами, префикс не собирается руками. Сломать этот образец нарочно — часть упражнений главы: уберите флаги разбора, подайте вход с примесями и наблюдайте строгий отказ вместо молчаливого усечения.

Полная программа: всё семейство форматирования

Из examples/number/format/main.c — пять строк форматирования одной витриной:

examples/number/format/main.c源码 ↗






















 */

#include <stdio.h>

#include <xrt.h>



int main(void)
{
	str sInteger = xrtIntFormat(
		INT64_C(-123456789), XRT_STR_LITERAL(",d"));
	str sHex = xrtUIntFormat(
		UINT64_C(0xDEADBEEF), XRT_STR_LITERAL("#_X"));
	str sFloat = xrtNumFormat(
		1234567.895, XRT_STR_LITERAL(",.2f"));
	str sPercent = xrtNumFormat(
		0.125, XRT_STR_LITERAL(".1%"));
	str sCharacter = xrtIntFormat(
		20320, XRT_STR_LITERAL("c"));

	/* 任何一个失败都统一走清理(xrtFree 允许 NULL)。 */
	if ( (sInteger == NULL) || (sHex == NULL) ||
		 (sFloat == NULL) || (sPercent == NULL) ||
		 (sCharacter == NULL) ) {
		xrtFree(sInteger);
		xrtFree(sHex);
		xrtFree(sFloat);
		xrtFree(sPercent);
		xrtFree(sCharacter);
		return 1;
	}
	printf("%s\n%s\n%s\n%s\n%s\n",
		sInteger, sHex, sFloat, sPercent, sCharacter);
	xrtFree(sInteger);
	xrtFree(sHex);
	xrtFree(sFloat);
	xrtFree(sPercent);
	xrtFree(sCharacter);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/number/format/main.c -lws2_32 -liphlpapi-123,456,7890XDEAD_BEEF1,234,567.9012.5%你

Что только что произошло. (1) Целое с разделителями тысяч — стандарт читаемости отчётов и журналов; стандартной библиотеке нужен рукописный цикл вставки запятых, здесь это один ,d. (2) Hex с группировкой подчёркиваниями — отладочный стиль 0XDEAD_BEEF: префикс, группировка и верхний регистр — три пожелания одной строкой формата. (3)(4) Фиксированная точка и проценты — обратите внимание: проценты это «авто ×100 плюс %», вход 0.125 даёт 12.5%, без ручной арифметики и ручной склейки. (5) Рендеринг символа: целое 20320 по кодовой точке Unicode выводится как символ UTF-8 в пятой строке — первое знакомство с превращением кодовая точка ↔ символ у семейства наборов символов (глава 27). Все пять строк вывода — владеющие продукты, каждая освобождается своим xrtFree.

26.4契约

  • Строгость по умолчанию: успех только при лёгком целиком входе; снисходительность (пробелы/подчёркивания) включается явно флагами; отказ ставит ошибку и не пишет выход.
  • Точность на границах: граничные значения вроде INT64_MIN/UINT64_MAX разбираются точно; в реализации нет пути «сперва в положительное».
  • Кратчайший круговой проход: точность xrtNumString по умолчанию и есть кратчайший круговой проход; машинная сериализация никогда не задаёт точность руками.
  • Владеющие продукты: семейства String/Format возвращают str, ровно одно освобождение xrtFree.
  • Явные системы счисления: основание разбора и система счисления/префикс вывода объявляются параметрами/флагами — без угадываний и умолчаний в пользу шестнадцатеричной.

От примеров к инженерии: три хозяина функций разбора

Три способа крепления функций разбора в инженерии определяют стратегию обработки ошибок. Валидация на входе: загрузка конфигурации, декодирование полей протокола вызывают разбор на границе системы — отказ отвергает весь вход, а ошибка несёт контекст «какое поле, какая примесь» (звёздный час форматирования ошибок, глава 4). Преобразование на горячем пути: извлечение чисел, исполняющееся на каждый запрос, — строгость покупает определённость «отказ означает баг»; горячему пути не нужны ветки подстраховки. Форматирование слоя показа: семейство Format смотрит в человеческие глаза (журналы, отчёты, сообщения об ошибках) — тысячи и проценты живут здесь, а межмашинная передача ходит кратчайшим круговым проходом, не строками формата. Три хозяина отвечают трём позам «граница отвергает, путь утверждает, показ форматирует» — неправильная поза встречается чаще неправильной функции.

Взгляд на тестирование: граничные значения прежде охвата

Повторное появление INT64_MIN в этой главе не совпадение — оно демонстрирует взгляд на тесты: выбор граничных значений важнее количества случаев. Контрольный список границ для числовых преобразований: экстремумы каждого типа, ноль и минус ноль, ведущие нули, одиночный символ, пустая строка, одинокий знак, строка цифр максимальной длины — пятнадцать отобранных границ убивают девять десятых дефектов реализации, тогда как десять тысяч случайных значений могут не задеть ни одной. Эта идея вернётся в главе 6 (выбор точек внедрения FailAfter) и в главе 30 (числовые границы JSON).

Типичные ошибки

Ловушка 1: строгость разбора с мировоззрением atoi:Ловушка 1: строгость разбора с мировоззрением atoi

Симптом: после переезда с кода, который «всегда работал», на разбор XRT та же пачка конфигураций вдруг сыпется отказами — ошибки концентрируются на полях с пробелами и примесями.

Причина: снисходительное поведение стандартной библиотеки (пропуск пробелов, разбор «сколько получится») приняли за контракт; строгий разбор превращает эти проблемы из «молчаливых неверных значений» в «явные отказы» — отказ не регрессия, а баг, который был там всегда и впервые попал в поле зрения.

错误写法
int iPort = atoi(sConfig);   /* "8080x" -> 8080, "x" молча отброшен */
正确写法
int64 iPort;
if ( !xrtIntParse((xstrview){ sConfig, strlen(sConfig) }, 10, 0, &iPort) ) {
	ReportConfigError(sConfig);   /* "8080x" отказывается прямо здесь - примеса увидена */
	return false;
}
Ловушка 2: точность сериализации плавающих задана руками:Ловушка 2: точность сериализации плавающих задана руками

Симптом: проверка контура сериализация-десериализация изредка падает; или числа в журнале превращаются в семнадцатизначный «шум точности».

Причина: ручная точность — яма с двух концов: слишком коротко — теряется точность (круговое сравнение больше не сходится), слишком длинно — шум на выходе (судьба .17g).

错误写法
char sBuf[32];
snprintf(sBuf, sizeof(sBuf), "%.17g", fValue);   /* 0.1 -> 0.10000000000000001 */
正确写法
str sText = xrtNumString(fValue, 0);   /* кратчайший круговой проход: 0.1 остаётся 0.1 */
/* ... используем sText ... */
xrtFree(sText);

Практические задания

Базовое: проверка строгости

Разберите пять входов "42", " 42", "4_2", "42x", "" с флагами по умолчанию и с включёнными — запишите десять результатов: сперва в столбик ожидания (какие успешны и с каким значением, какие отказ), потом прогон и сверка; к следующему упражнению — только когда все десять сошлись.

Продвинутое: набор граничных тестов

Напишите набор граничных тестов для xrtIntParse: INT64_MIN/MAX, UINT64_MAX, -0, ведущие нули, одиночное подчёркивание, одиночный пробел — точный ожидаемый результат на каждый вход. Подсказка: это миниатюра тестов репозитория; выбор границ важнее количества утверждений.

Вызов: читатель конфигурации (три модуля в ансамбле)

Реализуйте read_int(配置文本, 键) (текст конфигурации, ключ): разрезка по строкам (глава 25), Cut на ключ и значение, значение — через строгий разбор (пробелы разрешены, подчёркивания нет), ключ — без учёта регистра. Критерии приёмки: пять видов уродливых входов (примесь, выход за диапазон, пустое значение, отсутствующий ключ, дублирующийся ключ) дают различимые отчёты об ошибках; значения INT64_MIN/MAX проходят контур точно.

Итоги

ТемаКратко
Строгий разборxrtIntParse/xrtNumParse: весь вход легален или отказ; снисходительность флагами
Флаги разбораXNUMBER_PARSE_SPACE (пробелы) / XNUMBER_PARSE_SEPARATOR (подчёркивания)
Вывод целыхxrtIntString: система счисления + префикс + регистр объявляются флагами
Кратчайший круговой проходxrtNumString(值, 0): ровно столько цифр, чтобы круг сошёлся
Строки формата,d тысячи / #_X hex / ,.2f фикс / .1% проценты / c кодовая точка
Владениевсе продукты str, ровно один xrtFree; у семейства Format есть и вариант To в буфер вызывающего
Дисциплина граництесты обязаны включать INT64_MIN — он специализируется на убийстве реализаций «сперва в положительное, потом отрицание»
Три хозяинавалидация на границе отвергает / горячий путь утверждает / слой показа форматирует — поза важнее выбора функции
Взгляд на тестыпятнадцать отобранных границ ценнее десяти тысяч случайных — выбор прежде охвата

📖 Справочники API в этой главе:number (22 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки