在什么情况下我们需要double,float和long double的函数?

时间:2013-12-10 10:36:30

标签: objective-c c

在math-headers中我们看到

extern float fabsf(float);
extern double fabs(double);
extern long double fabsl(long double);

...

extern float fmodf(float, float);
extern double fmod(double, double);
extern long double fmodl(long double, long double);

为什么每种类型都有一个功能? 这不是很多重复的代码吗?如果我在哪里写一个lerp函数或一个钳位函数,我需要为每种类型写一个吗?

似乎我们将有重复的代码,其中只有一件事在改变 - 类型。

extern float clampf(float value, float min, float max)
{
    if(value > max)
        return max;
    if(value < min)
        return min;
    return value;
}

extern double clamp(double value, double min, double max)
{
    if(value > max)
        return max;
    if(value < min)
        return min;
    return value;
}

问题1:这种结构的历史原因是什么?

问题2:我应该遵循相同的模式吗?或者我应该只实现double种,因为它是最常见的那种?

问题3:或者我应该只使用macro来完全克服类型问题?

2 个答案:

答案 0 :(得分:5)

历史上(大约C89及之前),数学库只包含这些函数的双精度版本,这就是为什么这些版本没有后缀的原因。如果您需要计算float的正弦值,您可以编写自己的实现,或者(更可能!)您只需编写:

float x;
float y = sin(x);

然而,这引入了现代架构的一些开销。具体来说,在当今最常见的体系结构中,编译器必须发出如下所示的代码:

convert x to double
call sin
convert result to float

这些转换非常快(通常与添加相同),但它们仍有一些成本。除了转换成本之外,sin需要提供具有~53位精度的结果,如果结果将被转换回单精度,则其中一半以上被完全浪费。在这两个因素之间,专用的单精度sin例程可能快两倍;这是一些非常常用的库函数的重大胜利!

如果我们查看像fabs这样的函数(并假设编译器不是简单地内联并降低它们),情况会更糟糕。 fabs,在典型的现代架构中,是一个简单的按位和操作。因此,将呼叫限制在一起的两次转换(如果你只有double)比操作本身要贵得多,并且很容易造成5倍的减速。这就是为什么添加这些函数的多个版本以支持每种FP类型的原因。

如果你不想跟踪所有这些,你可以#include <tgmath.h>,它会根据参数的类型推断出正确使用的函数(意思是

sin((float)x)

将生成对sinf(x)的调用,而

sin((long double)x)

将致电sinl(x))。

在您自己的代码中,您通常知道先验您的参数类型是什么,并且只需要支持一种或两种类型。 clamplerp特别是图形操作,几乎普遍只用于单精度变体。

顺便提一下,您使用clamplerp的事实非常好,表明您可能希望在OpenCL而不是C / Obj-C中编写代码; OpenCL数学库为您实现这些操作(以及许多其他类似的操作),并提供适用于各种基本类型的实现,包括向量。

答案 1 :(得分:1)

floatdouble是不同的数据类型,与intlong int相同。您可以使用doublefloat值进行操作的函数,并且隐式转换将在大多数情况下使其按预期工作,但如果您使用float上的double操作的函数{1}}值,你几乎不可避免地会失去精确度。

还有其他更长的解释,例如: What's the difference between a single precision and double precision floating point operation?