x86 Assembly (targeting the x87 FPU), 23 bytes
As is standard with 32-bit calling conventions, the double-precision floating-point parameter is passed on the stack, and returned at the top of the x87 FPU stack. Assemble with MASM:
.686 ; fucomip requires a Pentium Pro or later CPU
divisor DD 043b40000r ; 360.0f
fld DWORD PTR [divisor] ; single-precision takes fewer bytes to store; we don't need the precision
fld QWORD PTR [esp + 4] ; load parameter from stack
fprem ; st(0) = parameter % 360
; st(1) = 360
fucomip st(0), st(1) ; st(0) < 0?
fadd st(0), st(1) ; fixup negative modulo by adding 360
fstp st(1) ; discard st(1); result is left in st(0)
To call from C:
extern double CoterminalAngle(double value);
D9 05 00 00 00 00
DD 44 24 04
As the comment indicates, to minimize code size, I've stored the divisor constant (
360.0f) as a single-precision floating-point value. This means it is half the length it would be if it were a double-precision value, and we don't need the precision to store a proper representation of the value. Upon loading, the x87 FPU will implicitly extend it to its native ten-byte extended-precision format.
We're also playing it fast-and-lose with the
FPREM instruction for golfing purposes, assuming it does not need to reduce the exponent of the input value by more than 63. The careful (read: correct) way to call it would be iteratively, in a loop, continuing to execute it as long as the "parity" flag is set.
That would add 6 bytes to the total.
x86 Assembly (targeting AVX), 47 bytes
By way of comparison (and for completeness), here's an AVX implementation. It's more bytes, but also more efficient. The parameter is passed in
XMM0, and the result is returned in the same register, as with any x86-64 or vector x86-32 calling convention.
43 B4 00 00 | divisor DD 043b40000r ; 360.0f
| ; Load single-precision FP value, and convert it to double-precision.
C5 EA 5A 15 00 | vcvtss2sd xmm2, xmm2, DWORD PTR [divisor]
00 00 00 |
| ; Divide input parameter (XMM0) by divisor (XMM2), and store result in XMM1.
C5 FB 5E CA | vdivsd xmm1, xmm0, xmm2
| ; Truncate the result of the division to the nearest integer.
C5 FB 2C C1 | vcvttsd2si eax, xmm1
C5 F3 2A C8 | vcvtsi2sd xmm1, xmm1, eax
| ; Multiply the truncated result by divisor (XMM2).
C5 F3 59 CA | vmulsd xmm1, xmm1, xmm2
| ; Subtract this temporary value (XMM1) from the original input (XMM0).
C5 FB 5C C1 | vsubsd xmm0, xmm0, xmm1
| ; See if the result is negative.
C5 F1 57 C9 | vxorpd xmm1, xmm1, xmm1 ; xmm1 = 0
C5 F9 2F C8 | vcomisd xmm1, xmm0 ; result < 0?
76 04 | jbe Finished
C5 FB 58 C2 | vaddsd xmm0, xmm0, xmm2 ; fixup negative modulo by adding divisor (XMM2)
C3 | ret
Of course, it would be even more efficient to multiply by the reciprocal, instead of dividing. But that means more precision is required to store the divisor, and would thus slightly increase the size of the code.
a % bchallenge would be more interesting, although I feel like we've had that before. \$\endgroup\$