Не могу придумать формулу. Задача несколько специфическая. Предположим, что у нас есть некоторая переменная, которая может хранить число из диапазона от 0 до 9.
Надо придумать как умножить такие переменные.
Диапазон следует представлять как замкнутую ленту из последовательных ячеек 0, 1, 2, ..., 9, таким образом если мы пишем 9+1, то попадаем в ячейку 0 (т.е. 9+1=0), и так далее вплоть до 9+9=8.

Дано:
max = 9 - это предельное значение диапазона,
a - одна переменная,
b - другая переменная
carry - переменная переноса.

Операции:
+, -, *, /
битовые |, &, ^, ! (or, and, xor, not)
% - взятие остатка
ну и любые другие можно использовать

Примеры:
a=1, b=9 => carry = 0
a=2, b=9 => carry = 1
a=4, b=8 => carry = 3

Ограничения:
переменных способных хранить числа, состоящие более, чем из одного знака, не существует. Это означает, что операция 2*9 невыполнима в стандартном понимании, так как не существует переменной способной хранить число 18, результатом операции 2*9 будет 2*9=9+9=8 как уже говорилось. Данное ограничение можно воспринимать как своеобразную архитектуру процессора, который работает только лишь с однозначными числами.

Найти:
функцию carry = f(a,b,max).

Например, при 2*9 нахождение carry=1 позволит отпечатать произведение как последовательная печать переменной carry как 1 и печать операции 2*9 как 8, итого 18.

@темы: Головоломки и занимательные задачи, Дискретная математика

Комментарии
21.11.2020 в 22:58

Аппаратно числа в регистрах процессора сохранены как двоичные числа, а сложение реализовано поразрядно с помощью таблицы истинности или элементарных операций AND, OR и т.д. Кому интересно, скажу, что подробнее эта тема раскрыта в двух хороших книжках - (Харрис) Цифровая схемотехника и архитектура компьютера и её продолжении (написанным, кстати, российскими специалистами в области проектирования цифровой схемотехники) "Цифровой синтез. Практический курс. - ДМК-Пресс".

Поэтому нужно ещё как минимум оговорить, как хранятся числа и (обязательно!) что нужно считать элементарными операциями в этой архитектуре. Иначе в самом простом случае мы можем заявить, что всё реализовано через большую таблицу истинности и задача решена.
21.11.2020 в 23:24

Сорри, я не заметил, что доступны битовые операции, значит я немного не на тот вопрос ответил. )))

Вопрос в определении carry.
Ну. во-первых, если вы обратитесь к рекомендованной литературе, то окажется, что carry - это обычно расширение регистра, и если переполнение случилось, то в дополнительных битах не нуль (то есть carry не вычисляют отдельной формулой), это как бы 32(64)-тый бит регистра (для вашей задачи надо взять не один дополнительный бит, а 4).

Во-вторых, битовые операции несколько странно применять для регистров емкостью не кратных степени 2. Что, например, будет означать битовая операция 5 OR 8?

Про формулу сейчас ещё подумаю.
22.11.2020 в 01:36

Trotil, тестовое задание дали, там суть в том, что тип данных только один -- беззнаковое 16-тиразрядное целое число, и вот на нём надо реализовать произведение 32-разрядных чисел. На чистом Си.

Код написал в итоге так пока



Работает. Как понимаю вместо девятки UINT_MAX бахнуть и всё будет работать для стандартного 32-разрядного unsigned int.
По задаче же придётся свою константу для наибольшего значения 16-разрядного числа определить насколько понимаю, UINT16_MAX какой-нибудь.
22.11.2020 в 03:27

Trotil, проверил в деле функцию carry, работает.



Выводит


Осталось алгоритм умножения подобрать какой-нибудь... И собрать это всё дело.
22.11.2020 в 04:03

> беззнаковое 16-тиразрядное целое число, и вот на нём надо реализовать произведение 32-разрядных чисел

Классический вариант решения подобной задачи - хранить такое число в массиве. Не нужно никаких carry. Или нельзя uint16[2]?
22.11.2020 в 04:11

> typedef unsigned int uint16;

Что это? Есть архитектурно независимый тип данных uint16_t. Используй его.
22.11.2020 в 09:07

Trotil, можно, так и делаю потом. Про uint16_t не знал, но как переполнение обрабатывать?
22.11.2020 в 09:41

> Про uint16_t не знал, но как переполнение обрабатывать?
В книжках, которые я порекомендовал, есть отдельная глава по сложению n-разрядных чисел и отдельная глава по умножению n-разрядных чисел. Вкратце, делают подробную реалиазацию на небольшом количестве бит, а затем строят простую цепочку этих блоков, т.е. наращивают сумматор до необходимой разрядности.

Если не хочешь глубоко вникать в эту тему, вот тебе лайфхак - реализуй на базе uint16_t арифметику восьмибитных чисел, тогда тебе не придётся вводить отдельную переменную под перенос или умножение, а 32-битное число, это массив из 4 этих чисел, где значимыми являются только последние 8 бит.
22.11.2020 в 10:07

Trotil, понимаю это, под результат и стоит массив из 4ех элементов. Я к тому, что вроде бы переполнения не избежать при любом раскладе. Допустим старшие 2 байта в элементе с индексом 0, младшие 2 байта в элементе с индексом 1. Также допустим, что реализуем алгоритм умножения столбиком, знаю что есть алгоритм карацубы, алгоритм бута, но не вникал в них ещё, поэтому первое что в голову пришло беру. При умножении столбиком вначале перемножаются единицы, в данном случае это будет res[3] = a[1] * b[1], но есть проблема переполнения, например элементы равны a[1]=b[1]=65535 = 0xffff, a мы не то что умножение, обычное сложение допустим делаем 65535+65535=65534=0xfffe, вместо 131070=0x0001 0xfffe.
22.11.2020 в 10:16

В варианте, который я предложил, Число 65535 размещается в массиве из двух элементов типа uint_16t, a[0]=0xff, a[1]=0xff
сложение, это b[0]=a[0]+a[0]=0x1fe, b[1]=a[1]+a[1]=0x1fe, и переносов: b[1] = (a[0] >> 8) + b[0] и сохранить b[2] перенос из b[1].
22.11.2020 в 10:23

Trotil, арифметика 8-битных чисел на базе uint16_t не подходит потому, что тут уже два типа по факту uint8_t и его расширение до uint16_t, появляются биты для хранения числа, а значит цифры можно сдвигать операторами сдвига << или >>, а надо на базе uint8_t сделать одну операцию из арифметика 16-битных чисел, и тут операторы сдвига не помогут на первый взгляд, отя потому, что двигать некуда.
22.11.2020 в 10:27

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

Trotil, арифметика 8-битных чисел на базе uint16_t не подходит потому, что тут уже два типа по факту uint8_t и его расширение до uint16_t, появляются биты для хранения числа, а значит цифры можно сдвигать операторами сдвига << или >>, а надо на базе uint8_t сделать одну операцию из арифметика 16-битных чисел, и тут операторы сдвига не помогут на первый взгляд, отя потому, что двигать некуда.

Я не очень понял, почему нельзя... Ну если вдруг действительно нельзя, сделай 4-ёх битные на базе 8-ми битных :)
22.11.2020 в 10:36

Trotil, спасибо, дошло до меня. Фактически умышленно в элемент заносится числа наполовину меньшие максимального, тогда место есть, сдвиг работает. Тогда получается представление 32разрядного будет состоять уже не из двух, а из 4ех элементов, а результат умножения из 8 элементов.
22.11.2020 в 10:51

Trotil, спасибо, дошло до меня. Фактически умышленно в элемент заносится числа наполовину меньшие максимального, тогда место есть, сдвиг работает. Тогда получается представление 32разрядного будет состоять уже не из двух, а из 4ех элементов, а результат умножения из 8 элементов.

Ура.
Всё правильно, только результат умножения всё равно из 4ех элементов, старшие биты теряются, хотя в некоторых архитектурах предусмотрено сохранение результата умножения в двух регистрах, например x86 результат операции imul двух 32-битных чисел сохраняется в EAX и EDX.

Терминологически неграмотно брать массивы из 8 элементов, если арифметика 32-битная, надо аккуратнее действовать.
22.11.2020 в 11:01

Trotil, насколько понял объявление такое:
uint16_t a_high = { 0xff, 0xff }; // 1-ое число 16-разрядное
uint16_t a_low = { 0xff, 0xff} ; // 2-ое число 16-разрядное
uint16_t a = { a_high, a_low }; // 1-ое число 32-разрядное
и
uint16_t b_high = { 0xff, 0xff }; // 3-ье число 16-разрядное
uint16_t b_low = { 0xff, 0xff} ; // 4-ое число 16-разрядное
uint16_t b = { b_high, b_low }; // 2-ое число 32-разрядное

Формула такая
a * b = 16^2 * a_high * b_high +
16 * (a_high * b_low + a_low * b_high) +
a_low * b_low

Произведение и сложение 16-разрядных реализовываем в функциях, а при программировании формулы их вызываем.
22.11.2020 в 11:05

Попробуй, сделай тесты. :)
22.11.2020 в 11:16

Trotil, интересный, конечно, трюк, понравился. Никогда не писал подобных алгоритмов. Вы, вероятно, удивились с подходом через функцию carry, но сразу скажу, если бы читал про "сужение числа" , то этот carry в голову бы даже не пришел. Даже сейчас удивляюсь как такой велосипед придумал.
22.11.2020 в 11:25

Вы, вероятно, удивились с подходом через функцию carry

Нисколько, это классический подход с результом и отдельно переполнением, только его сложнее реализовывать, на мой взгляд. Использование carry даже оптимальнее, если речь идёт о реальных процессорах и нужно минимизировать число тактов для операции сложения и умножения.
22.11.2020 в 17:54

Trotil, понял. Часа 3 назад сел за ноут и дописал через переполнение, все случаи не проверял, просто взял пример, чтобы на каждом шаге переполнение было и проверил. Параллельно выяснилось, что вот здесь lin.in.ua/tools/numconv.html калькулятор из dec в hex число 1346878763369683037669386354688 неправильно переводит, я уж думал код криво написал.

p.s. реализация функции mul() не нравится, хотелось бы как-то покомпактнее её записать, но ничего в голову не пришло...

Solution 1
// Result and overflow



Сейчас буду пробовать через усечение :alles:
22.11.2020 в 19:12

Ещё один тест



Работает, поэтому заключу, что всё работает.
22.11.2020 в 21:59

Код выше крашился в функции mul(), неправильно перенос считал при a[0]*b[1] + a[1]*b[0]. Исправленная окончательная версия.


23.11.2020 в 11:33

Я бы сделал следующий тест:

взял бы случайное число и сложил бы его сам с собой 12345 раз.
Потом проверил на равенство результат и умножение.
В качестве нового слагаемого на вход сумматору подавать результат суммы, полученной на предыдущем шаге.
28.11.2020 в 17:48

Trotil,
В варианте, который я предложил, Число 65535 размещается в массиве из двух элементов типа uint_16t, a[0]=0xff, a[1]=0xff
сложение, это b[0]=a[0]+a[0]=0x1fe, b[1]=a[1]+a[1]=0x1fe, и переносов: b[1] = (a[0] >> 8) + b[0] и сохранить b[2] перенос из b[1].


пробую написать сложение, но никак не получается.

Вот код

28.11.2020 в 18:01

Trotil, удалось-таки



Вывод
sum: 1 fffe
28.11.2020 в 19:10

Trotil, а как произведение писать? Пишу по аналогии со сложением, но на выходе ересь какая-то...


28.11.2020 в 22:28

Попробуй сначала разобраться с умножением на число 0xff.
28.11.2020 в 22:43

Trotil, посмотрел книги, которые вы рекомендовали, Харриса оказывается знаю, мы это в универе проходили, верилог всё же другое как по мне дк.

p.s.
вообще думал мне задание на ООП кинут, а тут чистый сишник, как увидел, так сразу плохо стало :)
само задание сдал, но спортивный интерес остался, хочу через битовые сдвиги запилить
прогуглил интернет и что странно, эта тема как-то криво раскрыта в сети, как бы код есть, но не тот, который хотелось бы увидеть.
28.11.2020 в 23:43

Trotil,


Вывод:
mul: fe ff01

Правильно работает, хм :thnk:
28.11.2020 в 23:46

А дальше схема, как при умножении в столбик: взять второй байт, умножить, сдвинуть результат (на сколько?), сложить с результатом, полученным на первом шаге, взять третий байт и т.д. Вроде несложно.