GAP, 368 Bytes
For mathematicians, this is multiplication in the polynomial ring F_2[x], identifying polynomials with natural numbers by evaluating at x=2 as a polynomial over Z.
Sure, let's do that! (this is only loosly golfed, the point was more to move into F2[x] and do the calculations more than any attempt at being a winning entry)
Here's the code
f:=function(i,j)R:=PolynomialRing(GF(2));x:=IndeterminatesOfPolynomialRing(R);x:=x[1];a:=function(i)local n,r;r:=0*x;while not i=0 do n:=0;while 2^n<=i do n:=n+1;od;n:=n-1;r:=r+x^n;i:=i-2^n;od;return r;end;b:=function(r)local c,i,n;i:=0;n:=0;for c in CoefficientsOfUnivariatePolynomial(r) do if c=Z(2)^0 then n:=n+2^i;fi;i:=i+1;od;return n;end;return b(a(i)*a(j));end;
Here's the ungolfed code with explanation:
xor_multiplication:=function(i,j)
R:=PolynomialRing(GF(2));
x:=IndeterminatesOfPolynomialRing(R);
x:=x[1];
to_ring:=function(i)
local n,r;
r:=0*x;
while not i=0 do
n:=0;
while 2^n<=i do
n:=n+1;
od;
n:=n-1;
r:=r+x^n;
i:=i-2^n;
od;
return r;
end;
to_ints:=function(r)
local c,i,n;
i:=0;n:=0;
for c in CoefficientsOfUnivariatePolynomial(r) do
if c=Z(2)^0 then
n:=n+2^i;
fi;
i:=i+1;
od;
return n;
end;
return to_ints( to_ring(i)*to_ring(j));
end;
Okay, so first off, we create the univariate polynomial ring over the field F2 and call it R
. Note that GF(2)
is F2 in GAP.
R:=PolynomialRing(GF(2));
Next, we are going to assign the GAP variable x
to the indeterminate of the ring R
. Now, whenever I say x
in GAP, the system will know I am talking about the indeterminate of the ring R
.
x:=IndeterminatesOfPolynomialRing(R);
x:=x[1];
Next, we have two functions, which are inverse maps of each other. These maps are both onto, but they are not structure preserving, so I couldn't figure out a better way to implement them in GAP. There almost certainly is a better way, if you know it, please comment!
The first map, to_ring
takes an integer and maps it to its corresponding ring element. It does this by using a conversion to binary algorithm, where every 1
that would appear in binary is replaced by an x^n
where n
is the appropriate power that 2 would take if the number was indeed binary.
to_ring:=function(i)
local n,r;
r:=0*x; # initiate r to the zero element of R
while not i=0 do # this is a modified binary algorithm
n:=0;
while 2^n<=i do
n:=n+1;
od;
n:=n-1;
r:=r+x^n;
i:=i-2^n;
od;
return r;
end;
The next function reverses this. to_ints
takes a ring element and maps it to its corresponding integer. I do this by getting a list of the coefficients of the polynomial and for each nonzero coefficient, the result is increased by 2^n, in the same way that we would convert binary to decimal.
to_ints:=function(r)
local c,i,n;
i:=0;n:=0;
for c in CoefficientsOfUnivariatePolynomial(r) do
if c=Z(2)^0 then
# ^-- Right here you'll notice that the Z(2) is basically '1' in GF(2). So Z(2)^0 ~ 1 and Z(2)*0 ~ 0
# effectively, this line checks for nonzero coefficients
n:=n+2^i;
fi;
i:=i+1;
od;
return n;
end;
For the final step, we call these functions. We take the two integer inputs, convert them into elements in the ring R
, then multiply these elements together, and send the product back to the integers.
return to_ints( to_ring(i)*to_ring(j));
PCLMULQDQ
from the CLMUL extension. Unfortunately I got downvoted for my knowledge of the x86 instruction set before (Related toPEXT/PDEP
), so I'm going to just leave this as a comment here. \$\endgroup\$