Разберите последствия: чем отличается переполнение обычного целочисленного сложения от переполнения с оператором wrapping в Swift?
Обычная целочисленная арифметика Swift при переполнении аварийно завершает выполнение с ошибкой времени исполнения. Операторы wrapping с символом & не завершают программу: результат вычисляется по модулю диапазона конкретного целочисленного типа.
Такой подход разделяет два намерения разработчика: обычная арифметика должна обнаруживать ошибку, а циклическая арифметика должна быть явно разрешена. Это предотвращает незаметное искажение данных, характерное для безусловного переполнения фиксированной разрядности.
Фиксированный целочисленный тип имеет ограниченный диапазон. Например, UInt8 хранит значения от 0 до 255, поэтому увеличение 255 на единицу невозможно представить тем же типом.
Если переполнение возникло из-за ошибки в расчёте, wrapping-оператор скроет проблему и передаст дальше корректное с точки зрения битового представления, но потенциально неверное значение. Если же алгоритм специально использует циклический счётчик, обычный оператор приведёт к аварийному завершению.
Операторы +, - и * проверяют, помещается ли математический результат в диапазон типа. При выходе за диапазон они вызывают trap, поэтому выполнение прекращается, если ошибка не перехвачена на уровне процесса.
Операторы &+, &- и &* выполняют вычисление с wrapping-семантикой. Для беззнакового типа результат соответствует остатку по модулю количества представимых значений; для знакового типа используется двоичное представление фиксированной разрядности.
В примере обычное прибавление единицы к UInt8.max завершило бы выполнение с переполнением, а &+ вернул 0. Типы операндов всё равно должны быть совместимы, а wrapping не устраняет переполнения при преобразовании между типами: преобразование также требует явно выбранной семантики.
Для вычислений, где потеря старших разрядов недопустима, лучше использовать обычные операторы и отдельно проверять диапазон до преобразования или операции. Wrapping уместен для циклических индексов, счётчиков протоколов, хеширования и низкоуровневых алгоритмов, где переход через границу является частью спецификации.
В сетевом протоколе есть восьмибитный счётчик пакетов, который по спецификации циклически переходит от 255 к 0. Использование обычного сложения сделает штатный переход аварией, поэтому wrapping-оператор точно выражает требование протокола.
Альтернативой было бы вручную проверять достижение максимального значения и присваивать 0. Такой вариант явнее для бизнес-логики, но длиннее и легче ошибиться в граничных условиях. Для общего финансового или пользовательского счётчика выбран бы обычный оператор с проверкой: авария или явная ошибка предпочтительнее тихой порчи данных.
Вопрос: распространяется ли wrapping на значение с плавающей точкой?
Нет. Wrapping-операторы относятся к целочисленной арифметике фиксированной разрядности. Поведение Float и Double определяется правилами IEEE 754: там возможны значения вроде бесконечности и NaN, а не циклический переход по диапазону целого типа.
Вопрос: можно ли считать wrapping безопасным способом исправления переполнения?
Нет. Он безопасен только относительно аварийного завершения, но не относительно смысла данных. Если переполнение не предусмотрено алгоритмом, wrapping превращает обнаруживаемую ошибку в скрытое изменение результата.
Вопрос: почему тип операндов важен даже при использовании wrapping-оператора?
Потому что wrapping выполняется в диапазоне конкретного типа, а не в абстрактной математике. Переполнение UInt8 происходит уже после 255, тогда как UInt64 имеет совершенно другую границу; кроме того, Swift не смешивает произвольные целочисленные типы без явного преобразования. Это защищает от неявной смены разрядности и неоднозначного результата.