Perl, 137 caracteres
($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
Ressalvas
- Às vezes, imprime um
00
byte extra no final do resultado. Obviamente, o resultado ainda está correto, mesmo com esse byte extra.
- Imprime um espaço extra após o último hex byte no resultado.
Explicação
A explicação será um pouco longa, mas acho que a maioria das pessoas aqui achará interessante.
Antes de mais, quando eu tinha 10 anos, aprendi o pequeno truque a seguir. Você pode multiplicar quaisquer dois números positivos com isso. Descreverei isso usando o exemplo de 13 × 47. Comece escrevendo o primeiro número, 13 e dividindo -o por 2 (arredondar para baixo a cada vez) até chegar a 1:
13
6
3
1
Agora, ao lado dos 13, você escreve o outro número, 47, e continua multiplicando -o por 2 o mesmo número de vezes:
13 47
6 94
3 188
1 376
Agora você cruza todas as linhas em que o número à esquerda é par . Nesse caso, esse é apenas o 6. (Não consigo executar o código de correção, então vou removê-lo.) Finalmente, você adiciona todos os números restantes à direita:
13 47
3 188
1 376
----
611
E esta é a resposta certa. 13 × 47 = 611.
Agora, como todos vocês são viciados em computadores, perceberão que o que realmente estamos fazendo nas colunas esquerda e direita é x >> 1
e y << 1
, respectivamente. Além disso, adicionamos o y
único se x & 1 == 1
. Isso se traduz diretamente em um algoritmo, que escreverei aqui no pseudocódigo:
input x, y
result = 0
while x > 0:
if x & 1 == 1:
result = result + y
x = x >> 1
y = y << 1
print result
Podemos reescrever o if
para usar uma multiplicação e, em seguida, podemos mudar isso facilmente para que funcione de byte a byte em vez de pouco a pouco:
input x, y
result = 0
while x > 0:
result = result + (y * (x & 255))
x = x >> 8
y = y << 8
print result
Isso ainda contém uma multiplicação com y
tamanho arbitrário, portanto, também precisamos mudar isso para um loop. Nós vamos fazer isso em Perl.
Agora traduza tudo para o Perl:
$x
e $y
são as entradas no formato hexadecimal, para que tenham primeiro o byte menos significativo .
Assim, em vez de x >> 8
eu faço $x =~ s/.. *//s
. Eu preciso do espaço + estrela porque o último byte pode não ter espaço nele (também pode usar espaço + ?
). Isso coloca automaticamente o byte removido ( x & 255
) em $&
.
y << 8
é simplesmente $y = "00$y"
.
O result
é realmente uma matriz numérica @r
,. No final, cada elemento de @r
contém um byte da resposta, mas, no meio do cálculo, pode conter mais de um byte. Vou provar a você abaixo que cada valor nunca é superior a dois bytes (16 bits) e que o resultado é sempre um byte no final.
Então, aqui está o código Perl desvendado e comentado:
# 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
Agora, a prova de que isso sempre gera bytes e que o cálculo nunca gera valores maiores que dois bytes. Vou provar isso por indução ao longo do while
loop:
O vazio @r
no início claramente não possui valores maiores que 0xFF (porque não possui valores). Isso conclui o caso base.
Agora, dado que @r
contém apenas bytes únicos no início de cada while
iteração:
O for
loop explicitamente &=
s todos os valores na matriz de resultados com 255, exceto o último , portanto, precisamos apenas olhar para esse último.
Sabemos que sempre removemos apenas um byte $x
e $y
:
Portanto, $e*hex
é uma multiplicação de dois valores de byte único, o que significa que está no intervalo 0 — 0xFE01
.
Pela hipótese indutiva, $r[$i]
é um byte; portanto, $s = $r[$i] += $e*hex
está no intervalo 0 — 0xFF00
.
Portanto, $s >> 8
é sempre um byte.
$y
cresce um extra 00
em cada iteração do while
loop:
Portanto, em todas as iterações do while
loop, o for
loop interno é executado por mais uma iteração do que na while
iteração anterior .
Portanto, o $r[++$i] += $s >> 8
na última iteração do for
loop de sempre adiciona $s >> 8
a 0
, e já estabeleceu que $s >> 8
é sempre um byte.
Portanto, o último valor armazenado no @r
final do for
loop também é um único byte.
Isso conclui um desafio maravilhoso e emocionante. Muito obrigado por publicá-lo!