Ответ 1
.L2:
movq %rcx, %rax
andl $1, %ecx ; save the least significant bit of %rax
pxor %xmm0, %xmm0
shrq %rax ; make %rax represent half the original number, as a signed value
orq %rcx, %rax ; "round to odd": if the division by two above was not exact, ensure the result is odd
cvtsi2sdq %rax, %xmm0 ; convert to floating-point
addsd %xmm0, %xmm0 ; multiply by two
ucomisd %xmm0, %xmm1 ; compare …
setae %al
ret
Последние три команды реализуют <=
и return
из исходного кода. Остальные являются частью преобразования от uint64_t
до double
.
Трудный для понимания шаг - это тот, который я прокомментировал как "круглый до нечетного". "Округление до нечетного" - это метод, который предотвращает неприятные эффекты "двойное округление" .
Фактически, алгоритм должен преобразовывать с 64-битного в 63-битный, а затем из 63 бит в бинарный код IEEE 754. Если реализовать наивно, в некоторых случаях эти два преобразования могут привести к результату, который отличается от прямого однократного преобразования от 64-разрядного целого к плавающей точке. Это то, что называется "двойное округление".
64-bit ---(round to odd)---> 63-bit ---(round to nearest even)----> binary64
64-bit -(round-to-nearest-even,the conversion the compiler wants)-> binary64
Чтобы ответить на другие аспекты вашего вопроса:
Но это не то, что делает код: похоже, он сохраняет бит младшего разряда
%rcx
, а затем возвращает его обратно в результат. Зачем?? И зачем беспокоиться, когда эти бит младшего порядка будут потеряны в любом случае (или я ошибаюсь здесь)?
Это точно, как реализовать в этом конкретном экземпляре round-to-odd. Наименьший значащий бит %rcx
равен единице, если сдвиг не является точным делением на два, и в этом случае результат должен быть выполнен нечетным.
Тот же алгоритм, по-видимому, используется независимо от оптимизации; Я использовал -O3 здесь, чтобы было легче видеть.
Последовательность команд оптимальна (насколько я могу видеть, для современных процессоров) и соответствует преобразованию исходного уровня из uint64_t
int в double
. Это не требует усилий от компилятора, чтобы использовать его даже на самом низком уровне оптимизации. Что может произойти с оптимизацией (но этого не происходит здесь), так это то, что инструкции объединены с другими инструкциями, которые соответствуют другим конструкциям исходного уровня. Но нет смысла иметь другую последовательность команд, чем оптимальную для генерации для преобразований в -O0
.