assembly - Round floating-point numbers in NASM
问题描述
I am trying to build a NASM library for a C program. I would like to round a floating-point number given as parameter.
The C function prototype would look like this:
double nearbyint(double x);
I tried using the frndint
instruction but I need to push my parameter to the stack first.
Here is what I came up with (does not compile):
bits 64
section .text
global nearbyint
nearbyint:
push rbp
mov rbp, rsp
fld xmm0
frndint
fstp xmm0
leave
ret
解决方案
在 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 的rint
作用:它添加2^52
(bit pattern 0x43300000, 0x00000000
),使数字如此之大,以至于最接近的可表示double
s 是整数。因此,正常的 FP 舍入到最接近的可表示值会舍入为整数。 IEEE binary64double
有 52 个显式尾数(又称有效位)位,所以这个数字的大小并非巧合。
(对于负输入,它使用-2^52
)
再次减去它会给你一个原始数字的四舍五入版本。
glibc 实现会检查一些特殊情况(如 Inf 和 NaN),以及小于 0 的指数(即幅度小于 1.0 的输入),它会复制输入的符号位。(我猜-0.499 将舍入为 -0.0 而不是 0,并确保 0.499 将舍入为 +0,而不是 -0。)
用 SSE2 实现它的一种简单方法是用 隔离输入的符号位pand xmm0, [signbit_mask]
,然后在 0x43300000 的 FP 位模式中进行 OR 运算,给你+- 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 ....
特别是如果你省略这些东西,这非常便宜,就 FP 延迟而言SIGNBIT_FIXUP
,并不比 2 微指令贵多少。roundsd
(在大多数 CPU 上与+roundsd
具有相同的延迟。这几乎可以肯定不是巧合,但它确实避免了任何单独的微指令来整理符号)。addsd
subsd
推荐阅读
- graph-algorithm - 使用 TinkerPop3 有效地使用 JUNG
- android - 如何在锁定屏幕处于活动状态时以焦点运行 Activity?
- php - 带有密码的 php 的 gnupg 解密命令
- java - 使用 writer 在 Clojure 中编写 CSV 文件
- hadoop - HiveException:java.lang.RuntimeException:无法实例化 org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient
- python - 命令失败:./distributr.sh -m "kivy" -d "main"
- php - 在 WordPress 循环中的每 4 个帖子上获取
- javascript - 使用 bootstrap4 单击按钮切换列表(ul)弹出框
- xml - 具有继承的元素的 JAXB SCD 路径
- python-3.x - Python Splinter 星级评分