web-dev-qa-db-ja.com

Cでdoubleの小数部分を*効率的に*抽出します

私はIEEE doubleを取り、可能な最も効率的な方法でその整数部分を削除しようとしています。

が欲しいです

1035 ->0
1045.23->0.23
253e-23=253e-23

デノーマル、無限、またはNaNを適切に処理することは気にしません。私はIEEEのdoubleを使用していることを知っているので、少しいじってもかまわないので、複数のマシンで動作するはずです。

ブランチのないコードがより好ましいでしょう。

私の最初の考えは(疑似コードで)

char exp=d.exponent;
(set the last bit of the exponent to 1)
d<<=exp*(exp>0);
(& mask the last 52 bits of d)
(shift d left until the last bit of the exponent is zero, decrementing exp each time)
d.exponent=exp;

しかし、問題は、指数の最後のビットがゼロになるまでdを左にシフトする効率的な方法を考えられないことです。さらに、最後のビットがすべて設定されていない場合は、ゼロを出力する必要があるようです。これは、2を底とする対数問題に関連しているようです。

このアルゴリズムまたはより優れたアルゴリズムの助けをいただければ幸いです。

ブランチレスコードが必要な理由は、効率的にベクトル化するためです。

21
Jeremy Salwen

簡単なことはどうですか?

double fraction = whole - ((long)whole);

これは、値自体からdoubleの整数部分を減算するだけで、残りは小数部分でなければなりません。もちろん、これには表現上の問題がある可能性があります。

36
Mark Elliot

最適な実装は、ターゲットアーキテクチャに完全に依存します。

最近のIntelプロセッサでは、これはroundsdおよびsubsdの2つの命令で実現できますが、-portable Cコードでは表現できません。

一部のプロセッサでは、これを行う最も速い方法は、浮動小数点表現での整数演算です。初期のAtomおよび多くのARM CPUが頭に浮かびます。

他の一部のプロセッサでは、最も速いのは、整数にキャストしてから戻し、次に減算して分岐して、大きな値を保護することです。

多くの値を処理する場合は、丸めモードをゼロに丸めに設定してから、整数に切り捨てた数値に+/- 2 ^ 52を加算および減算し、元の値から減算して取得できます。分数。 SSE4.1がなくても最新のIntel CPUがあり、ベクトル化したい場合、通常これが最善の方法です。ただし、処理する値が多い場合にのみ意味があります。これは、丸めモードの変更には多少のコストがかかるためです。

他のアーキテクチャでは、他の実装が最適です。一般に、Cプログラムの「効率性」について話しても意味がありません。特定のアーキテクチャでの特定の実装の効率のみ。

12
Stephen Canon
#include <math.h>
double fraction = fmod(d, 1.0);
10
user541686

提案

関数 remainder は剰余を計算しますが、 modf のような整数部分は計算しません:

#include <math.h>

double fracpart(double input)
{
    return remainder(input, 1.);
}

これは、仕事をするために不必要な値を計算しないので、最も効率的な(そして移植可能な)方法です(cf. modf(long)fmodなど)


基準

Mattewがコメントで提案したように、このソリューションをこのページで提供される他のすべてのソリューションと比較するために、いくつかの ベンチマークコード を書きました。

65536回の計算の時間測定値を以下に示します(最適化をオフにしてClangでコンパイル)。

method 1 took 0.002389 seconds (using remainder)
method 2 took 0.000193 seconds (casting to long)
method 3 took 0.000209 seconds (using floor)
method 4 took 0.000257 seconds (using modf)
method 5 took 0.010178 seconds (using fmod)

再びClangでは、今回は-O3 国旗:

method 1 took 0.002222 seconds (using remainder)
method 2 took 0.000000 seconds (casting to long)
method 3 took 0.000000 seconds (using floor)
method 4 took 0.000223 seconds (using modf)
method 5 took 0.010131 seconds (using fmod)

最も単純な解決策は、ほとんどのプラットフォームで最良の結果をもたらすと思われ、そのタスクを実行する特定の方法(fmodmodfremainder)は実際には非常に遅い!

7
Mathieu Rodic

標準ライブラリ関数modfは、この問題を非常にきれいに解決します。

#include <math.h>
/*...*/
double somenumber;
double integralPart;
double fractionalPart = modf(somenumber, &integralPart);

これは、あなたが要求したことを実行し、移植性があり、かなり効率的です。

文書化されていない詳細は、2番目の引数がNULLである可能性があるかどうかであり、それにより、整数部分を一時的に回避します。

残念ながら、多くの実装では2番目の引数にNULLがサポートされていないため、この値を使用するかどうかに関係なく一時的に使用する必要があります。

3
user1555418

Microsoft Visual Studio 2015でC++を使用してプロファイリングと実験を行ったところ、正の数に最適な方法は次のとおりです。

double n;
// ...
double fractional_part = n - floor(n);

これはmodfよりも高速であり、すでに述べたように、剰余関数は最も近い整数に丸められるため、使用されません。

3
Graham Asher