在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
来完全克服类型问题?
答案 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)
)。
在您自己的代码中,您通常知道先验您的参数类型是什么,并且只需要支持一种或两种类型。 clamp
和lerp
特别是图形操作,几乎普遍只用于单精度变体。
顺便提一下,您使用clamp
和lerp
的事实非常好,表明您可能希望在OpenCL而不是C / Obj-C中编写代码; OpenCL数学库为您实现这些操作(以及许多其他类似的操作),并提供适用于各种基本类型的实现,包括向量。
答案 1 :(得分:1)
float
和double
是不同的数据类型,与int
和long int
相同。您可以使用double
对float
值进行操作的函数,并且隐式转换将在大多数情况下使其按预期工作,但如果您使用float
上的double
操作的函数{1}}值,你几乎不可避免地会失去精确度。
还有其他更长的解释,例如: What's the difference between a single precision and double precision floating point operation?。