質問

2つのフロートを使用してダブルディベッションを行いたいと思います(ダイレクトコンピューティングはダブルディバイションをサポートしていないようです)。

それは可能ですか?

これは私がこれまでに試したものです(C#コード、後でHLSLになるはずです):

int count = 7;
double value = 0.0073812398871474;
float f1 = (float)value;
float f2 = (float)((value - f1));
float r1 = f1 / count;
float r2 = f2 / count;
double result = (double)r1 + (double)r2;

0,00105446285765182(結果)

0,00105446284102106(正しい結果)

F1の丸めに関係しています。代わりに値がある場合:

 double value = 0.0073812344471474;

その後、結果は正しいです。

役に立ちましたか?

解決

フロート分割でカウントの逆数を計算し、ニュートンラフソンの相互式を使用して精度を完全にダブルに改善します。

int count = 7;
double value = 0.0073812398871474;
double r = (double) (1.0f / count); // approximate reciprocal
r = r * (2.0 - count*r); // much better approximation
r = r * (2.0 - count*r); // should be full double precision by now.
double result = value * r;

他のヒント

どうやらあなたの算術エラーはすぐには明確ではありません。綴りましょう。

ダブルには、大部分と小さな部分が2つの部分があり、それぞれが約32ビットの精度を持っているとします。 (これは正確に2倍の仕組みではありませんが、私たちの目的のために行うでしょう。)

フロートには一部しかありません。

私たちは一度に32ビットをしていたが、すべてをダブルスに保つことを想像してください:

double divisor = whatever;
double dividend = dividendbig + dividendlittle;
double bigquotient = dividendbig / divisor;

大物とは何ですか?ダブルです。したがって、2つの部分があります。 Bigquotientは、BigQuotientBig + BigQuotientlittleに等しくなります。続行:

double littlequotient = dividendlittle / divisor;

繰り返しになりますが、リトルカチエントはLittlequotientbig + Littlequotientlittleです。次に、商を追加します:

double quotient = bigquotient + littlequotient;

それをどのように計算しますか?商には2つの部分があります。 QuotientBigはBigQuotientBigに設定されます。 quotientlittleは、bigquotientlittle + Littlequotientbigに設定されます。 Littlequotientlittleは破棄されます。

今、あなたがフロートでそれをするとします。あなたが持っている:

float f1 = dividendbig;
float f2 = dividendlittle;
float r1 = f1 / divisor;

OK、R1とは何ですか?フロートです。したがって、一部しかありません。 R1はBigQuotientBigです。

float r2 = f2 / divisor;

R2とは何ですか?フロートです。したがって、一部しかありません。 R2はLittlequotientBigです。

double result = (double)r1 + (double)r2;

それらを一緒に追加すると、BigQuotientBig + LittleQuotientBigが得られます。 BigQuotientlittleはどうなりましたか? あなたはそこで32ビットの精度を失ったので、途中で32ビットを取得することは驚くことではありません。 32ビットで64ビットの算術を近似するために、すべての適切なアルゴリズムを思いついていません。

計算するため (big + little)/divisor, 、あなたは単にすることはできません (big / divisor) + (little / divisor). 。代数の規則は、あなたがいるときには適用されません 丸め その間 毎日 分割!

それは今明確ですか?

それは可能ですか?

はい、あなたがいる限り:

  • 避けられない精度の損失を受け入れます
  • そもそもすべてのダブルスがフロートに収まるわけではないことに注意してください

アップデート

コメントを読んだ後(二重精度は要件です)、私の更新された答えは次のとおりです。

いいえ。

それで、どんなものはどうですか

result = value * (double)(1f / (float)count); ?

そこでは、2つのフロートしか分割されていません。私はそこに必要以上のキャストを持っていますが、それは重要な概念です。

編集:
さて、あなたは実際のものと丸いものの違いを心配していますよね?だから、あなたがそれを正しくするまで何度もそれをしてください!

double result = 0;
double difference = value;
double total = 0;
float f1 = 0;
while (difference != 0)
{
    f1 = (float)difference;
    total += f1;
    difference = value - total;
    result += (double)(f1 / count);
}

...しかし、ご存知のように、簡単な答えはまだ「いいえ」です。これはまだすべての丸めエラーをキャッチすることさえありません。私のテストから、それは最大で約30%の時間で、不正確さを最大で1E-17に低下させます。

コメントで、あなたは言います:

もちろん、精度の損失はないはずです。これが私が2つのフロートを使用している理由です。精度の損失を受け入れると、2つのフロートをキャストして部門を行うことができました。

IEEE-754 single precision 値には24の重要なバイナリ桁があります。 a double precision 値には53桁の数字があります。精度を失うことなく2つの単一精度値として二重精度値を表すことさえできません。

そうは言っても、そうです 可能 ダブルとシングルの二重精度の減算/追加の間の変換のみを使用して、正しく丸い二重精度分割を行うには、単一の精度操作を行いますが、本当に正しくやりたい場合はかなり複雑です。実際のIEEE-754正しい丸めが必要ですか、それとも最後のビットまで正しい答えが必要ですか?

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top