round()for C ++中的float

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了round()for C ++中的float相关的知识,希望对你有一定的参考价值。

我需要一个简单的浮点舍入函数,因此:

double round(double);

round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1

我可以在math.h中找到ceil()floor() - 但不是round()

它是以另一个名称出现在标准C ++库中,还是缺少?

答案

C ++ 98标准库中没有round()。你可以自己写一个。以下是round-half-up的实现:

double round(double d)
{
  return floor(d + 0.5);
}

C ++ 98标准库中没有循环函数的可能原因是它实际上可以以不同的方式实现。以上是一种常见的方式,但还有其他如round-to-even,如果你要进行大量的四舍五入,它会有较少的偏见并且通常会更好;但实施起来有点复杂。

另一答案

如果你最终想要将你的double函数的round()输出转换为int,那么这个问题的公认解决方案将类似于:

int roundint(double r) {
  return (int)((r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5));
}

当以均匀随机值传递时,我的机器上的时钟大约为8.88 ns。

据我所知,以下在功能上是等效的,但在我的机器上的时钟频率为2.48 ns,具有显着的性能优势:

int roundint (double r) {
  int tmp = static_cast<int> (r);
  tmp += (r-tmp>=.5) - (r-tmp<=-.5);
  return tmp;
}

性能更好的原因之一是跳过分支。

另一答案

小心floor(x+0.5)。以下是范围[2 ^ 52,2 ^ 53]中奇数的情况:

-bash-3.2$ cat >test-round.c <<END

#include <math.h>
#include <stdio.h>

int main() {
    double x=5000000000000001.0;
    double y=round(x);
    double z=floor(x+0.5);
    printf("      x     =%f
",x);
    printf("round(x)    =%f
",y);
    printf("floor(x+0.5)=%f
",z);
    return 0;
}
END

-bash-3.2$ gcc test-round.c
-bash-3.2$ ./a.out
      x     =5000000000000001.000000
round(x)    =5000000000000001.000000
floor(x+0.5)=5000000000000002.000000

这是http://bugs.squeak.org/view.php?id=7134。使用类似@konik的解决方案。

我自己的强大版本将是这样的:

double round(double x)
{
    double truncated,roundedFraction;
    double fraction = modf(x, &truncated);
    modf(2.0*fraction, &roundedFraction);
    return truncated + roundedFraction;
}

避免楼层(x + 0.5)的另一个原因是here

另一答案

这些天使用包含C99 / C ++ 11数学库的C ++ 11编译器应该不是问题。但问题就变成了:你选择哪种舍入函数?

C99 / C ++ 11 round()实际上通常不是你想要的舍入函数。它采用了一种时髦的舍入模式,在中途情况下(0)作为中间案例(+-xxx.5000)的抢七局。如果你特别想要那种舍入模式,或者你的目标是round()rint()更快的C ++实现,那么就使用它(或者用这个问题的其他答案之一来模仿它的行为,这个答案是从表面上看并仔细转载的特定的舍入行为。)

round()的舍入与IEEE754默认的round to nearest mode with even as a tie-break不同。最近 - 甚至避免了数字平均值的统计偏差,但是偏向偶数。

有两个数学库舍入函数使用当前的默认舍入模式:std::nearbyint()std::rint(),两者都在C99 / C ++ 11中添加,所以它们在std::round()的任何时候都可用。唯一的区别是nearbyint永远不会引发FE_INEXACT。

由于性能原因更喜欢rint():gcc和clang都更容易内联,但是gcc从不内联nearbyint()(即使使用-ffast-math


gcc/clang for x86-64 and AArch64

I put some test functions on Matt Godbolt's Compiler Explorer,你可以看到source + asm输出(对于多个编译器)。有关读取编译器输出的更多信息,请参阅this Q&A和Matt的CppCon2017谈话:“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”

在FP代码中,内联小函数通常是一个很大的胜利。特别是在非Windows上,标准调用约定没有调用保留的寄存器,因此编译器不能在call中保留XMM寄存器中的任何FP值。所以即使你真的不知道asm,你仍然可以很容易地看到它是否只是对库函数的尾调用,或者它是否内联到一个或两个数学指令。内联到一个或两个指令的任何内容都优于函数调用(对于x86或ARM上的此特定任务)。

在x86上,任何内联到SSE4.1 roundsd的东西都可以用SSE4.1 roundpd(或AVX vroundpd)自动矢量化。 (FP->整数转换也以压缩SIMD形式提供,但FP-> 64位整数除外,它需要AVX512。)

  • std::nearbyint(): x86 clang:使用-msse4.1内联到单个insn。 x86 gcc:仅使用-msse4.1 -ffast-math内联到单个insn,并且仅在gcc 5.4和更早版本上。后来gcc从未内联它(也许他们没有意识到其中一个直接位可以抑制不精确的异常?这就是clang使用的,但是当它内联时,较旧的gcc使用与rint相同的立即数) AArch64 gcc6.3:默认情况下内联到单个insn。
  • std::rint: x86 clang:使用-msse4.1内联到单个insn x86 gcc7:使用-msse4.1内联到单个insn。 (没有SSE4.1,内联几条指令) x86 gcc6.x及更早版本:使用-ffast-math -msse4.1内联到单个insn。 AArch64 gcc:默认情况下内联到单个insn
  • std::round: x86 clang:不内联 x86 gcc:使用-ffast-math -msse4.1内联多个指令,需要两个向量常量。 AArch64 gcc:内联到单个指令(硬件支持此舍入模式以及IEEE默认和大多数其他指令。)
  • std::floor / std::ceil / std::trunc x86 clang:使用-msse4.1内联到单个insn x86 gcc7.x:使用-msse4.1内联到单个insn x86 gcc6.x及更早版本:使用-ffast-math -msse4.1内联到单个insn AArch64 gcc:默认情况下内联到单个指令

Rounding to int / long / long long:

这里有两个选项:使用lrint(如rint但返回longlong long用于llrint),或使用FP-> FP舍入函数,然后以正常方式转换为整数类型(使用截断)。有些编译器比其他编译器优化一种方式。

long l = lrint(x);

int  i = (int)rint(x);

请注意,int i = lrint(x)首先转换floatdouble - > long,然后将整数截断为int。这对于超出范围的整数有所不同:C ++中的未定义行为,但是对于x86 FP - > int指令(编译器将发出的除非在编译时在进行常量传播时看到UB,然后它是允许编写代码,如果它被执行则会中断)。

在x86上,溢出整数的FP->整数转换产生INT_MINLLONG_MIN0x8000000的位模式或64位等效,只有符号位设置)。英特尔将此称为“整数无限期”值。 (参见html" rel="nofollow" target="_b

以上是关于round()for C ++中的float的主要内容,如果未能解决你的问题,请参考以下文章

C语言中的for循环

通过编写C dll来加速C#中的数学代码?

C语言里面float数据用printf(“%d”)输出的问题

编写并将float数组附加到C ++中hdf5文件中的唯一数据集

C中的重复符号错误

急求 程序代码 c/c++ 操作系统中的 处理机调度算法