Круглые числа с плавающей точкой в ​​NASM - PullRequest
2 голосов
/ 09 марта 2019

Я пытаюсь создать библиотеку NASM для программы на Си.Я хотел бы округлить число с плавающей точкой, указанное в качестве параметра.

Прототип функции C будет выглядеть так:

double nearbyint(double x);

Я пытался использовать инструкцию frndint, но мне нужносначала поместите мой параметр в стек.

Вот что я придумал (не компилирует):

bits 64

section .text

global nearbyint

nearbyint:
    push    rbp
    mov     rbp, rsp

    fld     xmm0
    frndint
    fstp    xmm0

    leave
    ret

1 Ответ

2 голосов
/ 09 марта 2019

Единственный способ получить данные между x87 и XMM - перенаправить их в память.например, movsd [rsp-8] / fld qword [rsp-8] для использования красной зоны.

Но вам вообще не нужно использовать x87, и не стоит, если вы хотите, чтобы он был эффективным.

Если у вас SSE4.1, используйте roundsd для округления до целого числа.

  • rint: roundsd xmm0,xmm0, 0b0100 - текущий режим округления (бит 2 =1) , флаг неточного исключения устанавливается, если результат! = Вход (бит 3 = 0).
  • nearbyint: roundsd xmm0,xmm0, 0b1100 текущий режим округления (бит 2 = 1), неточное исключение исключено(бит 3 = 1).
  • roundsd xmm0,xmm0, 0b1000: переопределение режима округления (бит 2 = 0) до _MM_FROUND_TO_NEAREST_INT (биты [1: 0] = 00) .См. roundpd документы для таблицы.Неточное исключение исключено (бит 3 = 1).

Без SSE4.1 для roundsd , посмотрите, что glibc's rintделает: добавляет 2^52 (битовая комбинация 0x43300000, 0x00000000), делая число настолько большим, что ближайшие представимые double с являются целыми числами.Таким образом, обычное округление FP до ближайшего представимого значения округляется до целого числа. IEEE binary64 double имеет 52 явных битов мантиссы (иначе значимых), поэтому размер этого числа равен , а не .

(Для отрицательных входных данных этоиспользует -2^52)

Вычитание, которое снова дает округленную версию вашего исходного числа.

Реализация glibc проверяет для некоторых особых случаев (таких как Inf и NaN) и для показателей меньше, чем0 (т. Е. Входы с магнитудой меньше 1,0) копирует в знаковый бит ввода.(Я полагаю, что -0,499 округляется до -0,0 вместо 0, и чтобы убедиться, что 0,499 будет округлять до +0, а не -0.)

Простой способ реализовать это с помощью SSE2 - изолироватьзнаковый бит ввода с pand xmm0, [signbit_mask], затем ИЛИ в битовой комбинации FP 0x43300000 ..., что дает вам +- 2^52.

default rel

;; UNTESTED.  IDK if the SIGNBIT_FIXUP does anything other than +-0.0
rint_sse2:
    ;movsd  xmm1, [signbit_mask]  ; can be cheaply constructed on the fly, unlike 2^52
    ;pand   xmm1, xmm0

    pcmpeqd  xmm1, xmm1
    psrlq    xmm1, 1             ; 0x7FFF...
%ifdef SIGNBIT_FIXUP
    movaps   xmm2, xmm1          ; copy the mask
%endif

    andnps   xmm1, xmm0          ; isolate sign bit

%ifdef SIGNBIT_FIXUP
    movaps   xmm3, xmm1          ; save another copy of the sign bit
%endif

    orps     xmm1, [big_double]  ; +-2^52
    addsd    xmm0, xmm1
    subsd    xmm0, xmm1

%ifdef SIGNBIT_FIXUP
    andps    xmm0, xmm2          ; clear the sign bit
    orps     xmm0, xmm3          ; apply the original sign
%endif
    ret

section .rodata
align 16
   big_double: dq 0x4330000000000000   ; 2^52
   ; a 16-byte load will grab whatever happens to be next
   ; if you want a packed rint(__m128d), use   times 2 dq ....

Особенно, если вы пропустите SIGNBIT_FIXUP, этодовольно дешевый, не намного дороже, чем roundsd 2 моп с точки зрения задержки FP.(roundsd имеет то же время ожидания, что и addsd + subsd на большинстве процессоров. Это почти наверняка не совпадение, но оно позволяет избежать отдельных мопов для сортировки знака).

...