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:
.MODEL flat
.686 ; fucomip requires a Pentium Pro or later CPU
CONST SEGMENT
divisor DD 043b40000r ; 360.0f
CONST ENDS
PUBLIC _CoterminalAngle
_TEXT SEGMENT
_CoterminalAngle PROC
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
fldz
fucomip st(0), st(1) ; st(0) < 0?
jbe Finished
fadd st(0), st(1) ; fixup negative modulo by adding 360
Finished:
fstp st(1) ; discard st(1); result is left in st(0)
ret
_CoterminalAngle ENDP
_TEXT ENDS
END
To call from C:
extern double CoterminalAngle(double value);
In bytes:
43B40000
D9 05 00 00 00 00
DD 44 24 04
D9 F8
D9 EE
DF E9
76 02
D8 C1
DD D9
C3
Notes:
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.
For example:
RemainderLoop:
fprem
fstsw ax
sahf
jp RemainderLoop
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)
|
| Finished:
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 % b
challenge would be more interesting, although I feel like we've had that before. \$\endgroup\$