我正在用C开发一个专用数学函数库。我需要为库提供一个处理单精度和双精度的功能。这里重要的一点是“单个”函数应该在内部使用“单个”算术(分别用于“双重”函数)。
作为示例,请查看LAPACK(Fortran),它提供了每个函数的两个版本(SINGLE和DOUBLE)。还有C数学库(例如, expf 和 exp )。
为了澄清,我想支持类似于以下(人为的)示例的内容:
float MyFloatFunc(float x) {
return expf(-2.0f * x)*logf(2.75f*x);
}
double MyDoubleFunc(double x) {
return exp(-2.0 * x)*log(2.75*x);
}
我考虑过以下方法:
将宏用于函数名称。这仍然需要两个独立的源代码库:
#ifdef USE_FLOAT
#define MYFUNC MyFloatFunc
#else
#define MYFUNC MyDoubleFunc
#endif
将宏用于浮点类型。这允许我在两个不同版本之间共享代码库:
#ifdef USE_FLOAT
#define NUMBER float
#else
#define NUMBER double
#endif
刚开发两个独立的库,忘记尝试保存头痛。
有人有推荐或其他建议吗?
答案 0 :(得分:7)
对于多项式近似,插值和其他固有近似数学函数,您不能在双精度和单精度实现之间共享代码,而不会在单精度版本中浪费时间或在双精度版本中更加近似-precision one。
然而,如果你走单一代码库的路线,下面的代码应该适用于常量和标准库函数:
#ifdef USE_FLOAT
#define C(x) x##f
#else
#define C(x) x
#endif
... C(2.0) ... C(sin) ...
答案 1 :(得分:5)
(部分灵感来自Pascal Cuoq的回答)
如果你想要一个具有float和双重版本的库的库,你可以将递归#include
与宏结合使用。它不会产生最清晰的代码,但它确实允许您对两个版本使用相同的代码,并且模糊处理足够薄,它可能是可管理的:
<强> mylib.h:强>
#ifndef MYLIB_H_GUARD
#ifdef MYLIB_H_PASS2
#define MYLIB_H_GUARD 1
#undef C
#undef FLT
#define C(X) X
#define FLT double
#else
/* any #include's needed in the header go here */
#undef C
#undef FLT
#define C(X) X##f
#define FLT float
#endif
/* All the dual-version stuff goes here */
FLT C(MyFunc)(FLT x);
#ifndef MYLIB_H_PASS2
/* prepare 2nd pass (for 'double' version) */
#define MYLIB_H_PASS2 1
#include "mylib.h"
#endif
#endif /* guard */
<强> mylib.c:强>
#ifdef MYLIB_C_PASS2
#undef C
#undef FLT
#define C(X) X
#define FLT double
#else
#include "mylib.h"
/* other #include's */
#undef C
#undef FLT
#define C(X) X##f
#define FLT float
#endif
/* All the dual-version stuff goes here */
FLT C(MyFunc)(FLT x)
{
return C(exp)(C(-2.0) * x) * C(log)(C(2.75) * x);
}
#ifndef MYLIB_C_PASS2
/* prepare 2nd pass (for 'double' version) */
#define MYLIB_C_PASS2 1
#include "mylib.c"
#endif
每个文件#include
本身在第二遍中使用不同的宏定义一次,以生成使用宏的代码的两个版本。
但有些人可能会反对这种做法。
答案 2 :(得分:2)
最重要的问题是:
如果您有建议的通用编码,则必须以高跷的方式编写代码,小心不要编写任何未修饰的常量或非宏函数调用(或函数体)。
如果您有单独的源代码树,代码将更容易维护,因为每个树看起来都像普通(非混淆)C代码,但如果'浮动'版本中的YourFunctionA中存在错误,则会你总是记得在'双'版本中进行匹配的改变。
我认为这取决于功能的复杂性和波动性。我的怀疑是,一旦第一次编写和调试,很少需要回到它。这实际上意味着你使用哪种机制并不重要 - 两者都可行。如果函数体有些不稳定,或者函数列表是易失性的,则单个代码库可能更容易整体。如果一切都非常稳定,那么两个独立代码库的清晰度可能会更加优越。但这是非常主观的。
我可能会使用单个代码库和从墙到墙的宏。但我不确定那是最好的,另一种方式也有它的优点。
答案 3 :(得分:1)
&lt; tgmath.h&gt;标题,在C 1999中标准化,提供对&lt; math.h&gt;中的例程的类型泛型调用。和&lt; complex.h&gt;。在包含&lt; tgmath.h&gt;之后,如果x是long double,源文本“sin(x)”将调用sinl,如果x是double,则调用sin,如果x是float,则调用sinf。
您仍需要对常量进行条件化,以便根据需要使用“3.1”或“3.1f”。根据您的需要以及对您来说更美观的方法,有各种各样的句法技巧。对于以float精度表示的常量,您可以简单地使用float表单。例如,如果x是double,“y = .5f * x”将自动将.5f转换为.5。然而,“罪(.5f)”将产生sinf(.5f),这不如罪(.5)准确。
您可以将条件化简化为一个明确的定义:
#if defined USE_FLOAT
typedef float Float;
#else
typedef double Float;
#endif
然后你可以用这样的方式使用常量:
const Float pi = 3.14159265358979323846233;
Float y = sin(pi*x);
Float z = (Float) 2.71828182844 * x;
这可能不完全令人满意,因为在极少数情况下,数字转换为double然后浮动不如直接转换为float的数字准确。因此,使用上面描述的宏可能会更好,其中“C(数字)”会在必要时在数字后附加一个后缀。