Одна и та же строка дает 12,5 или 125 — и база тут не виновата

06.10.26

Разработка - Механизмы платформы 1С

Присваивание, в котором тип не совпадает, платформа не роняет: она молча приводит значение к типу приемника. Обычно это считают удобством. Замер показал, насколько дорого это удобство. Один и тот же код, одна и та же база, одна и та же таблица — строка «12,5» в колонку Число дает 12,5 в одном сеансе и 125 в другом. Ни ошибки, ни предупреждения. Если это сумма документа, разница в десять раз уедет в проводки. В статье: полная таблица того, что платформа делает с несовпадающим типом (чему равны «1 000», «abc», слишком большое число и слишком точное), почему КодЛокализацииИнформационнойБазы тут не помогает, и как разобрать строку так, чтобы результат не зависел от сеанса. Отдельно — почему Новый Файл(Путь).Существует() не компилируется, и чем опасен вывод, который из этой ошибки обычно делают. И честно про то, как первая версия моей же функции разбора ломала ровно то, что чинила. Протоколы всех прогонов приложены.

Есть присваивание, в котором тип не совпадает:

СтрокаТаблицы.Сумма = "12,5";   // колонка объявлена как Число(10,2)

Платформа не падает. Она молча приводит значение к типу приемника и работает дальше. Это известно и обычно воспринимается как удобство: не надо писать Число() руками.

Удобство выходит боком, и вот насколько. Один и тот же код, одна и та же база, одна и та же таблица. Разница только в том, из какого сеанса код запущен:

 

Что кладем в колонку Число(10,2) Сеанс первый Сеанс второй
строку "12,5" 12,5 125

 

Не ошибка, не исключение, не предупреждение. Просто в одном случае двенадцать с половиной, а в другом сто двадцать пять. Если это сумма документа, разница в десять раз уедет в проводки. Как, по-вашему, такое ловится до того, как уедет?

Ниже — откуда это берется, полная таблица того, что платформа делает с несовпадающим типом, и почему привычный способ проверить локализацию базы тут не помогает.

 

Как замерялось

Бухгалтерия 3.0.206, платформа 8.5, прогоны 01.10.2026. Таблица значений с тремя типизированными колонками: Число(10,2), Дата, Строка(100). В каждую по очереди кладется значение не того типа, и сразу после присваивания читается, что в ней оказалось:

Таблица.Колонки.Добавить("Чис", Новый ОписаниеТипов("Число",
	Новый КвалификаторыЧисла(10, 2)));
СтрокаТЗ = Таблица.Добавить();
...
СтрокаТЗ.Чис = Значение;
Возврат "тип " + ТипЗнч(СтрокаТЗ.Чис) + " значение [" + Строка(СтрокаТЗ.Чис) + "]";

Читать надо именно сразу. Если присвоить десять значений подряд и посмотреть в конце, увидишь только последнее — я на этом сначала и сел, и из первого прогона половину выводов пришлось выкинуть.

Два сеанса: через внешнее соединение и через веб-клиент. Код между ними не менялся ни на символ — в обоих случаях исполнялся один и тот же модуль одной и той же собранной обработки. Это важно, потому что первая версия замера сравнивала реквизит формы в одном сеансе с колонкой таблицы в другом, и тогда менялись сразу две вещи. Такое сравнение ничего не доказывает, и его пришлось переделать.

 

Полная таблица

Русская локализация базы, сеанс внешнего соединения:

 

Кладем Получаем Комментарий
"abc" 0 тихо, без исключения
"12,5" 12,5  
"12.5" 12,5 точка тоже понимается
" 7 " 7 пробелы по краям срезаются
"1 000" 0 пробел внутри убивает все число
Истина 1  
Неопределено 0  
12.345 12,35 округлено до двух знаков по квалификатору
99999999999 99 999 999,99 обрезано по разрядности колонки

 

Три строки тут заслуживают отдельного взгляда.

"1 000" дает ноль, а не тысячу. Пробел как разделитель разрядов платформа не понимает, и в отличие от "abc" эта строка выглядит как совершенно нормальное число. Именно так приходят суммы из выгрузок, из скопированного в буфер и из того, что пользователь набрал руками.

99999999999 превращается в 99 999 999,99. Не ошибка «не лезет», не ноль, а максимум, который влезает в Число(10,2). То есть значение подменяется на похожее и правдоподобное — худший из возможных вариантов, потому что такое число пройдет любую проверку на пустоту и на знак.

12.345 становится 12,35. Округление по квалификатору тоже молчаливое. Для суммы это обычно и нужно, для коэффициента или курса — почти никогда.

Для даты и строки короче:

 

Кладем в Дата Получаем
0 пустая дата
"нетдаты" пустая дата
Неопределено пустая дата

 

А вот со строкой есть отдельная засада. Число 1000000, положенное в Строка(100), становится не 1000000, а 1 000 000 — с разделителями разрядов. Коды символов в русском сеансе: 49, 160, 48, 48, 48, 160, 48, 48, 48. Сто шестидесятый — неразрывный пробел. Такую строку не сравнить с той, что пришла из файла, и Число() ее обратно не возьмет.

 

Тихо — это не то же, что мягко

Самое неприятное выясняется, если положить рядом неявное приведение и явный вызов:

неявно:  СтрокаТЗ.Чис = "abc"   ->  0
явно:    Число("abc")           ->  Преобразование значения
                                    к типу Число не может быть выполнено

Одна и та же задача, одни и те же данные, и ровно противоположное поведение. Явный вызов кричит, неявное присваивание молчит и кладет ноль.

Отсюда следствие, которое стоит проговорить. Код с Число(ЗначениеИзФайла) упадет на мусоре и заставит разобраться. Код с Реквизит = ЗначениеИзФайла на том же мусоре отработает штатно и запишет ноль. Второй вариант короче, выглядит аккуратнее и в обзоре кода не вызывает вопросов.

 

А исключение где?

Нигде. За все пробы приведения не выбросилось ни одного исключения: все строки таблицы отчитались словом «прошло». Это и есть вся проблема в одной строке.

 

Теперь про сеанс

Возвращаюсь к тому, с чего начал. Два прогона, один и тот же модуль:

 

  внешнее соединение веб-клиент
КодЛокализацииИнформационнойБазы() ru_RU ru_RU
как сеанс печатает 12.5 12,5 12.5
как сеанс печатает 1000000 1 000 000 1,000,000
"12,5" в колонку Число 12,5 125
"12.5" в колонку Число 12,5 12.5
Число("12,5") 12,5 125
коды символов у строки из 1000000 49 160 48 48 48 160 48 48 48 49 44 48 48 48 44 48 48 48

 

Разбор строки идет по региональным настройкам сеанса. В сеансе, где разделитель дробной части точка, запятая понимается как разделитель разрядов: "12,5" читается как «двенадцать тысяч пять», лишние разряды отбрасываются, остается 125.

И вот главное для практики: КодЛокализацииИнформационнойБазы() в обоих прогонах вернул ru_RU. По нему разницу не увидеть, потому что он про базу, а не про сеанс. База одна, а поведение разное.

Веб-клиент в этом замере шел с английскими региональными настройками. Откуда они взялись, я измерял не напрямую: вероятнее всего, такой язык запросил браузер, но прямой пробы с другим языком браузера у меня пока нет, так что берите это как объяснение, а не как факт. Измерено другое, и этого достаточно: разделитель в веб-сеансе оказался точкой при русской локализации базы. То есть одного пользователя с иначе настроенным сеансом хватает, чтобы тот же код в той же базе начал читать суммы по-другому.

 

Про Новый Тип(...).Метод()

Заодно в этом же прогоне проверилась конструкция, которую легко написать по привычке из других языков:

Р = Новый Файл("C:\").Существует();
// {<Неизвестный модуль>(1,23)}: Неопознанный оператор

Обратиться к методу прямо на выражении Новый нельзя. Нужна промежуточная переменная, и тогда все работает:

Ф = Новый Файл("C:\");
Р = Ф.Существует();

Рисунок одинаковый на любом типе: Новый Массив().Количество(), Новый Структура("А", 1).Количество(), Новый ТаблицаЗначений().Количество() — все «Неопознанный оператор». Скобки не спасают: (Новый Массив).Количество() дает ту же ошибку. А если скобок после типа не поставить вовсе, ошибка меняется на «Тип не определен (Массив.Количество)» — разборщик читает все выражение как одно имя типа.

Отдельно рядом стоит СистемнаяИнформация. Это тип, а не функция, поэтому СистемнаяИнформация().ВерсияПриложения дает «Процедура или функция с указанным именем не определена». Правильно так же, через переменную.

Опасность тут не в самой ошибке — она ловится первой же сборкой. Опасность в выводе, который из нее делают. У меня в практике было так: конструкция не компилировалась в клиентском коде, из этого был сделан вывод, что синхронная работа с файлами на клиенте запрещена, и рабочая проверка файла удалена из состава продукта. Проверка гипотезы заняла одну минуту: в типовых конфигурациях стоит разрешение синхронных вызовов, никакого запрета не было. Выкинули работающий код из-за пропущенной промежуточной переменной.

 

Где это уже лежит в вашем коде

Тихое приведение не оставляет следов, поэтому искать надо не ошибку, а место, где она возможна. Мест таких немного, и все они узнаются по виду.

Присваивание в типизированный приемник значения, пришедшего извне. Реквизит объекта, реквизит формы, колонка таблицы с заданным типом, параметр запроса — слева тип задан жестко, справа стоит то, что принесли. Если справа строка из файла или из поля ввода, приведение состоится молча.

Заполнение по имени. ЗаполнитьЗначенияСвойств, Структура в цикле, обход колонок строкой — все это переносит значения без сверки типов, и ошибка уезжает на шаг дальше от того места, где ее видно.

Арифметика поверх приведенного значения. Ноль, полученный из "1 000", в сумме не заметен. А вот деление на такой ноль заметно сразу, и именно по этому падению чаще всего и находят первопричину — через неделю после того, как она появилась.

Мы для себя сделали из этого одно правило: если значение пришло не из базы, оно разбирается явно, и у разбора есть ветка отказа. Длиннее на три строки, зато отказ виден.

Функция ЧислоИзСтроки(Текст, Отказ)

	// Разделитель дробной части берем у самого сеанса, а не предполагаем.
	Разделитель = Сред(Строка(1.1), 2, 1);

	Очищенный = СтрЗаменить(СокрЛП(Текст), Символ(160), "");
	Очищенный = СтрЗаменить(Очищенный, " ", "");

	// Если в строке и точка, и запятая - это не число, а чья-то выдумка.
	Если СтрНайти(Очищенный, ",") > 0 И СтрНайти(Очищенный, ".") > 0 Тогда
		Отказ = Истина;
		Возврат 0;
	КонецЕсли;

	Очищенный = СтрЗаменить(Очищенный, ".", Разделитель);
	Очищенный = СтрЗаменить(Очищенный, ",", Разделитель);

	Попытка
		Возврат Число(Очищенный);
	Исключение
		Отказ = Истина;
		Возврат 0;
	КонецПопытки;

КонецФункции

Замены тут не для красоты. Неразрывный пробел с кодом 160 приезжает из строк, которые сама платформа и напечатала. Обычный пробел — из выгрузок и из буфера. А разделитель берется у сеанса через Сред(Строка(1.1), 2, 1) — платформа сама печатает одну десятую так, как принято в этом сеансе, и второй символ и есть нужный знак.

В обоих сеансах числа совпали до знака:

 

Строка на входе Что вышло  
12,5 и 12.5 12,5 оба написания
1 000 и то же с неразрывным пробелом 1000  
-3,25 и -3.25 -3,25  
abc, пустая строка 0 отказ
12,5,7, 12.5,7 0 отказ
1,000,000 0 отказ, и это нарочно

 

Последнюю строку поясню. Число с запятыми-разделителями разрядов функция отвергает, хотя могла бы угадать тысячу. Угадывать она не должна: ровно это угадывание и дает 125 вместо 12,5. Лучше отказ, который видно.

 

А первая версия этой функции была сломана

Писать такое неприятно, но без этого статья будет нечестной.

В первой редакции листинг стоял такой: убрать пробелы, заменить точку на запятую, вызвать Число(). Логика очевидная — привести к русскому разделителю и разобрать. Я так и написал, и формулировка в статье была уверенная: «после такой замены строка читается одинаково в любом сеансе».

Прогон показал обратное. В сеансе с точечным разделителем эта функция берет правильное "12.5", превращает в "12,5" и получает 125. То есть она портит ровно то, что должна была починить, и портит молча — отказа нет, значение «принято». Строку "12,5,7" первая версия тоже приняла, вернув 1257.

Урок из этого ровно тот же, что и у всей статьи. Код, который приводит типы по предположению о разделителе, работает до первого сеанса с другими настройками. Разница только в том, что платформа делает это в одну строку и бесплатно, а я сделал то же самое в пять строк и с комментарием, объясняющим, почему это надежно.

 

Чего этот замер не доказывает

Два ограничения назову прямо, чтобы на статью не ссылались шире, чем она проверена.

Первое. Разница между сеансами измерена, а вот причина ее названа как объяснение, а не как измеренный факт. Я говорю, что дело в региональных настройках сеанса, и показываю, чем они отличаются, но не ставил опыт, где меняется только язык, а все остальное остается прежним. Пока такой пробы нет, честная формулировка звучит так: сеансы различались вот этим, а не причина в этом.

Второе. Все снято на двух типах сеанса. Если у вас третий набор региональных настроек, картина может оказаться иной, и утверждать про нее мне нечем.

 

Что с этим делать

Коротко, по убыванию пользы.

Не полагаться на неявное приведение там, где данные пришли извне. Файл, буфер, веб-сервис, ручной ввод — все это строки неизвестного качества. Явный разбор с обработкой отказа длиннее на три строки и отличается тем, что про неудачу вы узнаете.

Проверять не базу, а сеанс. Если поведение зависит от региональных настроек, смотреть надо на то, как сеанс печатает число, а не на код локализации базы. Самая короткая проба — Строка(12.5): увидели точку, значит разделитель дробной части точка, и запятая в строках будет понята как разряды.

Разбирать числа не по умолчанию, а по формату. Там, где формат источника известен заранее, он и должен быть задан явно, а не унаследован от того, кто запустил обработку.

Проверять «ограничение платформы», прежде чем под него подстраиваться. Это не про числа, но урок из того же прогона. Ошибка компиляции с правдоподобным текстом — плохой повод сокращать состав продукта.

Все замеры от 01.10.2026 приложены. Если у вас есть сеанс с третьим набором региональных настроек — скажите, что он делает с "12,5", интересно, сходится ли картина.

Вступайте в нашу телеграмм-группу Инфостарт

приведение типов Число из строки региональные настройки сеанса разделитель дробной части неразрывный пробел квалификаторы числа тонкий клиент веб-клиент локализация СтрокаВЧисло реквизит формы тихая ошибка формат числа язык сеанса загрузка из файла типизация реквизита ТаблицаЗначений внешняя обработка 1С 8.5 Бухгалтерия 3.0

Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».

  • 0% комиссии — оплата напрямую исполнителю;
  • Исполнители любого масштаба — от отдельных специалистов до команд под проект;
  • Прямой обмен контактами между заказчиком и исполнителем;
  • Безопасная сделка — при необходимости;
  • Рейтинги, кейсы и прозрачная система откликов.

См. также

Механизмы платформы 1С Разработчик Бесплатно (free)

Разберем 15 мифов о работе платформы «1С:Предприятие 8» – как распространенных, так и малоизвестных. Начнем с классики: «Код, написанный в одну строку, работает быстрее, чем многострочный». Так ли это на самом деле?

16.07.2025    45673    TitanLuchs    109    

156

Работа с интерфейсом WEB-интеграция Механизмы платформы 1С Бесплатно (free)

Веб-интерфейсы повышают качество внешнего вида приложений 1С, предсказуемы с точки зрения верстки, позволяют организовать удобные и высокопроизводительные рабочие места для пользователей. Расскажем об особенностях разработки веб-интерфейсов на React внутри 1С, двустороннем взаимодействии 1С и JavaScript, а также сборке веб-приложения в одностраничный файл.

19.06.2025    18686    zeegin    15    

64

Механизмы платформы 1С Работа с интерфейсом Разработчик Стажер 1С:Предприятие 8 Бесплатно (free)

Про ООП в 1С и о том, как сделать свой код более кратким и выразительным при помощи использования текучего интерфейса (fluent interface).

03.02.2025    26034    bayselonarrend    127    

68

Механизмы платформы 1С Разработчик 1С:Предприятие 8 Бесплатно (free)

В этой статье подробно рассматривается работа с JSON в XDTO в 1С:Предприятие. Вы узнаете, как сериализовать и десериализовать объекты XDTO в JSON, интегрировать 1С с веб-сервисами и API, а также корректно обрабатывать данные при обмене. Разбираются особенности работы с коллекциями, использование функций восстановления и частые ошибки при работе с JSON и XDTO.

30.01.2025    33313    user2122906    10    

69

Механизмы платформы 1С Файловый обмен (TXT, XML, DBF), FTP Разработчик 1С:Предприятие 8 Бесплатно (free)

Этот материал познакомит вас с механизмом XDTO (XML Data Transfer Objects) в 1С и научит эффективно использовать его возможности. Мы разберёмся, как работать с XML-схемами, создавать модели данных, манипулировать объектами XDTO, а также сериализовать и десериализовать их в XML. Вы узнаете, как использовать XDTO для интеграции с внешними системами, избегать типичных ошибок и оптимизировать код. К концу вы будете уверенно применять XDTO для решения сложных задач обмена данными и автоматизации процессов.

17.01.2025    55792    user2122906    13    

63

Механизмы платформы 1С WEB-интеграция Разработчик 1С:Предприятие 8 Бесплатно (free)

В платформе 8.3.27 появилась возможность использовать WebSocket-клиент. Давайте посмотрим, как это все устроено и чем оно нам полезно.

14.01.2025    46792    dsdred    110    

160

Механизмы платформы 1С Разработчик Стажер 1С:Предприятие 8 1C:Бухгалтерия Бесплатно (free)

Эта небольшая статья - некоторого рода шпаргалка по файловым потокам: как и зачем с ними работать, какие преимущества это дает.

23.06.2024    40461    bayselonarrend    24    

180
Для отправки сообщения требуется регистрация/авторизация