1с увеличить переменную на 1

Обновлено: 07.07.2024

Разрядность результатов выражений и агрегатных функций в языке запросов

При использовании в запросах операций и функций над данными, в определение которых входят спецификации длины ( Строка , Число ) и точности ( Число ), бывает необходимо учитывать правила вычисления длины и точности результатов. Этот раздел содержит описание соответствующих правил.

Операции над строками

Описание данных типа Строка может включать следующие дополнительные спецификации: длина (максимальное количество символов), допустимая длина (фиксированная или переменная), неограниченная длина ( Истина или Ложь ). В таблице приведены правила вычисления дополнительных спецификаций результатов функций в зависимости от дополнительных спецификаций их строковых операндов.

ПОДСТРОКА(Строка, m, k) Неограниченная длина Длина k, переменная Длина k, переменнаяМИНИМУМ(Строка) Неограниченная длина Длина n, фиксированная Длина n, переменнаяМАКСИМУМ(Строка) Неограниченная длина Длина n, фиксированная Длина n, переменная

В следующей таблице приведены правила определения дополнительных спецификаций результата операции "+" (конкатенация строк), имеющей 2 строковых операнда.

Неограниченная длина Неограниченная длина Неограниченная длина Неограниченная длинаДлина n Фиксированная Неограниченная длина Длина n + m фиксированная Длина n + m переменнаяПеременная Неограниченная длина Длина n + m переменная Длина n + m переменная

Операции над числами

Выполняя различные преобразования числовых данных, необходимо учитывать, что максимальное количество цифр, которое может содержать число (его целая и дробная часть вместе), равно 38 цифр. 1С:Предприятие использует десятичные числа с фиксированной точкой. Это значит, например, что число 34.28 содержит 4 цифры, число 0.00000001 содержит 8 цифр (незначащий 0 перед точкой за цифру не считается), а число 3200000000 содержит 10 цифр. Разрядность результатов различных операций вычисляется так, чтобы по возможности исключить арифметические переполнения, и в то же время максимально сохранить точность значений. При разработке конфигураций выбор разрядности полей объектов метаданных должен определяться возможной разрядностью хранимых в них данных и не должен учитывать возможное увеличение разрядности после выполнения арифметических операций и функций.

"+" (сложение), "-" (вычитание)

Операции сложения и вычитания в языке запросов всегда выполняются точно. Для этого длина дробной части результата определяется как максимальная из длин дробных частей операндов. Длина целой части результата как максимальная из длин целых частей операндов, увеличенная на 1. Если при этом общее количество цифр целой и дробной части превысит 38, то оно будет приведено к 38 за счет уменьшения числа цифр дробной части, но не менее, чем до 8.

"*" (умножение)

Операции умножения выполняется точно в том случае, если хотя бы один ее операнд является целым числом. Длина дробной части результата определяется как наибольшая из длин дробных частей операндов, но не менее 8 цифр. Длина целой части результата вычисляется как сумма длин целых частей операндов. Если при этом общее количество цифр целой и дробной части превысит 38, то оно будет приведено к 38.

При умножении двух чисел разрядность результата вычисляется следующим образом: количество разрядов целой части содержит сумму количеств разрядов целых частей операндов; количество разрядов дробной части содержит сумму количеств разрядов дробных частей операндов. Если суммарное количество разрядов (целой и дробной части) результата превышает максимально допустимое (38 - для всех СУБД кроме DB2, для DB2 - 31), то происходит балансировка точности. В этом случае операнды приводятся к разрядности, позволяющей сохранить необходимую точность операции. Например, при умножении полей (не констант) ЧИСЛО(17,4) и ЧИСЛО(27,4) платформа приведет операнды к типу ЧИСЛО(15,4) и ЧИСЛО(22,4), что в результате даст тип ЧИСЛО(37,8), не превышающий максимальное количество разрядов в СУБД. Однако, если какой-либо из операндов не поместится в соответствующую разрядность, то будет ошибка СУБД во время исполнения запроса

"/" (деление)

Операция деления выполняется точно только если оба операнда являются целыми числами, и первый операнд делится на второй без остатка. Длина дробной части результата определяется как максимальная из длин дробных частей операндов, но не менее 8 цифр. Длина целой части результата равна сумме длин целой части делимого и дробной части делителя. Если при этом общее количество цифр целой и дробной части превысит 38, то оно будет приведено к 38 за счет уменьшения числа цифр дробной части, но не менее, чем до 8.

СУММА

Агрегатная функция СУММА вычисляется по возможности точно. Количество цифр дробной части результата равно количеству цифр дробной части операнда. Количество цифр целой части увеличивается на 7 цифр. Если при этом общее количество цифр целой и дробной части превысит 38, то оно будет приведено к 38 за счет уменьшения числа цифр дробной части, но не менее, чем до 8.

МИНИМУМ, МАКСИМУМ, СРЕДНЕЕ

Результат функций МИНИМУМ, МАКСИМУМ, СРЕДНЕЕ имеет такое же количество цифр в целой и дробной частях, как и их операнд.

КОЛИЧЕСТВО, ГОД, КВАРТАЛ, МЕСЯЦ, ДЕНЬГОДА, ДЕНЬ, НЕДЕЛЯ, ДЕНЬНЕДЕЛИ, ЧАС, МИНУТА, СЕКУНДА, РАЗНОСТЬДАТ

Перечисленные функции имеют числовой результат с 10 цифрами в целой части. Дробная часть отсутствует.

Проблемные ситуации

Если при работе запроса возникает ошибка СУБД, следует изменить запрос, приводящий к ошибке: с помощью операции ВЫРАЗИТЬ можно привести разрядность операндов к такому виду, чтобы в результате расчета разрядности умножения платформа 1С:Предприятие не проводила автоматическую балансировку точности.

Например: привести разрядность операндов к типам ЧИСЛО(17,4) и ЧИСЛО(20,4) - в этом случае результатом умножения будет разрядность ЧИСЛО(37,8), что не превышает максимально допустимую разрядность для используемой СУБД.


Возьмите переменную и увеличьте её на 1. Звучит просто, верно? Ну… С точки зрения PHP-разработчика, наверное, да. Но так ли это на самом деле? Здесь могут возникнуть некоторые трудности. Существует несколько способов инкрементировать значения, они могут выглядеть равноценными, но под капотом PHP работают по-разному, что может привести к, так сказать, интересным результатам.

Рассмотрим три примера добавления единицы к переменной:


Код разный, но в каждом случае значение переменной увеличивается. А какой будет результат?


Интуитивно все три способа выглядят равнозначно. То есть для инкрементирования можно использовать как $a++ , так и $a += 1 . Но давайте рассмотрим другой пример:


Наверняка многие из вас не ожидали такого результата! Может быть, кто-то уже знал, что добавление к строковой переменной приводит к изменению набора символов, но два int(1) ? Откуда они взялись? С точки зрения PHP-разработчика это выглядит очень несогласованно, и выходит, что наши три способа инкрементирования неравнозначны. Давайте посмотрим, что происходит в недрах PHP при выполнении кода.

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

Приведённый выше код преобразуется в такой байт-код:


Таким образом, $a++ превращается в два опкода ( POST_INC и FREE ), $a += 1 — в один ( ASSIGN_ADD ) и $a = $a + 1 тоже в два. Обратите внимание, что во всех трёх случаях получились разные опкоды, что уже подразумевает разное исполнение PHP.

Рассмотрим первый способ инкрементирования — унарный оператор ( $a++ ). Этот PHP-код преобразуется в опкод POST_INC . К слову, PRE_INC получается из ++$a , и вам нужно знать разницу между ними. Второй опкод — FREE — очищает результат после POST_INC , потому что мы не используем его возвращаемое значение: POST_INC на месте изменяет актуальный операнд. В данном случае можно проигнорировать этот опкод.

Причина различия в исполнении этих опкодов кроется в файле zend_vm_def.h , который вы можете найти в исходном С-коде PHP. Это большой заголовочный файл, наполненный макросами, поэтому его не так легко читать, даже если вы знаете С. При вызове опкода POST_INC выполняется содержимое строки 971.

Если коротко, то происходит вот что:

  • Проверяется, принадлежит ли переменная ( $a в PHP-коде, которая в байт-коде превращается в !0 ) к типу long . По сути, система проверяет, содержит ли переменная число. Хотя PHP — язык с динамической типизацией, каждая переменная всё же принадлежит к какому-то «типу». Типы могут меняться, как мы увидим далее. Если наша переменная относится к long , то вызывается С-функция fast_increment_function() и происходит возврат к следующему опкоду.
  • Если переменная нечисловая, то выполняются базовые проверки на возможность инкрементирования. Например, этого нельзя сделать со строковыми смещениями (string offset) $a = "foobar" ; $a[2]++ , мы получим ошибку.
  • Далее проверяется, является ли переменная несуществующим свойством объекта, имеющего волшебные PHP-методы __get и __set . Если это так, то с помощью __get извлекается правильное значение, вызывается fast_increment_function() и значение сохраняется с помощью вызова метода __set . Эти методы вызываются из С, а не из PHP.
  • Наконец, если переменная не является свойством, то просто вызывается increment_function() .

Функция fast_increment_function() относится к zend-операторам, и её задачей является максимально быстрое инкрементирование конкретной переменной.

Если переменная относится к типу long , то для её инкрементирования используется очень быстрый ассемблерный код. Если значение достигло максимального числа типа INT ( LONG_MAX ), то переменная автоматически преобразуется в двойную ( double ). Это самый быстрый способ увеличения числа, поскольку эта часть кода написана на ассемблере. Считается, что компилятор не может оптимизировать С-код лучше, чем ассемблер. Но способ работает только в том случае, если переменная относится к типу long . Иначе будет выполнен редирект на функцию increment_function() . Поскольку инкрементирование (и декрементирование) чаще всего выполняется в очень маленьких внутренних циклах (например, for ), то необходимо делать это как можно быстрее ради сохранения высокой производительности PHP.

Если fast_increment_function() — быстрый способ инкрементировать число, то increment_function — медленный ( slow ) способ. Сценарий процесса тоже зависит от типа переменной.

  • Если переменная относится к типу long , то число просто увеличивается (и преобразуется в double при достижении максимального значения, которое уже нельзя хранить в long ). Чаще всего это уже будет сделано с помощью fast_increment_function , но может случиться так, что этой функции всё равно будет передано long , так что и здесь необходима проверка.
  • Если переменная относится к типу double , то она просто увеличивается.
  • Если переменная относится к типу NULL , то всегда возвращается long 1 .
  • Если переменная относится к типу string , то применяется описанная выше магия.
  • Если переменная — объект и имеет функциональность оператора internal , то вызывается оператор add для добавления long 1 . Обратите внимание, что это работает только для классов internal , которые вручную определяют эти функции оператора, вы не можете определять операторы объекта в PHP-коде пространства пользователя. Это реализует единственный класс в исходном PHP-коде — GMP . Так что вы можете сделать $a = new gmp(1) + new gmp(3); // gmp(4) . Такая возможность появилась начиная с PHP 5.6, но перегрузка оператора невозможна в PHP напрямую.
  • Если переменная относится к какому-то другому типу, то её нельзя инкрементировать и возвращается код сбоя.

А теперь самое забавное. Работа со строками всегда полна нюансов, но в данном случае происходит вот что.

Во-первых, проверяется, содержит ли строка число. Например, строковая 123 содержит число 123. Такое «строчное число» будет преобразовано в нормальное число типа long (int(123) ). При конвертировании используется несколько уловок:

  • Удаляются пробелы.
  • Поддерживаются шестнадцатеричные числа ( 0x123 ).
  • Не поддерживаются восьмеричные и двоичные числа ( 0123 и b11 ).
  • Поддерживается научное представление ( 1E5 ).
  • Поддерживаются double .
  • Не поддерживаются и не считаются числами части, стоящие в начале или в конце строковой ( 135abc или ab123 ).

Если строковая не может быть преобразована в long или double , то вызывается функция increment_string() .

PHP использует систему инкрементирования наподобие Perl. Если строковая пустая, то просто возвращается string("1") . В противном случае для инкрементирования строковой применяется система переноса (carry-system).

Начинаем с конца переменной. Если символ от a до z , то он инкрементируется ( a становится b , и т. д.). Если символ z , то меняется на a и «переносится» на одну позицию перед текущей.

То есть: a становится b , ab становится ac (перенос не нужен), az становится ba ( z становится a , a становится b , потому что мы переносим один символ).

То же самое относится и к прописным символам от A до Z , а также к цифрам от 0 до 9 . При инкрементировании 9 превращается в 0 и переносится на предыдущую позицию.

Если мы достигли начала строковой переменной и нужно сделать перенос, то просто добавляется ещё один символ ПЕРЕД всей строковой. Тип тот же, что и у переносимого символа:


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

Но будьте осторожны, если станете инкрементировать «число в строке» несколько раз.

При инкрементировании string("2D9") получится string("2E0") ( string("2D9" ) не является числом, поэтому будет выполняться инкрементирование обычной строки). Но при инкрементировании string("2E0") вы получите уже double(3) , потому что 2E0 — научное представление 2 и она будет преобразована в double , который затем может быть инкрементирован до 3. Так что будьте внимательны с циклами инкрементирования!

Эта система инкрементирования строк также объясняет, почему мы можем инкрементировать “Z” до “AA”, но не можем декрементировать “AA” обратно до “Z”. Декрементируется только последний символ “A”, но что делать с первым? Его тоже надо декрементировать до “Z” с помощью (отрицательного) переноса? А что насчёт “0A”? Оно должно стать Z ? И если да, то при новом инкрементировании мы получим уже AA . Иными словами, мы не можем просто убрать символы во время декрементирования, как мы добавляем их при инкрементировании.

Рассмотрим теперь второй пример из начала статьи — суммирующий оператор присваивания ( $a += 1 ). Выглядит аналогично унарному оператору инкремента, но ведёт себя иначе с точки зрения генерируемых опкодов и фактического выполнения. Выражение полностью обрабатывается с помощью zend_binary_assign_op_helper, который после ряда проверок вызывает add_function с двумя операндами: $a и нашим значением int(1) .

Метод add_function работает по-разному в зависимости от типов переменных. По большей части он состоит из проверки типов операндов:

  • Если они оба относятся к long , то их значения просто увеличиваются (при переполнении преобразуются в double ).
  • Если один long , а второй double , то оба преобразуются в double и инкрементируются.
  • Если они оба относятся к double , то просто суммируются.
  • Если они оба являются массивами, то будут объединены на основе ключей: $a = [ 'a', 'b' ] + [ 'c', 'd' ]; . Получится [ 'a', 'b'] , как если бы объединили второй массив, но у них оказались одинаковые ключи. Обратите внимание, что объединение происходит не по значениям, а по ключам.
  • Если операнды являются объектами, то проверяется, имеет ли первый из них внутреннюю функциональность оператора (как в случае с методом increment_function() ). У вас не получится сделать так в PHP самостоятельно, это поддерживается только внутренними классами вроде GMP.

Преобразование скаляра в число зависит от типа скаляра. Обычно всё сводится к одному из следующих алгоритмов:

  • Если скаляр — строка, то с помощью is_numeric_string проверяется, содержит ли она число. Если нет, то возвращается int(0) .
  • Если скаляр — null или булево false , то возвращается int(0) .
  • Если скаляр — булево true , то возвращается int(1) .
  • Если скаляр — ресурс (resource), то возвращается цифровое значение номера ресурса (resource number).
  • Если скаляр — объект, то делается попытка преобразовать его в long (как и в случае с внутренними операторами, здесь может быть функциональность внутреннего преобразования (internal cast functionality), но она не всегда реализована и доступна только для основных классов, а не для PHP-классов в пространстве пользователя).

Это самый простой из всех трёх вариантов. При его выполнении вызывается функция fast_add_function() . Как и fast_increment_function() , она напрямую использует ассемблерный код для увеличения чисел, если оба операнда относятся к long или double . Если это не так, то осуществляется редирект на функцию add_function() , используемую выражением присваивания.

Поскольку и оператор сложения, и суммирующий оператор присваивания используют одну и те же базовую функциональность, то $a = $a + 1 и $a += 1 работают одинаково. Единственное различие заключается в том, что оператор сложения МОЖЕТ выполняться быстро, если оба операнда относятся к long или double . Так что если вы хотите сделать микрооптимизацию, то $a = $a + 1 будет работать быстрее, чем $a += 1 . Не только благодаря fast_add_function() , но и потому, что нам не нужно обрабатывать дополнительный байт-код для сохранения результатов обратно в $a .

Инкрементирование значения отличается от простого сложения: add_function преобразует типы в совместимые пары, а increment_function этого не делает. Теперь мы можем объяснить полученные результаты:


Поскольку increment_function не преобразует булево значение (это не число и не строка, которую можно преобразовать в число), то происходит тихий сбой и значение не инкрементируется. Поэтому осталось bool(false) . В случае с add_function делается попытка найти соответствие пары boolean и long , которое не существует. В результате оба значения преобразуются в long : bool(false) становится int(0) , а int(1) остаётся int(1) . Теперь у нас есть пара long & long , поэтому add_function просто суммирует их и получается int(1) . (Вопрос: во что превратится булево true + int(1) ?)

Также мы можем объяснить ещё одну странность:


Поскольку строку не получается преобразовать в число, то выполняется обычное инкрементирование строки. Выражение добавления преобразует строки в long после проверки на наличие чисел. Поскольку их нет, то выполняется конвертирование строки в int(0) и к ней добавляется int(1) .

Читайте также: