1

是否有 x86 内联 asm 解决方案可以采用双倍,乘以 100.00,然后转换为整数。“输入”双精度实际上是一个价格,我想将“美分”转换为整数。

可以做出的假设。

  • 双精度值不会是 NaN、Infinity 或有符号零。
  • 双重将是积极的
  • 转换可能需要一些舍入。例如:8.19999 应该变成 820 作为整数。
  • SSE4 指令可用
  • 数据串行到达
  • GCC >=4.7 是首选编译器。

换一种说法,当使用 gcc 4.7.x 并使用 -O3、--fast-math 进行编译时,是否有比这种类型的代码更好的 x86 asm 方法?

#include <math.h>
int cents = llround(price*100.0);
4

1 回答 1

1

我将继续编写一个转换函数,假设输入小于 2^52/100:

#include <string.h>
#include <stdio.h>

/*@ requires 0 <= d < 0x1.0p52 ; */
long long cents(double d)
{
  d = d * 100. + 0x1.0p52;
  long long l;
  memcpy(&l, &d, sizeof(double));
  return l & 0xFFFFFFFFFFFFF;
}

int main()
{
  printf("%lld\n", cents(0.994));
  printf("%lld\n", cents(0.996));
  printf("%lld\n", cents(123456789.004));
  printf("%lld\n", cents(123456789.006));
}

预期结果是:

99
100
12345678900
12345678901

gcc -O2将我的函数的计算部分编译cents()为:

mulsd   LCPI1_0(%rip), %xmm0
addsd   LCPI1_1(%rip), %xmm0
movd    %xmm0, %rcx
movabsq $4503599627370495, %rax
andq    %rcx, %rax

您可能想要内联它或告诉您的编译器内联它。这可能会或可能不会比llround()取决于您的处理器更快。

如果您有可用的融合乘加指令,则d * 100. + 0x1.0p52可以在一条指令中计算,但无论如何都要花费加载常量。如果您必须在循环中执行其中许多操作,请将常量保留在寄存器中(或告诉编译器它可以这样做)。


另一种方法是添加0x1.fffffffffffffp-2double紧接在下面0.5)并截断为long long

long long cents(double d) { return d * 100. + 0x1.fffffffffffffp-2; }

使用0x1.fffffffffffffp-2而不是的基本原理0.5是,它在所有有整数的情况下都会为您提供最接近的整数。相比之下0.5,在某些情况下,加法会为您提供两个最接近的整数中最远的一个(详细信息,在这篇文章float中使用 type而不是)。作为交换,您必须放弃关系 ( , , ...) 从零四舍五入的属性:通过使用它们被四舍五入。double0.1250.6250x1.fffffffffffffp-2

你知道为什么我的例子来说明关系是0.125而不是0.005,不是吗?如果没有,请不要介意。

于 2013-06-01T17:23:35.070 回答