2011-10-27 20 views
8

私はC言語で特殊目的の数学関数のライブラリを開発しています。ライブラリが単精度と倍精度の両方を扱えるようにする必要があります。ここでの重要な点は、 "single"関数は内部的に "single"算術のみを使うべきであるということです( "double"関数の場合)。単精度浮動小数点と倍精度浮動小数点の両方を扱うCコードの適切な設計?

図のように、それぞれの機能(SINGLEとDOUBLE)の2つのバージョンを提供するLAPACK(Fortran)を見てください。また、C数学ライブラリ(例:expfexp)。明確にするために

、私は以下の(不自然)の例のように何かサポートしたい:私は以下のアプローチについて考えてきました

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); 
} 

を:

関数名用のマクロを使用して
  1. 。これには依然として、2つの別々のソースコードベースが必要です。

    #ifdef USE_FLOAT 
    #define MYFUNC MyFloatFunc 
    #else 
    #define MYFUNC MyDoubleFunc 
    #endif 
    
  2. 浮動小数点型のマクロを使用します。

    #ifdef USE_FLOAT 
    #define NUMBER float 
    #else 
    #define NUMBER double 
    #endif 
    
  3. わずか2つの別々のライブラリを開発し、頭痛を保存しようとして忘れ:これは私は2つの異なるバージョンは全体のコードベースを共有することができます。

推奨または推奨事項はありますか?

答えて

7

多項式近似、補間、その他の本質的に近似的な数学関数の場合、単精度バージョンで時間を無駄にすることなく、または単精度バージョンで必要以上に近似することなく、コードを倍精度単精度実装と共有することはできません。倍精度のもの。

あなたは、単一のコードベースのルートを行けばそれにもかかわらず、次は定数と標準ライブラリ関数のために働く必要があります。

#ifdef USE_FLOAT 
#define C(x) x##f 
#else 
#define C(x) x 
#endif 

... C(2.0) ... C(sin) ... 
+0

はい、この優秀な点に感謝します。ここでの目標は、 "single"の実行速度を "double"の精度上の利点とトレードオフすることです。 –

2

あなたがなるための大きな疑問:

  • がそれです2つの別個の不鮮明なソースツリー、または難読化された1つのソースツリーを維持するのが簡単です。

提案されている共通のコーディングをお持ちの場合は、未定義の定数やマクロ以外の関数呼び出し(または関数本体)を記述しないように非常に注意してください。

ソースコードツリーが別々の場合は、各ツリーが通常の(難読化されていない)Cコードのように見えるという点で、コードの管理は簡単ですが、 'float'バージョンのYourFunctionAにバグがある場合、あなたはいつも 'ダブル'バージョンで一致する変更を行うことを忘れないでください。

私はこれが機能の複雑さと揮発性に依存していると思います。私の疑惑は、一度書き込まれて初めてデバッグされた場合、それに戻っていく必要はほとんどありません。これは、実際にどのメカニズムを使用するかは重要ではないことを意味します。どちらも実行可能です。関数本体がやや揮発性であるか、または関数のリストがvolatileである場合、単一のコードベースは全体的に簡単になります。すべてが非常に安定していれば、2つの別々のコードベースの明快さが望ましいかもしれません。しかし、それは非常に主観的です。

私はおそらく、単一のコードベースとwall-to-wallマクロを使用します。しかし、私はそれが最善であるとは確信していませんし、他の方法も利点があります。

5

(部分的にはPascal Cuoqの答えに触発されています) floatとdoubleバージョンのライブラリが1つあれば、再帰的に#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 S自体一つの追加の時間、2つのバージョンを生成するために、2回目のパスで異なるマクロ定義を使用してマクロを使用するコード。

しかし、このアプローチには異議を唱える人もいます。

+0

このアプローチを実装に使用することがあります。私は決してヘッダーのためにそれを使用しません。ユーザーはライブラリを使いたいときにこのような実装の詳細を見る必要はありません。また、すべてのマクロ定義をインクルードファイルに移動します。 –

+0

非常に怖い - 非常に興味深い。ヘッダーファイルが#includeできることは決して知りませんでした。このトリックがCプリプロセッサをTuring-complete(C++のテンプレートのようなもの)にするのだろうか? –

1

C 1999年に標準化< > tgmath.hヘッダは、<のmath.h >と<のcomplex.h >内のルーチンへ種類汎用の呼び出しを提供します。 <tgmath.h>をインクルードした後、xがlong doubleの場合はsin(x)、xがdoubleの場合はsin、xがfloatの場合はsinfが呼び出されます。

"3.1"または "3.1f"を適切に使用するためには、定数を条件付きで調整する必要があります。あなたのニーズやより美的に見えるものに応じて、さまざまな統語テクニックがあります。 float精度で正確に表現された定数については、単にfloat形式を使用することができます。たとえば、「y = .5f * x」は、xがdoubleの場合、.5fを.5に自動的に変換します。しかし、 "sin(0.5f)"はsinf(0.5f)を生成しますが、これはsin(0.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; 

理由が完全に満足されない可能性があることまれに二重に変換されてから浮動小数点に変換された数値が、直接浮動小数点に変換された数値よりも正確でない場合があります。したがって、上記のマクロでは、C(数字)が必要に応じて数字に接尾辞を付加する方がよい場合があります。

+0

tgmath.hへのポインタありがとう。それが存在することを知ってうれしいですが、タイプ情報を使ってどの関数を呼び出すかを選択しますか?そのことがどうしてC99に入ったのですか? –

関連する問題