Perl, 137 символов
($x,$y)=<>;while($x=~s/.. *//s){$e=hex$&;$i=0;$s=$r[$i]+=$e*hex,$r[$i]&=255,$r[++$i]+=$s>>8 for$y=~/.. */gs;$y="00$y"}printf'%02x 'x@r,@r
Предостережения
- Иногда печатает дополнительный
00
байт в конце результата. Конечно, результат все еще правильный даже с этим лишним байтом.
- Печатает дополнительный пробел после последнего шестнадцатеричного байта в результате.
объяснение
Объяснение будет немного длинным, но я думаю, что большинство людей найдут его интересным.
Прежде всего, когда мне было 10 лет, меня научили следующему маленькому трюку. Вы можете умножить любые два положительных числа с этим. Я опишу это на примере 13 × 47. Вы начинаете с написания первого числа 13 и деления его на 2 (округление каждый раз), пока не достигнете 1:
13
6
3
1
Теперь, рядом с 13, вы пишете другое число, 47, и продолжаете умножать его на 2 столько же раз:
13 47
6 94
3 188
1 376
Теперь вы вычеркиваете все линии, где число слева чётно . В данном случае это только 6. (Я не могу сделать зачеркнутый код, поэтому я просто удалю его.) Наконец, вы добавляете все оставшиеся числа справа:
13 47
3 188
1 376
----
611
И это правильный ответ. 13 × 47 = 611.
Теперь, поскольку вы все компьютерные гики, вы поймете, что то, что мы на самом деле делаем в левом и правом столбцах, это x >> 1
и y << 1
, соответственно. Кроме того, мы добавляем y
только если x & 1 == 1
. Это переводит непосредственно в алгоритм, который я напишу здесь в псевдокоде:
input x, y
result = 0
while x > 0:
if x & 1 == 1:
result = result + y
x = x >> 1
y = y << 1
print result
Мы можем переписать, if
чтобы использовать умножение, и затем мы можем легко изменить это так, чтобы оно работало побайтово, а не побитно:
input x, y
result = 0
while x > 0:
result = result + (y * (x & 255))
x = x >> 8
y = y << 8
print result
Это по-прежнему содержит умножение на y
, которое имеет произвольный размер, поэтому нам нужно также преобразовать его в цикл. Мы сделаем это на Perl.
Теперь переведите все на Perl:
$x
и $y
являются входными данными в шестнадцатеричном формате, поэтому они имеют младший байт первым .
Таким образом, вместо x >> 8
меня $x =~ s/.. *//s
. Мне нужен пробел + звезда, потому что последний байт может не иметь пробела (может использовать пробел + ?
тоже). Это автоматически помещает удаленный byte ( x & 255
) в $&
.
y << 8
это просто $y = "00$y"
.
На result
самом деле это числовой массив @r
. В конце каждый элемент @r
содержит один байт ответа, но в середине вычисления он может содержать более одного байта. Ниже я докажу вам, что каждое значение никогда не превышает двух байтов (16 бит) и что результат всегда равен одному байту в конце.
Итак, вот код Perl, который был раскрыт и прокомментирован:
# Input x and y
($x, $y) = <>;
# Do the equivalent of $& = x & 255, x = x >> 8
while ($x =~ s/.. *//s)
{
# Let e = x & 255
$e = hex $&;
# For every byte in y... (notice this sets $_ to each byte)
$i = 0;
for ($y =~ /.. */gs)
{
# Do the multiplication of two single-byte values.
$s = $r[$i] += $e*hex,
# Truncate the value in $r[$i] to one byte. The rest of it is still in $s
$r[$i] &= 255,
# Move to the next array item and add the carry there.
$r[++$i] += $s >> 8
}
# Do the equivalent of y = y << 8
$y = "00$y"
}
# Output the result in hex format.
printf '%02x ' x @r, @r
Теперь для доказательства того, что это всегда выводит байты , и что вычисление никогда не генерирует значения больше двух байтов. Я докажу это по индукции через while
цикл:
Пустое @r
в начале явно не имеет значений больше 0xFF (потому что оно вообще не имеет значений). Это завершает базовый случай.
Теперь, учитывая, что @r
в начале каждой while
итерации содержится только один байт :
for
Цикл явно &=
S все значения в результирующем массиве с 255 , за исключением последнего , так что мы только должны смотреть на этом последнем.
Мы знаем , что мы всегда удалить только один байт из $x
и $y
:
Следовательно, $e*hex
это умножение двух однобайтовых значений, что означает, что оно находится в диапазоне 0 — 0xFE01
.
По индуктивной гипотезе, $r[$i]
это один байт; следовательно, $s = $r[$i] += $e*hex
находится в диапазоне 0 — 0xFF00
.
Поэтому $s >> 8
всегда один байт.
$y
увеличивается на 00
каждой итерации while
цикла:
Следовательно, на каждой итерации while
цикла внутренний for
цикл выполняется на одну итерацию больше, чем на предыдущей while
итерации.
Таким образом, $r[++$i] += $s >> 8
в последней итерации for
цикла всегда добавляет $s >> 8
к 0
, и мы уже установили , что $s >> 8
всегда один байт.
Следовательно, последнее значение, хранящееся в @r
конце for
цикла, также является одним байтом.
Это завершает замечательный и захватывающий вызов. Большое спасибо за публикацию!