NumPy получил отрицательные квадраты: как исправить переполнение целых

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

Обновлено 14 мин чтенияУчёба и сессия
Программный код на экране ноутбука

Отрицательный квадрат в лабораторной таблице

Студент обрабатывает показания учебного прибора в Python. Исходные целые числа положительные и выглядят правильно, но после возведения в квадрат часть результатов становится отрицательной. Код завершается, график строится, исключения нет. Прежде чем искать ошибку в физической модели, проверьте числовой тип массива. В NumPy целые значения могут храниться в формате фиксированной разрядности. Вход помещается в такой формат, а промежуточный результат уже выходит за его пределы. Тогда дальнейший анализ получает неверные числа при внешне исправном исходном файле.

Разберём самостоятельный учебный пример с массивом int16 и значениями 180, 190, 200. Это условные отсчёты, а не реальные данные конкретного прибора. Нужно вычислить их квадраты и сумму квадратов. Правильные результаты легко получить вручную: 32400, 36100, 40000; сумма 108500. Именно эти небольшие числа помогут найти место повреждения. Они не подменяют проверку вашей методики, зато позволяют отделить числовую ошибку программы от смысла экспериментального измерения. Приведённые результаты проверены в NumPy 2.3.5 на обычном одномерном массиве.

Сначала проверьте сами входные значения

Сохраните исходный набор и выведите несколько отсчётов до вычисления квадратов. Сопоставьте их с полученным файлом или журналом измерений. Если 190 уже превратилось в другое значение на входе, проблема началась раньше, и изменение типа перед последней формулой не устранит её. Для нашего примера все три значения прочитаны правильно. Это важное условие расследования: мы исследуем вычисление над верными входами. Сбой импорта, неправильная единица и случайное смешение колонок требуют своих отдельных проверок.

Запишите, что выражает каждая колонка. Целое показание прибора может быть счётчиком, номером отсчёта или кодом состояния. Возводить идентификатор в квадрат бессмысленно даже в самом широком типе. В условной работе это именно числовые отсчёты, для которых операция предусмотрена заданием. Рядом укажите ожидаемый диапазон входа. Если прибор ограничивает значения двумястами единицами, квадрат может достигать сорока тысяч. Такое простое рассуждение сразу показывает, что удобный тип хранения входа необязательно подходит для последующего расчёта над ним.

Посмотрите dtype перед первым подозрительным действием

У массива NumPy свойство dtype сообщает тип его элементов. В нашем случае это int16: знаковое целое с шестнадцатью битами. Допустимые границы удобно посмотреть через numpy.iinfo для конкретного типа. Для int16 они равны −32768 и 32767. Поэтому все три входа помещаются в массив, квадрат 180 ещё помещается, а квадраты 190 и 200 уже превышают верхнюю границу. Проверка одного маленького входа легко создаёт ложное ощущение, что вся формула работает правильно.

Не судите о типе по виду печати. И массив целых, и обычный список Python могут показывать похожие числа в квадратных скобках. Отдельный элемент NumPy тоже способен сохранять собственный числовой тип. Поэтому запишите фактический тип именно объекта, участвующего в операции. Длинное имя переменной вроде «исходные измерения» ничего о разрядности не сообщает. Если обработка состоит из нескольких шагов, полезно посмотреть dtype до умножения и сразу после него. Это локальная проверка места, где ожидаемое число впервые превращается в неверное.

Повторите ошибку на трёх числах

Создадим в учебной среде одномерный массив a из 180, 190 и 200 с явным dtype int16. При поэлементном умножении a на a в проверенном примере получаются 32400, −29436 и −25536, а тип результата остаётся int16. Первый квадрат правильный, два следующих повреждены. Отрицательность здесь не означает отрицательную энергию или изменение знака входных наблюдений. Это сигнал о несоответствии вычислительного представления нужному диапазону. Нельзя обсуждать физический смысл таких квадратов, пока не установлена причина числового поведения.

Для сравнения вычислите те же три квадрата обычной целочисленной арифметикой Python или вручную. Получатся 32400, 36100 и 40000. Совпадение на первом числе и расхождение на двух больших локализуют проблему гораздо точнее, чем общий график сотни измерений. Запишите рядом вход, ожидаемый квадрат и полученный квадрат. Это маленькая таблица расследования, которую можно показать преподавателю. В ней уже видно, что формула умножения выбрана правильно, но тип промежуточного массива не позволяет сохранить нужные результаты.

Преобразуйте вход до умножения

Правильный порядок в этом примере состоит в создании более широкого рабочего массива из исходных отсчётов, а затем вычислении квадратов. Для данных 180, 190, 200 подходит int64: сначала a.astype(np.int64), затем умножение полученной копии на неё же. Результат содержит 32400, 36100 и 40000. Имена лучше дать понятные: исходные отсчёты, отсчёты для расчёта, квадраты. Тогда читатель видит, какое представление относится к хранению, а какое выбрано для вычислительной операции.

Это решение относится к проверенному диапазону примера. Оно не означает, что int64 автоматически подходит любой степени любого числа. Оцените наибольший ожидаемый промежуточный результат вашей формулы и сравните с границами выбранного типа. Если дальше следует ещё умножение на большой коэффициент, проверять нужно и его. Студент должен суметь объяснить выбор ширины, а не просто назвать «самый большой тип». Для небольшого учебного набора обычно достаточно грубой верхней оценки и контрольного расчёта, без сложной системы автоматического подбора числовых представлений.

Почему преобразование после операции уже опоздало

Теперь рассмотрим другую последовательность: сначала перемножили массив int16 сам на себя, затем перевели получившиеся квадраты в int64. В проверенном примере числа остаются 32400, −29436 и −25536. Расширилось место хранения уже неправильных значений. Информация о правильных квадратах не появилась, потому что заново никто не вычислял их из исходных отсчётов. Такую ошибку легко пропустить: dtype теперь выглядит подходящим, и при поверхностной проверке кажется, что проблема разрядности устранена.

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

Широкая сумма не исправляет испорченные квадраты

Следующий соблазн — изменить только тип итогового суммирования. У numpy.sum есть параметр dtype для аккумулятора; кроме того, для некоторых узких целых типов сумма по умолчанию использует более широкий целый тип. Но если входом суммы уже стали неверные квадраты, она складывает именно их. В нашем примере их сумма равна −22572. Это корректное сложение неправильных промежуточных значений. Замена последнего действия не возвращает пропущенные 36100 и 40000, потому что они должны были появиться на предыдущем шаге.

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

Не ожидайте обязательного сообщения об ошибке

Для операций над целочисленными массивами отсутствие предупреждения не гарантирует отсутствие переполнения. Документация NumPy отдельно различает поведение массивов и скалярных операций. Поэтому опыт с одной скалярной переменной нельзя автоматически переносить на весь массив. Для диагностики сохраняйте форму объекта, dtype и действие, при котором получили расхождение. Наш проверочный пример относится к поэлементному умножению двух одномерных массивов одинакового типа. Он не утверждает, что любая смесь Python-чисел, скаляров NumPy и массивов ведёт себя совершенно одинаково.

Если после обновления библиотеки поведение короткого фрагмента изменилось, запишите версию и повторите минимальный пример в текущей среде курса. Не возвращайте старую среду только ради исчезновения предупреждения. Содержательный критерий остаётся тем же: квадраты известных входов должны совпадать с ожидаемыми числами, а выбранный диапазон должен подходить формуле. Автоматическое повышение типа зависит от операции и участвующих типов. Явное преобразование проверенного рабочего входа помогает сделать намерение понятным, но его всё равно следует подтвердить результатом, а не воспринимать как заклинание от всех числовых ошибок.

Проверьте границу, а не только красивые круглые значения

Для int16 удобно сравнить квадраты 181 и 182. Первый равен 32761 и ещё помещается, второй равен 33124 и уже больше максимума. Эти числа показывают границу гораздо лучше проб с десятью и двадцатью. В рабочем массиве int64 оба квадрата должны получиться правильно. Если набор допускает отрицательные отсчёты, проверьте симметричные значения: квадрат −182 должен совпадать с квадратом 182. Такая проверка относится к математическому свойству операции и помогает обнаружить повреждение представления без просмотра каждого наблюдения.

Не удаляйте значения около границы как «выбросы» только потому, что они ломают прежний расчёт. Вход может быть совершенно допустимым по инструкции прибора. Ошибка находится в выбранном типе обработки. Исключение больших наблюдений способно скрыть переполнение ценой изменения набора и выводов. Сначала исправьте вычисление, затем оценивайте данные по правилам вашей методики. Если после исправления остаётся физически невозможное входное значение, его происхождение проверяется отдельно. Техническая починка не подтверждает автоматически качество каждого измерения, как и подозрительный итог не доказывает плохое измерение.

Выбор вещественного типа требует своей причины

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

Не меняйте единицу только ради уменьшения числа без изменения подписи и формулы. Если двести отсчётов соответствуют определённому напряжению, преобразование должно следовать калибровке. Иначе формально помещающийся результат будет относиться к другой величине. Полезно держать расчёт в двух явных шагах: допустимое числовое представление и содержательный пересчёт единиц. Они связаны, но отвечают на разные вопросы. Сначала убедитесь, что числа могут корректно участвовать в операции; затем проверьте, что сама операция выражает предусмотренную лабораторной методику, включая все коэффициенты и размерности.

Пересчитайте всё, что зависело от повреждённого массива

После исправления квадратов заново получите сумму, среднее, производные показатели и график, если они использовали прежний результат. Изменение типа одной переменной не меняет автоматически отдельно сохранённую таблицу и вручную набранный вывод. Проследите цепочку от квадратов к итоговому документу. Если notebook сохраняет старые результаты, выполните исправленный расчёт с начала в предусмотренном порядке. Здесь важно именно получение новых зависимых значений. Перестановка картинки или подписи поверх старого графика числовую ошибку не устраняет и затрудняет дальнейшее объяснение.

Сравните два контрольных входа и итоговую сумму уже в экспортированном результате. Для нашего набора это 190, его квадрат 36100 и сумма 108500. Сохраните исходную таблицу отсчётов отдельно от производной. Если после исправления меняется содержательный вывод лабораторной, перепишите его по новым значениям. Не пытайтесь сохранить прежний тезис выбором другого округления. Обнаруженная вычислительная ошибка — основание повторно проверить утверждение, а не повод стыдливо скрыть отличие. Короткое объяснение причины обычно полезнее сложной истории, почему старое число «почти подходило» к ожидаемой теории.

Не сузьте тип при сохранении промежуточного результата

После правильного умножения посмотрите, куда попадают квадраты. Если программа заранее создала выходной массив int16, перенос правильных значений в это узкое хранилище снова требует проверки диапазона. Успешное промежуточное вычисление не даёт гарантии для всех последующих присваиваний и преобразований. Проще сохранить квадраты отдельным массивом подходящего типа и проверить его непосредственно перед дальнейшим использованием. Если специально сужаете представление ради формата обмена, сначала установите, допускает ли этот формат полученные величины. Требование маленького файла не делает недопустимый результат правильным.

При выгрузке в обычный текст сравните несколько фактически записанных чисел. Тип массива внутри программы и параметры отдельного выходного формата относятся к разным этапам. В нашем примере строка с квадратом 200 должна содержать 40000, если по заданию сохраняется целый квадрат. Если внешний формат допускает только более узкий диапазон, нужен другой согласованный формат или другое содержательное представление. Нельзя молча обрезать значение и продолжать называть файл результатом прежнего расчёта. Ограничение передачи следует выявить до того, как напарник начнёт анализировать уже повреждённую выгрузку.

Проверьте повторное использование рабочего массива

Если код выполняется несколько раз, убедитесь, что новое вычисление снова начинается с отсчётов. Переменная с названием «данные» могла после первого запуска уже содержать квадраты. Повторное умножение тогда даст четвёртые степени, и ожидаемый диапазон изменится. Это другая причина неверного результата, которую удобно отделить тем же маленьким примером. Назовите исходный и производный массивы по роли, проверьте первые значения перед операцией и не используйте сохранённый повреждённый выход как замену исходного входа. Правильный dtype помогает только тому вычислению, которое вы действительно намеревались выполнить.

Сохраните проверку так, чтобы её можно было повторить

Рядом с решением достаточно нескольких сведений: версия NumPy, тип входа, тип рабочего расчёта, операция и маленький пример с ожидаемыми результатами. Не превращайте пояснение в полный справочник типов. Получатель должен быстро увидеть, почему расширение выполнено до умножения и по какой границе выбран формат. Если преподаватель проверяет программную работу, сохраняйте исполняемый расчёт в предусмотренном формате. Изображение правильной таблицы показывает результат, но не позволяет установить порядок преобразования и убедиться, что он повторяется на других допустимых входах.

В Prepodoff доступны личные материалы по папкам и предметам. Можно добавить туда разрешённое пояснение вычислений или финальный учебный документ, если так удобнее готовиться к защите. Исполнение и проверка массивов остаются в вашей среде Python. Завершённая работа позволяет ответить на три конкретных вопроса: где впервые повреждалось значение, почему выбранный тип теперь вмещает нужный диапазон и как проверен итог. Тогда отрицательный квадрат перестаёт быть загадочным свойством прибора и становится понятной исправленной ошибкой вычислительного шага.

Была полезна статья?

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

Пригодится для подготовки