По мере того, как я все больше и больше увлекаюсь теорией, лежащей в основе программирования, я нахожусь очарованным и ошеломленным, казалось бы, простыми вещами. Я понимаю, что мое понимание большинства фундаментальных процессов оправдано круговой логикой
Q : Как это работает?
A : Потому что это так!
Я ненавижу это понимание! Я люблю знания, и, кроме того, я люблю учиться, что приводит меня к моему вопросу (хотя и широкому).
Вопрос:
Как фундаментальные математические операторы оцениваются с помощью языков программирования?
Как современные методы были улучшены?
пример
var = 5 * 5;
Моя интерпретация:
$num1 = 5; $num2 = 5; $num3 = 0;
while ($num2 > 0) {
$num3 = $num3 + $num1;
$num2 = $num2 - 1;
}
echo $num3;
Это кажется крайне неэффективным. При более высоких коэффициентах этот метод очень медленный, в то время как стандартный встроенный метод является мгновенным. Как бы вы имитировали умножение без повторения сложения?
var = 5 / 5;
Как это вообще сделано? Я не могу придумать способ буквально разделить его на 5 равных частей.
var = 5 ^ 5;
Итерации итераций сложения? Моя интерпретация:
$base = 5;
$mod = 5;
$num1 = $base;
while ($mod > 1) {
$num2 = 5; $num3 = 0;
while ($num2 > 0) {
$num3 = $num3 + $num1;
$num2 = $num2 - 1;
}
$num1 = $num3;
$mod -=1;
}
echo $num3;
Опять же, это крайне неэффективно, но я не могу придумать другого способа сделать это. Этот же вопрос распространяется на все математические связанные функции, которые обрабатываются автоматически.
источник
Ответы:
Чтобы действительно понять, как работает арифметика внутри компьютера, вам нужно запрограммировать на ассемблере. Предпочтительно один с маленьким размером слова и без инструкций умножения и деления. Что-то вроде 6502.
На 6502 практически вся арифметика выполняется в регистре под названием «Аккумулятор». (Регистр - это специальная ячейка памяти внутри процессора, к которой можно быстро получить доступ.) Таким образом, чтобы добавить два числа, вы загружаете первое число в накопитель, а затем добавляете второе число к нему.
Но это упрощает. Поскольку 6502 - это 8-разрядный процессор, он может обрабатывать числа только от 0 до 255. Большую часть времени вы захотите работать с большими числами. Вы должны добавить их порциями по 8 бит за раз. У процессора есть флаг Carry, который устанавливается, когда результат добавления двух чисел переполняет Аккумулятор. Процессор добавляет, что при выполнении сложения его можно использовать для «переноса 1», предполагая, что вы начинаете с младшего байта числа. Многобайтовое добавление на 6502 выглядит так:
Вычитание аналогично, за исключением того, что вы сначала устанавливаете перенос, используете инструкцию SBC вместо АЦП, и в конце перенос очищается, если был недостаток.
Но ждать! А как насчет отрицательных чисел? Ну, с 6502 они хранятся в формате, называемом дополнением до двух. Предполагая 8-битное число, -1 сохраняется как 255, потому что, когда вы добавляете 255 к чему-либо, вы получаете на единицу меньше в Аккумуляторе (плюс перенос). -2 хранится как 254 и так далее, вплоть до -128, который хранится как 128. Таким образом, для целых чисел со знаком половина диапазона байта 0-255 используется для положительных чисел и половина для отрицательных чисел. (Это соглашение позволяет вам просто проверить старший бит числа, чтобы увидеть, является ли он отрицательным.)
Думайте об этом как о 24-часовых часах: добавление 23 к времени приведет к времени на час раньше (на следующий день). Таким образом, 23 является модульным эквивалентом часов -1.
Когда вы используете более 1 байта, вы должны использовать большие числа для негативов. Например, 16-битные целые числа имеют диапазон 0-65536. Таким образом, 65535 используется для представления -1, и так далее, потому что добавление 65535 к любому числу приводит к одному меньшему (плюс перенос).
На 6502 есть только четыре арифметических действия: сложение, вычитание, умножение на два (сдвиг влево) и деление на два (сдвиг вправо). Умножение и деление могут быть выполнены с использованием только этих операций при работе в двоичном формате. Например, рассмотрим умножение 5 (двоичное число 101) и 3 (двоичное число 11). Как и в случае десятичного длинного умножения, мы начинаем с правой цифры множителя и умножаем 101 на 1, давая 101. Затем мы сдвигаем умножение влево и умножаем 1010 на 1, давая 1010. Затем мы складываем эти результаты вместе, давая 1111, или 15. Поскольку мы когда-либо умножаем только на 1 или 0, мы на самом деле не умножаемся; каждый бит множителя просто служит в качестве флага, который говорит нам, следует ли добавить (сдвинутый) множитель или нет.
Деление аналогично ручному делению с использованием пробных делителей, кроме двоичного. Если вы делите на константу, это можно сделать способом, аналогичным вычитанию: вместо того, чтобы делить на X, вы умножаетесь на предварительно вычисленное отображение 1 / X, которое дает желаемый результат плюс переполнение. Даже сегодня это быстрее, чем разделение.
Теперь попробуйте выполнить математику с плавающей запятой в сборке или преобразовать числа с плавающей запятой в хорошие выходные форматы в сборке. И помните, это 1979 год, а тактовая частота равна 1 МГц, поэтому вы должны делать это максимально эффективно.
Сегодня все по-прежнему работает примерно так, за исключением больших размеров слов и большего количества регистров, и, конечно, большая часть математики выполняется аппаратно. Но это все так же принципиально. Если вы сложите, например, количество сдвигов и сложений, необходимых для умножения, это довольно хорошо коррелирует с количеством циклов, необходимых для инструкции аппаратного умножения на ранних процессорах, имеющих такую инструкцию, например, 6809, где она была выполнена в микрокоде почти так же, как если бы вы делали это вручную. (Если у вас больше транзисторный бюджет, есть более быстрые способы сдвигов и добавлений, поэтому современные процессоры не выполняют эти операции последовательно и могут выполнять умножения всего за один цикл.)
источник
Binary Multiplier
детали.В конце основные арифметические операции выполняются аппаратно. Более конкретно, в процессоре (или фактически его части)
Другими словами, это электронные схемы. Установите соответствующие биты в качестве входных данных, и вы получите соответствующие биты в качестве выходных данных. Это комбинация основных логических элементов.
http://en.wikipedia.org/wiki/Adder_%28electronics%29
http://en.wikipedia.org/wiki/Binary_multiplier
источник
Все это покрыто тщательным терпением в книге «Искусство компьютерного программирования» Дона Кнута.
Эффективные алгоритмы сложения, вычитания, умножения и деления описаны в деталях.
Вы можете читать такие вещи, которые хорошо освещают разделение.
http://research.microsoft.com/pubs/151917/divmodnote.pdf
источник
Это делается в пикосекундах с помощью электронных схем. Google 'аппаратный множитель' и т. Д. Для деталей. Современные процессоры являются чрезвычайно сложным результатом десятилетий непрерывного совершенствования.
Кстати, так как вы не умножаете на повторное сложение, почему вы думаете, что компьютер будет?
источник
Это ни в коем случае не является исчерпывающим ответом, но должно дать вам некоторое представление о том, как все реализовано. Как вы, наверное, знаете, числа представлены в двоичном виде. Например, компьютер может представить число 5 как 00000101. Самая базовая операция, которую может сделать компьютер, это сдвиг влево, что даст 00001010, что является десятичным числом 10. Если бы оно было сдвигом вправо дважды, это было бы 00010100 (десятичное число 20). Каждый раз, когда мы сдвигаем цифры влево, 1 раз мы удваиваем число. Скажем, у меня было число x, и я хотел умножить его на 17. Я мог сместить x влево 4 раза, а затем добавить x к результату (16x + x = 17x). Это был бы эффективный способ умножения числа на 17. Это должно дать вам некоторое представление о том, как компьютер может умножать большие числа без простого повторного сложения.
Деление может использовать комбинации сложения, вычитания, сдвига вправо, сдвига влево и т. Д. Существует также множество приемов для поднятия чисел до показателей степени.
источник
shl r0, 4
.Когда я был ребенком, я научился умножать и делить с помощью ручки и бумаги, не тратя время на слишком много дополнений. Позже я узнал, что квадратные корни тоже вычислимы.
В университете я научился вычислять тригонометрические и логарифмические операции с дюжиной умножений, делений и сложений. Они назвали это серией Тейлора.
До этого мой отец дал мне книгу, в которой эти сложные операции уже были вычислены для сотен значений и представлены в таблицах. Были также некоторые объяснения для оценки ошибки, когда вы хотели получить синус значения между двумя вычисленными значениями.
Целочисленные единицы, единицы с плавающей запятой, GPU и DSP просто реализуют все эти старые методы на кремнии.
источник
Я попытаюсь дать вам представление о том, как цифровые схемы предназначены для решения задач цифровой обработки, используя проблемы, которые вы ставите: как процессоры реализуют сложения и умножения.
Во-первых, давайте уберем прямой вопрос: как язык программирования эффективно оценивает умножения и сложения. Ответ прост, они собирают их в умножение и добавляют инструкции. Например, следующий код:
просто компилируется в нечто вроде:
(обратите внимание, что вышеприведенная сборка предназначена для воображаемого процессора, который не существует, для простоты).
В этот момент вы понимаете, что приведенный выше ответ просто сдвигает проблему и решает ее с помощью аппаратной магии. Следующий вопрос, очевидно, состоит в том, как работает эта аппаратная магия?
Давайте сначала посмотрим на более простую проблему: сложение.
Сначала мы делаем знакомую задачу, добавляя в обычную базу 10 чисел:
Первым шагом было бы добавить 7 и 8. Но это приводит к 15, что больше, чем одна цифра. Итак, мы несем 1:
Теперь мы добавляем 1, 1 и 2 вместе:
Итак, из этого мы получаем следующие правила:
когда результат сложения больше, чем одна цифра, мы сохраняем младшую значащую цифру и переносим самую значимую цифру вперед
если в нашу колонку перенесена цифра, мы добавим ее вместе с числами, которые добавляем
Теперь пришло время интерпретировать приведенные выше правила в базе 2 - булевой алгебре.
Таким образом, в булевой алгебре сложение 0 и 1 вместе = 1. Добавление 0 и 0 = 0. И добавление 1 и 1 = 10, что является более чем одной цифрой, поэтому мы переносим 1 вперед.
Из этого мы можем построить таблицу истинности:
Исходя из этого, мы можем построить две схемы / логические уравнения - одну для вывода суммы и одну для вывода переноса. Самый наивный способ - просто перечислить все входные данные. Любая таблица истинности, независимо от того, насколько большой и сложный может быть пересчитана в этой форме:
Это в основном сумма продуктов формы. Мы только смотрим на результаты, которые приводят к 1 и игнорируют 0:
Давайте заменим символы И ИЛИ и НЕ символами языка программирования, чтобы их было легче читать:
По сути, мы преобразовали таблицу следующим образом:
Это может быть непосредственно реализовано в виде схемы:
Наблюдающие читатели в этот момент заметят, что вышеприведенная логика может фактически быть реализована в виде одного шлюза - шлюза XOR, который удобно имеет поведение, требуемое нашей таблицей истинности:
Но если ваше аппаратное обеспечение не предоставляет вам шлюз XOR, вышеприведенные шаги помогут вам определить и реализовать его в терминах И, ИЛИ и НЕ.
Способ преобразования логических элементов в реальное оборудование зависит от того, какое у вас оборудование. Они могут быть реализованы с использованием различных физических механизмов, если этот механизм обеспечивает своего рода режим переключения. Логические ворота были реализованы со всем: от струй воды или струй воздуха (жидкости) до транзисторов (электроника) и падающих шариков. Это большая тема сама по себе, поэтому я собираюсь просто замять ее и сказать, что логические элементы можно реализовать как физические устройства.
Теперь мы делаем то же самое для сигнала переноса. Поскольку существует только одно условие, когда сигнал переноса истинен, уравнение просто:
Так что нести несложно:
Объединяя их вместе, мы получим так называемую половину сумматора:
Уравнения для приведенной выше схемы, кстати, выглядит так:
Половина сумматора чего-то не хватает. Мы внедрили первое правило - если результат больше чем одна цифра, чем перенос, но мы не внедрили второе правило - если есть перенос, добавьте его вместе с числами.
Таким образом, чтобы реализовать полный сумматор, схему сложения, которая может добавлять числа, состоящие более чем из одной цифры, нам нужно определить таблицу истинности:
Уравнение для суммы теперь:
Мы можем пройти тот же процесс, чтобы вычленить и упростить уравнение и интерпретировать его как схему и т. Д., Как мы делали выше, но я думаю, что этот ответ становится слишком длинным.
К настоящему времени вы должны понять, как устроена цифровая логика. Есть и другие приемы, которые я не упомянул, такие как карты Карно (используемые для упрощения таблиц истинности) и логические компиляторы, такие как эспрессо (чтобы вам не приходилось разлагать булевы уравнения вручную), но основное - это то, что я обрисовано в общих чертах выше:
Разложите проблему, пока вы не сможете работать на уровне одного бита (цифры).
Определите выходы, которые вы хотите, используя таблицу истинности.
Преобразуйте таблицу в логическое уравнение и упростите уравнение.
Интерпретировать уравнение как логические элементы.
Преобразуйте свою логическую схему в реальные аппаратные схемы, реализовав логические элементы.
Вот как на самом деле решаются фундаментальные (или, скорее, низкоуровневые) проблемы - множество таблиц истинности. Настоящая творческая работа состоит в том, чтобы разбить сложную задачу, такую как декодирование MP3 на битовый уровень, чтобы вы могли работать с ней с таблицами истинности.
Извините, у меня нет времени объяснять, как реализовать умножение. Вы можете попытаться разобраться в этом, выяснив правила того, как долго работает умножение, затем интерпретировать его в двоичном формате, а затем попытаться разбить его на таблицы истинности. Или вы можете прочитать Википедию: http://en.wikipedia.org/wiki/Binary_multiplier
источник
основные арифметические инструкции выполняются с инструкциями по сборке, которые очень эффективны.
Более сложные (или абстрактные) инструкции либо выполняются в сборке с использованием циклических механизмов, либо обрабатываются в стандартных библиотеках.
Изучая математику в колледже, вы начнете изучать такие вещи, как лямбда-исчисление и система обозначений Big-O. Все это и многие другие используются программистами для оценки и создания эффективных алгоритмов. В любом случае, базовые вещи обычно выполняются на низком уровне, например, в сборке или с помощью указателей.
Отличным введением в эту тему является «Кодекс» Чарльза Петцольда.
источник
Получите книгу типа « Основы цифровой логики ...» , которая, как мне кажется, все еще довольно стандартна для студентов-первокурсников и второкурсников, изучайте ее (под ред .: это стоит небольшого состояния, поэтому ищите подержанный или предыдущий выпуск Это). Это поможет вам разобраться с сумматорами и множителями и даст вам достаточно знаний, чтобы начать понимать некоторые принципы работы аппаратного обеспечения.
В краткосрочной перспективе ваш ответ станет «потому что он объединяет множество кусочков более простой логики, чтобы вызвать это сложное поведение», а не «потому что это так».
Если вы хотите попытаться понять все принципы компиляции и запуска программ, сделайте это вместе, чтобы в конечном итоге увидеть, как все встречается посередине.
источник
Здесь много хороших ответов. Вы также начали с правильной идеи: сложные операции, такие как умножение, строятся из более простых операций. Как вы уже догадались, существуют более быстрые способы умножения без инструкции умножения, чем использование серии дополнений. Любое умножение может быть реализовано как сумма меньших умножений или как комбинация сдвигов и сложений. Примеры:
Отдел также может быть разбит на более мелкие операции. XOR (^) - это встроенная инструкция для каждого процессора, на которую я когда-либо смотрел, но даже в этом случае она может быть реализована как комбинация AND, OR и NOT.
Однако у меня есть ощущение, что конкретные ответы будут менее удовлетворительными для вас, чем общее представление о видах инструкций, которые процессор предоставляет, и о том, как эти инструкции могут быть объединены в более сложные операции. Нет ничего лучше для такого любопытства, чем здоровая доза ассемблера. Вот очень доступное введение в язык ассемблера MIPS.
источник
Вот как современный процессор может реализовать умножение двух 64-битных целых чисел:
Вы знаете, как сделать умножение длинной руки. Чтобы умножить два 10-значных чисел, вы должны умножить одно 10-значное число на каждую из 10 цифр другого числа, записать результат из 11 цифр одну под другой и сдвинуть, а затем сложить все числа.
Современный процессор делает это со всеми 64 на 64 бита. Однако умножение двух одноразрядных чисел очень просто: 1 x 1 = 1, все остальные произведения равны нулю. Это реализовано с логическим и. И в отличие от десятичного произведения, где результатом может быть две цифры, двоичное произведение одноразрядных чисел всегда равно одному биту.
Итак, теперь у вас есть 64 строки по 64 бита, которые необходимо добавить. Но 64 дополнения 64-битных чисел являются неоправданными. Таким образом, процессор использует либо сумматор 3/2, либо сумматор 7/3: если вы добавите 3 однобитовых числа, результат может быть 0, 1, 2 или 3, который умещается в два бита. Если вы добавите 7 однобитовых чисел, результатом будет число от 0 до 7, которое может быть представлено 3 битами. IBM утверждает, что они могут сделать сумматор 7/3 всего с 18 примитивными цепями (документация PowerPC), я уверен, что Intel и ARM могут сделать это также.
У вас есть 4096 бит, сгруппируйте их примерно в 600 групп по 7 бит в одинаковых битовых позициях и используйте около 600 7/3 сумматоров, чтобы уменьшить результат с 4096 бит до менее 2000. Затем вы делаете то же самое снова и снова, пока не получите пары битов, которые можно подавать в обычный полный сумматор.
источник