Программисты пишут программы, которые потом могут использоваться в разных странах с разными стандартами и традициями, поэтому им приходиться обращать внимание на такие детали, которые в обычной жизни не замечаются в силу своей очевидности.
Вот, например, у меня на компьютере сегодняшняя дата пишется так - 06.01.2023, а на одном из моих серверов в уголке экрана светится 01/06/23. Это американский формат, и я каждый раз мучительно вспоминаю, 01/06 это первое июня или всё таки шестое января.
Ещё одно национальное отличие - символ отделения целой и дробной части в числах. В России по традиции и в соответствии со стандартами надо отделять дробную часть запятой (Пи = 3,14), а американцы (и вслед за ними программисты) используют десятичную точку (Pi = 3.14). Из-за этого различия иногда случаются разные казусы.
В русском Excel в разделе "Сохранить как" есть формат файла "CSV (разделитель - запятая)". Если вы сохраните таблицу в этом формате, то получите текстовый файл, в котором разделителем колонок будет... Нет, не запятая, как было обещано, а точка с запятой, поскольку запятая уже занята как разделитель дробной части. Если же вы попробуете открыть "американский" CSV-файл (с разделителем запятой и десятичной точкой), скачанный из интернета, то у вас возникнет сразу несколько проблем. Во-первых, все столбцы сольются в один, поскольку не будет распознан разделитель столбцов. Это можно преодолеть, предварительно вставив в начало файла магическую строку sep=, , но тут вы нарветесь на вторую проблему - теперь ваши числа с десятичными точками будут восприняты не как числа, а как текст. В принципе, можно выделить колонки с числами и сделать во всех ячейках замену точки на запятую, но у себя я наткнулся на третью проблему - при открытии файла Excel попытался угадать формат ячеек и безвозвратно заменил небольшие числа вида "1.2" на даты - "1 февраля 2023".
Для того, чтобы упростить работу с национальными форматами, программисты разработали специальные базы данных, автоматизирующие процесс локализации программ (по-английски кратко L10N поскольку L[ocalizatio]N). На программистском жаргоне эти базы называются локалями и обозначаются либо названием языка ("Русский"), либо аббревиатурой, составленной из названия языка и названия страны (en_US - американский английский). В Windows можно выбрать стандартную локаль (ищем в настройках Windows 10 слово "Регион") и (при желании) поправить отдельные ее параметры ("Панель управления\Часы и регион"). В командной строке Linux локаль в целом выбирается установкой значения переменной LANG или LC_ALL, а частичные изменения настраиваются через переменные LC_NUMERIC, LC_TIME и т.д. У меня, например, в Windows отдельно настроена десятичная точка, а в LINUX установлен программистский языковой стандарт LANG=C. В Excel в дополнительных параметрах есть отдельный пункт для разделителя дробной части - можно выбирать между разделителем из системной локали и произвольным символом, например, точкой.
А теперь про программистские ошибки.
В 2000-х годах в суперкомпьютерном центра МГУ эксплуатировался NUMA сервер от IBM. Сейчас не могу вспомнить, какой именно модели, поскольку в последний раз получал к нему доступ десять лет назад в 2013 году. На сервере использовался родной компилятор от IBM и этот компилятор имел замечательную ошибку. При установленной русской локали ru_RU он не мог распознать вещественные числа в программах, поскольку считал, что разделителем десятичных знаков должна быть запятая. Программы не собирались, а компилятор выдавал сообщение о недопустимом символе в позициях, в которых стояли десятичные точки. Когда моя коллега впервые наткнулась на эту ошибку у меня ушло минут 40 для того, чтобы осознать, что дело не в кодировке и не в невидимых символах в окрестности точек, а именно в локали. Ради интереса, я поменял все десятичные точки на запятые, но это не помогло: в зависимости от контекста, эти запятые интерпретировались либо как разделители параметров функции, либо как оператор «запятая», что, в общем-то, было вполне ожидаемо.
Недавно наткнулся в своей программе на то, что при установленной русской локали вывод вещественных чисел в C++ двумя разными способами дает разные результаты.
float f=3.14;
// Укажем, что мы хотим форматировать числа, даты, валюты и пр.
// по правилам русского языка
setlocale(LC_ALL, "Russian");
// libc по умолчанию использует пользовательские настройки
printf(«%f\n», f); // 3,14
// Стандартные потоки по умолчанию используют локаль POSIX
std::cout << f << std::endl; // 3.14
// Для вывода в cout по национальным правилам надо проделать не совсем очевидные вещи:
// Создание копии пользовательской локали
std::locale loc("");
std::cout << "Locale name = " << loc.name() << std::endl; //Russian
// Установка формата вывода в соответствии с указанной локалью
std::cout.imbue(loc);
// Вот теперь можно выводить числа в национальном формате
std::cout << f << std::endl; // 3,14
Давно хотел написать эту заметку, но объём материала казался каким-то незначительным. Сегодня наткнулся в блоге у Евгения Степанищева (
https://bolknote.ru/all/desyatichnaya-zapyataya/) на ещё один интересный пример, на этот раз из ранних версий PHP4, и решил, что пора писать обобщающий материал. Хотя бы для того, чтобы обратить на эту проблему внимание начинающих программистов.
Пример от Евгения (
https://onlinephp.io/c/fe351). Установка локали в PHP 4.2.3 влияет на конвертацию строки в число:
var_dump('1,4' + '0,7'); // int(1)
var_dump('1,4' + '0.7'); // float(1.7)
setlocale(LC_ALL, 'ru_RU.koi8r');
var_dump('1,4' + '0,7'); //float(2,1)
var_dump('1,4' + '0.7'); // float(1.4)