Что такое целочисленное деление
Что такое целочисленное деление
Оператор div и оператор mod
В этой статье речь пойдет о целочисленном делении и делении с остатком.
То есть например 20 / 5 = 4, 55 / 6 = 9, 100 / 3 = 33 и т.д.
Согласитесь, что в некоторых случаях это очень удобно и практично. Теперь поговорим о реализации этого метода в Паскале. Тут все достаточно просто, открывать Америку не придется. В паскале за целочисленное деление отвечает оператор div. Теперь как это записывается в Pascal’e
Таким образом, вот такая запись (55 / 6) нацело = 9 в результате использования оператора div будет выглядеть так
z будет равно 9. Запомните! При использовании оператора div дробная часть будет отброшена!
А сейчас поговорим о делении с остатком. Оно не особо отличается и главным здесь является то, что в результате отбрасывается как раз целая часть. То есть (40 / 6) с остатком = 4, (10 / 3) с остатком =1, (22 /5) с остатком = 2 и т.д. В паскале для этого есть оператор mod. Записывается он точно так же.
Например (40 / 6) с остатком = 4 с оператором mod будет такой
Кстати оператор mod часто используют, для определения кратности чисел (кратность — это делимость на какое-нибудь число нацело. То есть например говорят, что числа 3, 6, 9, 12, 21 кратны трем. Или числа 5,10,15,20 кратны 5). В статье нахождение четных элементов массива я упоминал о числах кратных двум (четных). Итак как эту кратность определить в паскале. Обратите внимание, что если число кратное, то у него есть остаток (точнее оно имеет в остатке ноль). Этим и стоит воспользоваться.
Сейчас я привел пример условия, которое проверяет кратность, где v — это число, проверяемое на кратность по числу m. Например чтобы проверить,
является ли 40 кратным 4, используем оператор mod с условием и получим
Деление чисел с остатком
Деление с остатком целых положительных чисел
Деление — это разбиение целого на равные части.
Остаток от деления — это число, которое образуется при делении с остатком. То есть то, что «влезло» и осталось, как хвостик.
Чтобы научиться делить числа с остатком, нужно усвоить некоторые правила. Начнем!
Все целые положительные числа являются натуральными. Поэтому деление целых чисел выполняется по всем правилам деления с остатком натуральных чисел.
Попрактикуемся в решении.
Пример
Разделить 14671 на 54.
Выполним деление столбиком:
Неполное частное равно 271, остаток — 37.
Ответ: 14671 : 54 = 271(остаток 37).
Деление с остатком положительного числа на целое отрицательное
Чтобы легко выполнить деление с остатком положительного числа на целое отрицательное, обратимся к правилу:
В результате деления целого положительного a на целое отрицательное b получаем число, которое противоположно результату от деления модулей чисел a на b. Тогда остаток равен остатку при делении |a| на |b|.
Неполное частное — это результат деления с остатком. Обычно в ответе записывают целое число и рядом остаток в скобках.
Это правило можно описать проще: делим два числа со знаком «плюс», а после подставляем «минус».
Все это значит, что «хвостик», который у нас остается, когда делим положительное число на отрицательное — всегда положительное число.
Алгоритм деления положительного числа на целое отрицательное (с остатком):
Пример
Разделить 17 на −5 с остатком.
Применим алгоритм деления с остатком целого положительного числа на целое отрицательное.
Разделим 17 на − 5 по модулю. Отсюда получим, что неполное частное равно 3, а остаток равен 2. Получим, что искомое число от деления 17 на − 5 = − 3 с остатком 2.
Ответ: 17 : (− 5) = −3 (остаток 2).
Деление с остатком целого отрицательного числа на целое положительное
Чтобы быстро разделить с остатком целое отрицательное число на целое положительное, тоже придумали правило:
Чтобы получить неполное частное с при делении целого отрицательного a на положительное b, нужно применить противоположное данному числу и вычесть из него 1. Тогда остаток d будет вычисляться по формуле:
d = a − b * c
Из правила делаем вывод, что при делении получается целое неотрицательное число.
Для точности решения применим алгоритм деления а на b с остатком:
Рассмотрим пример, где можно применить алгоритм.
Пример
Найти неполное частное и остаток от деления −17 на 5.
Разделим заданные числа по модулю.
Получаем, что при делении частное равно 3, а остаток 2.
Так как получили 3, противоположное ему −3.
Необходимо отнять единицу: −3 − 1 = −4.
Чтобы вычислить остаток, необходимо a = −17, b = 5, c = −4, тогда:
d = a − b * c = −17 − 5 * (−4) = −17 − (− 20) = −17 + 20 = 3.
Значит, неполным частным от деления является число −4 с остатком 3.
Ответ: (−17) : 5 = −4 (остаток 3).
Деление с остатком целых отрицательных чисел
Сформулируем правило деления с остатком целых отрицательных чисел:
Для получения неполного частного с от деления целого отрицательного числа a на целое отрицательное b, нужно произвести вычисления по модулю, после чего прибавить 1. Тогда можно произвести вычисления по формуле:
d = a − b * c
Из правила следует, что неполное частное от деления целых отрицательных чисел — положительное число.
Алгоритм деления с остатком целых отрицательных чисел:
Пример
Найти неполное частное и остаток при делении −17 на −5.
Применим алгоритм для деления с остатком.
Разделим числа по модулю. Получим, что неполное частное равно 3, а остаток равен 2.
Сложим неполное частное и 1: 3 + 1 = 4. Из этого следует, что неполное частное от деления заданных чисел равно 4.
Для вычисления остатка применим формулу. По условию a = −17, b = −5, c = 4, тогда получим d = a − b * c = −17 − (−5) * 4 = −17 − (−20) = −17 + 20 = 3.
Получилось, что остаток равен 3, а неполное частное равно 4.
Ответ: (−17) : (−5) = 4 (остаток 3).
Деление с остатком с помощью числового луча
Деление с остатком можно выполнить и на числовом луче.
Пример 1
Рассмотрим выражение: 10 : 3.
Отметим на числовом луче отрезки по 3 деления. Видим, что три деления помещаются полностью три раза и одно деление осталось.
Решение: 10 : 3 = 3 (остаток 1).
Пример 2
Рассмотрим выражение: 11 : 3.
Отметим на числовом луче отрезки по 3 деления. Видим, что три деления поместились три раза и два деления осталось.
Решение: 11 : 3 = 3 (остаток 2).
Проверка деления с остатком
Пока решаешь пример, бывает всякое: то в окно отвлекся, то друг позвонил. Чтобы убедиться в том, что все правильно, важно себя проверять. Особенно ученикам 5 класса, которые только начали проходить эту тему.
Формула деления с остатком
a = b * c + d,
где a — делимое, b — делитель, c — неполное частное, d — остаток.
Эту формулу можно использовать для проверки деления с остатком.
Пример
Рассмотрим выражение: 15 : 2 = 7 (остаток 1).
В этом выражении: 15 — это делимое, 2 — делитель, 7 — неполное частное, а 1 — остаток.
Чтобы убедиться в правильности ответа, нужно неполное частное умножить на делитель (или наоборот) и к полученному произведению прибавить остаток. Если в результате получится число, которое равно делимому, то деление с остатком выполнено верно. Вот так:
Теорема о делимости целых чисел с остатком
Если нам известно, что а — это делимое, тогда b — это делитель, с — неполное частное, d — остаток. И они между собой связаны. Эту связь можно описать через теорему о делимости с остатком и показать при помощи равенства.
Теорема
Любое целое число может быть представлено только через целое и отличное от нуля число b таким образом:
где q и r — это некоторые целые числа. При этом 0 ≤ r ≤ b.
Доказательство:
Если существуют два числа a и b, причем a делится на b без остатка, тогда из определения следует, что есть число q, и будет верно равенство a = b * q. Тогда равенство можно считать верным: a = b * q + r при r = 0.
Тогда необходимо взять q такое, чтобы данное неравенством b * q
Делись, рыбка, быстро и нацело
Деление — одна из самых дорогих операций в современных процессорах. За доказательством далеко ходить не нужно: Agner Fog[1] вещает, что на процессорах Intel / AMD мы легко можем получить Latency в 25-119 clock cycles, а reciprocal throughput — 25-120. В переводе на Русский — МЕДЛЕННО! Тем не менее, возможность избежать инструкции деления в вашем коде — есть. И в этой статье, я расскажу как это работает, в частности в современных компиляторах(они то, умеют так уже лет 20 как), а также, расскажу как полученное знание можно использовать для того чтобы сделать код лучше, быстрее, мощнее.
Собственно, я о чем: если делитель известен на этапе компиляции, есть возможность заменить целочисленное деление умножением и логическим сдвигом вправо (а иногда, можно обойтись и без него вовсе — я конечно про реализацию в Языке Программирования). Звучит весьма обнадеживающе: операция целочисленного умножения и сдвиг вправо на, например, Intel Haswell займут не более 5 clock cycles. Осталось лишь понять, как, например, выполняя целочисленное деление на 10, получить тот же результат целочисленным умножением и логическим сдвигом вправо? Ответ на этот вопрос лежит через понимание… Fixed Point Arithmetic (далее FPA). Чуть-чуть основ.
При использовании FP, экспоненту (показатель степени 2 => положение точки в двоичном представлении числа) в числе не сохраняют (в отличие от арифметики с плавающей запятой, см. IEE754), а полагают ее некой оговоренной, известной программистам величиной. Сохраняют же, только мантиссу (то, что идёт после запятой). Пример:
0.1 — в двоичной записи имеет ‘бесконечное представление’, что в примере выше отмечено круглыми скобками — именно эта часть будет повторяться от раза к разу, следуя друг за другом в двоичной FP записи числа 0.1.
В примере выше, если мы используем для хранения FP чисел 16-битные регистры, мы не сможем уместить FP представление числа 0.1 в такой регистр не потеряв в точности, а это в свою очередь скажется на результате всех дальнейших вычислений в которых значение этого регистра участвует.
Пусть дано 16-битное целое число A и 16-битная Fraction часть числа B. Произведение A на B результатом дает число с 16 битами в целой части и 16-тью битами в дробной части. Чтобы получить только целую часть, очевидно, нужно сдвинуть результат на 16 бит вправо.
Поздравляю, вводная часть в FPA окончена.
Формируем следующую гипотезу: для выполнения целочисленного деления на 10, нам нужно выполнить умножение Числа Делимого на FP представление числа 0.1, взять целую часть и дело в шля… минуточку… А будет ли полученный результат точным, точнее его целая часть? — Ведь, как мы помним, в памяти у нас хранится лишь приближенная версия числа 0.1. Ниже я выписал три различных представления числа 0.1: бесконечно точное представление числа 0.1, обрезанное после 16-ого бита без округления представление числа 0.1 и обрезанное после 16 ого бита с округлением вверх представление числа 0.1.
Оценим погрешности truncating представлений числа 0.1:
Чтобы результат умножения целого числа A, на Аппроксимацию числа 0.1 давал точную целую часть, нам нужно чтобы:
Удобнее использовать первое выражение: при мы всегда получим тождество (но, заметь, не все решения, что в рамках данной задачи — более чем достаточно). Решая, получаем
. То есть, умножая любое 14-битное число A на truncating with rounding up представление числа 0.1, мы всегда получим точную целую часть, которую бы получили умножая бесконечно точно 0.1 на A. Но, по условию у нас умножается 16-битные число, а значит, в нашем случае ответ будет неточным и довериться простому умножению на truncating with rounding up 0.1 мы не можем. Вот если бы мы могли сохранить в FP представлении числа 0.1 не 16 бит, а, скажем 19, 20 — то все было бы ОК. И ведь можем же!
Внимательно смотрим на двоичное представление — truncating with rounding up 0.1: старшие три бита — нулевые, а значит, и никакого вклада в результат умножения не вносят (новых бит).
Следовательно, мы можем сдвинуть наше число влево на три бита, выполнить округление вверх и, выполнив умножение и логический сдвиг вправо сначала на 16, а затем на 3 (то есть, за один раз на 19 вообще говоря) — получим нужную, точную целую часть. Доказательство корректности такого ’19’ битного умножения аналогично предыдущему, с той лишь разницей, что для 16-битных чисел оно работает правильно. Аналогичные рассуждения верны и для чисел большей разрядности, да и не только для деления на 10.
В регистре RCX пусть хранится некое целое 62-битное A, а в регистре RAX пускай хранится 64 битное FA представление truncating with rounding up числа 0.1 (заметь, никаких сдвигов влево нету). Выполнив 64-битное умножение получим, что в регистре RDX сохранятся старшие 64 бита результата, или, точнее говоря — целая часть, которая для 62 битных чисел, будет точной. То есть, сдвиг вправо (SHR, SHRX) не нужен. Наличие же такого сдвига нагружает Pipeline процессора, вне зависимости поддерживает ли он OOOE или нет: как минимум появляется лишняя зависимость в, скорее всего и без того длинной цепочке таких зависимостей (aka Dependency Chain). И вот тут, очень важно упомянуть о том, что современные компиляторы, видя выражение вида some_integer / 10 — автоматически генерируют ассемблерный код для всего диапазона чисел Делимого. То есть, если вам известно что числа у вас всегда 53-ех битные (в моей задаче именно так и было), то лишнюю инструкцию сдвига вы получите все равно. Но, теперь, когда вы понимаете как это работает, можете сами с легкостью заменить деление — умножением, не полагаясь на милость компилятору. К слову сказать, получение старших битов 64 битного произведения в C++ коде реализуется интринсиком (something like mulh), что по Asm коду, должно быть равносильно строчкам инструкции MUL
Возможно, с появлением контрактов (в С++ 20 не ждем) ситуация улучшится, и в каких-то кейсах, мы сможем довериться машине! Хотя, это же С++, тут за все отвечает программист — не иначе.
Рассуждения описанные выше — применимы к любым делителям константам, ну а ниже список полезных ссылок:
Целочисленная арифметика. Делим с округлением результата. Часть 1
Чем проще, на первый взгляд, задача, тем меньше разработчик вдумывается в то, как грамотно её реализовать, и допущенную ошибку, в лучшем случае, обнаруживает поздно, в худшем — не замечает вовсе. Речь пойдет об одной из таких задач, а именно, о дробном делении и о масштабировании в контроллерах, поддерживающих исключительно целочисленную арифметику.
Почему тонкостям вычислений в условиях такой арифметики разработчики прикладных программ не уделяют внимание, вопрос. Рискну только предположить, что, по всей вероятности, сказывается привычка производить вычисления на калькуляторе… Во всяком случае, с завидной регулярностью «имею счастье» лицезреть, как коллеги по цеху наступают на одни и те же грабли. Этот материал нацелен на то, чтобы те самые «грабли» нейтрализовать.
При целочисленной арифметике результат деления одного целого числа на другое состоит из двух чисел — частного и остатка. Если остаток деления отбросить, получим результат, в абсолютной величине округленный до меньшего целого.
Реализуя вычисления с дробями, этот момент частенько упускают из вида, а, упустив, получают потери в точности вычислений. Причем точность вычислений падает с ростом величины делителя. К примеру, что 53/13, что 64/13 дадут в результате 4, хотя, по факту, частное от деления второй дроби существенно ближе к 5.
На самом деле, округление результата до ближайшего целого организовать элементарно. Для этого достаточно удвоить остаток деления, просуммировав его сам с собою, а затем вновь поделить его на то же число, на которое делили первоначально, и частное от этого деления сложить с частным, полученным от первоначальной операции деления.
Принимая во внимание то, что такие вычисления в программе могут потребоваться неоднократно, алгоритм вычислений реализуем в формате, пригодном для упаковки в подпрограмму.
Для корректного выполнения необходимых для этого промежуточных вычислений понадобится массив из пяти регистров, обозначим его условно TEMP[0..4]. Почему пять и не меньше, поясню чуть ниже.
Шаги с 3-го по 7-й могут быть вынесены в подпрограмму.
При желании, запись результата может быть произведена непосредственно суммированием TEMP[0] c TEMP[1] за пределами подпрограммы расчета. Это непринципиально. Единственное, следует иметь в виду, что при множестве однотипных расчетов вынос операции сложения в основное тело программы способен привести к возрастанию задействованного ею объема программной памяти.
Так почему же для промежуточных вычислений потребовалось целых 5 регистров? А операция суммирования остатка деления самого с собой, о чем говорилось ранее, заменена умножением остатка на два? Очень просто — для того, чтобы оперировать с неограниченным набором целых чисел.
То бишь, удвоенный остаток от целочисленного деления дроби в интересах округления результата такого деления всегда должен быть представлен в формате double integer.
Целочисленная арифметика¶
Видео¶
Основные определения¶
Специальный символ, выполняющий арифметические вычисления. В выражении a * b символ * — оператор умножения, a и b — его операнды.
Свойство оператора, влияющее на очередность его выполнения в выражении с несколькими различными операторами при отсутствии явного (с помощью скобок) указания на порядок их вычисления.
Например, результат выражения 2 + 2 * 2 — 6, поскольку приоритет операции умножения выше, чем приоритет операции сложения. Изменить порядок вычислений в выражении можно с помощью скобок:
последовательность выполнения операций (или направление вычисления), реализуемая когда операции имеют одинаковый приоритет и отсутствует явное (с помощью скобок) указание на очерёдность их выполнения.
Пример оператора с правой ассоциативностью — оператор возведения в степень:
Арифметические операторы¶
В таблице приведены некоторые арифметические операторы языка Python в порядке уменьшения приоритета (операторы с наибольшим приоритетом расположены выше).
Возведение в степень
Унарные плюс и минус
Сложение и вычитание
Целочисленное деление и взятие остатка от деления¶
Эти операции полезны при вычислениях с отдельными разрядами чисел.
Функции перевода чисел в различные системы счисления¶
Функции принимают целое число и возвращают его строковое представление в двоичной, восьмеричной и шестнадцатеричной системах счисления соответственно.
С этой функцией мы познакомились на прошлом занятии. Сейчас дополним, что вторым аргументом она может принимать основание системы счисления, в которой записано число x :
Задачи¶
Дано целое десятичное число. Выведите его последнюю цифру.
Дано целое десятичное число. Найдите число десятков в его десятичной записи.
Дано трехзначное число. Найдите сумму его цифр.
Пирожок в столовой стоит \(a\) рублей и \(b\) копеек. Определите, сколько рублей и копеек нужно заплатить за \(n\) пирожков.
Приложение запрашивает у пользователя стоимость одного пирожка и количество пирожков. Пример:
Приложение должно вычислить стоимость запрошенного количества пирожков. Пример вывода:
Дополнительные задачи¶
В школе решили набрать три новых математических класса. Так как занятия по математике у них проходят в одно и то же время, было решено выделить кабинет для каждого класса и купить в них новые парты. За каждой партой может сидеть не больше двух учеников. Известно количество учащихся в каждом из трёх классов. Сколько всего нужно закупить парт чтобы их хватило на всех учеников? Программа получает на вход три целых десятичных числа: количество учащихся в каждом из трех классов.
4. Доработайте код задачи № 3 таким образом, чтобы он запрашивал время начала занятий (минуты и часы отдельно) и номер урока, а далее также рассчитывал время окончания уроков.
5. Пользователь вводит число и систему счисления этого числа. Программа переводит число в десятичную, двоичную, восьмеричную и шестнадцетеричную системы счисления с использованием стандартных функций. Пример вывода:
Домашнее задание¶
Дано трехзначное число. Найти произведение его цифр.
Даны значения двух моментов времени, принадлежащих одним и тем же суткам: часы, минуты и секунды для каждого из моментов времени. Известно, что второй момент времени наступил не раньше первого. Определите, сколько секунд прошло между двумя моментами времени.