起因
今日、問題が発生しました。キャラクターがモデルの横に引っかかっており、PVDでモデルを表示するのは正常です。最後の理由は、モデルの三角形の2つの頂点が非常に進んでいるためです。その結果、この非常に小さな差が浮動小数点演算中に破棄されるため、PhysXでは、距離によって移動された位置が0であると判断されるため、動かなくなっただけでジャンプできません。2つの頂点の情報を見てください。
[0] = {x = -4.10000086, y = -0.200000167, z = -3.56512594}
[1] = {x = -4.10000086, y = -0.200000077, z = -3.56512594}
y値にはわずかな違いしかないが、現時点では、浮動小数点数の有効範囲内にある。いくつかの質問をすることはこの問題を研究するのに役立ちます:
- 1.なぜ-0.200000167と-0.200000077の浮動小数点数が異なり、末尾に4.76413503(演算で使用される座標)を追加した結果が同じになるのですか?その理由は何ですか?
- 2.浮動小数点演算のロジックは何ですか?
- 3.浮動小数点数の精度が7ビットなのはなぜですか。
浮動小数点精度
IEEE754表現
テスト中
上記の問題データを抽出して、以下をテストします
float y = 4.76143503;
float y1 = -0.200000167;
float y2 = -0.200000077;
float y1Add = y + y1;
float y2Add = y + y2;
std::cout << std::bitset<32>(*(_ULonglong*)&y) << std::endl;
std::cout << std::bitset<32>(*(_ULonglong*)&y1) << std::endl;
std::cout << std::bitset<32>(*(_ULonglong*)&y1Add) << std::endl;
std::cout << std::bitset<32>(*(_ULonglong*)&y2) << std::endl;
std::cout << std::bitset<32>(*(_ULonglong*)&y2Add) << std::endl;
cout << "fTest = " << y1Add << endl;
cout << "fTest1 = " << y2Add << endl;
結果を見ることができます:
01000000100110000101110110101101
10111110010011001100110011011000
01000000100100011111011101000110
10111110010011001100110011010010
01000000100100011111011101000110
fTest = 4.56143
fTest1 = 4.56143
まとめ:
- y1とy2の浮動小数点表現は確かに異なります。つまり、IEEE754の小数点以下23桁で十分です。
- 加算後、結果は同じになります
次に、問題の原因が何であるかを分析します。
浮動小数点演算
[2]、[3]の主な手順を参照してください。
- 1.正規化された表現
- 2.ペア注文
- 3.仮数スカラー
- 4.正規化
- 5.丸め
IEEE 754 standard floating point Addition Algorithm
Floating-point addition is more complex than multiplication, brief overview of floating point addition algorithm have been explained below
X3 = X1 + X2
X3 = (M1 x 2E1) +/- (M2 x 2E2)
1) X1 and X2 can only be added if the exponents are the same i.e E1=E2.
2) We assume that X1 has the larger absolute value of the 2 numbers. Absolute value of of X1 should be greater than absolute value of X2, else swap the values such that Abs(X1) is greater than Abs(X2).
Abs(X1) > Abs(X2).
3) Initial value of the exponent should be the larger of the 2 numbers, since we know exponent of X1 will be bigger , hence Initial exponent result E3 = E1.
4) Calculate the exponent's difference i.e. Exp_diff = (E1-E2).
5) Left shift the decimal point of mantissa (M2) by the exponent difference. Now the exponents of both X1 and X2 are same.
6) Compute the sum/difference of the mantissas depending on the sign bit S1 and S2.
If signs of X1 and X2 are equal (S1 == S2) then add the mantissas
If signs of X1 and X2 are not equal (S1 != S2) then subtract the mantissas
7) Normalize the resultant mantissa (M3) if needed. (1.m3 format) and the initial exponent result E3=E1 needs to be adjusted according to the normalization of mantissa.
8) If any of the operands is infinity or if (E3>Emax) , overflow has occurred ,the output should be set to infinity. If(E3 < Emin) then it's a underflow and the output should be set to zero.
9) Nan's are not supported.
次に、自分で手動で計算しました。
01000000100110000101110110101101 = 4.76143503
阶码:10000001 = 129 - 127 = 2
尾数:1.00110000101110110101101
--------------------------------------------------
10111110010011001100110011011000 = -0.200000167
阶码:01111100 = 124 - 127 = -3
尾数:1.10011001100110011011000
01000000100100011111011101000110 = 4.56143475 = 4.76143503 + -0.200000167
阶码:10000001 = 129 - 127 = 2
尾数:1.00100011111011101000110
加法运算:
1.1001 1001 1001 1001 1011 000
对齐:小数点左移2-(-3) = 5位
0.00001.1001 1001 1001 1001 1011 000
相减:
1.0011 0000 1011 1011 0101 101
- 1.0000 1100 1100 1100 1100 110 11 000
= 1.0010 0011 1110 1110 1000 111
比较 1.0010 0011 1110 1110 1000 110
--------------------------------------------------
10111110010011001100110011010010 = -0.200000077
阶码:01111100 = 124 - 127 = -3
尾数:1.10011001100110011010010
01000000100100011111011101000110 = 4.56143475 = 4.76143503 + -0.200000077
阶码:10000001 = 129 - 127 = 2
尾数:1.00100011111011101000110
加法运算:
1.10011001100110011010010
对齐:小数点左移2-(-3) = 5位
0.0000110011001100110011010010
相减:计算器去掉小数点和前面的1来的比较快
1.0011 0000 1011 1011 0101 101
- 0.0000 1100 1100 1100 1100 110 10010
= 1.0010 0011 1110 1110 1000 111
比较 1.0010 0011 1110 1110 1000 110
要約:
- 計算の結果は確かに同じですが、それでも最終の4.76143503(最後のビット)とは少し異なります。これは、私の手動計算が、その後の正規化を含め、マシン計算とは少し異なることを示しています。さて、ここに質問を残してください
// 4.76143503 + -0.200000167
1.0010 0011 1110 1110 1000 111
// 4.76143503 + -0.200000077
1.0010 0011 1110 1110 1000 111
// 最终结果的二进制
1.0010 0011 1110 1110 1000 110
- 最初の2つの問題についてここで説明します。最も重要なのは、浮動小数点数の演算中(ここでは加算)です。これは、順序コードが整列しているため、小さい数(ここでは5ビット)の仮数の次の数桁は無視されます。 、-0.200000167と-0.200000077の違いはなくなりました
IEEE754の小数点以下23桁を見てみましょう。なぜ精度が小数点以下7桁なのですか。
浮動小数点精度
まず、自分の問題を特定します。
- 精度は小数点以下の数値を指し、IEEE754標準の23桁は2進数の有効数字を指します
- 私が理解したいのは、23桁の2進数の有効数字が10進数の7桁の有効数字に変換される方法です。
- オンラインで検索した後、最終的に決定できるのは、小数点が6桁から7桁まで正確である可能性があるということです
- 私の最終的な問題はどのように数学的に証明されますか?
最も一般的な説明は次のとおりです。[5] [6]を参照してください。
float型の値はバイナリの最後の23ビットによって決定され、最後の23ビットで表される10進数は最大で2 ^ 23 = 8388608です。これは、バイナリで表現できる正確な23ビットの数値が10進数の最大数は7桁であり、この数値は10進数の7桁であるため、値は重要ではありません。したがって、10進数の浮動小数点の精度は7桁です。簡単に言えば、2進数で表現できる最大の正確な数値は7桁です。
簡単に言えば、2進数で表すことができる8388608は7桁の数値ですが、7桁の有効数字すべてを含めることはできないため、6から7桁の有効数字です。しかし、私はまだ疑いがあります、このアルゴリズムは小数点の前に計算する必要があります、小数点の後の計算方法、そうであれば説明する方法は?
私の理解
[7] [8] [12]を参照した後、相対的に言えば、[8] [12]の説明を優先します。しかし、結局私はそれが別のものであることを理解しました。
- 1.小数点の後の2進数および10進数の変換は、このような式である必要があります。もちろん、0または1を掛けて、有効数字にデータがあることを示します。
次に、小数点の後の最小単位は$ 2 ^ {-23} = $
は、実際には浮動小数点数の一部の値は近似のみが可能であることを示しています
- 2. [8]数学的導出
自分の理解
- (1)小数点以下23桁、表現可能な最小の小数点以下、その他の小数点以下は、この最小単位を乗算して形成されます[12]
最初は、明らかに小数点以下が非常に多いので、なぜ7桁なのでしょうか。
- (2)この最小単位は、精度の仮数、理解方法を表します。
最小単位が0.1の場合、精度は小数点以下1桁になります。0.01、0.02などの0.1(最小単位の整数和)以降の小数を組み合わせることはできないためです。
最小単位が0.000001の場合、精度は小数点以下6になります。数字。0.0000001、0.0000002など、0.000001より小さい単位の小数を組み合わせることはできません。 - (3)正確に言えば、2進数で23桁の有効桁数は、小数点以下6〜7桁です。
以上から、0.00000011920928955078125の最小単位は23ビットのバイナリ(最小単位の倍数)の任意の組み合わせであり、0.00000011920928955078125より大きい数しか取得できないことがわかります。したがって、0.00000001(精度は小数点以下8桁)のような小数を取得することはできませんが、10進数0.000001(精度は小数点以下6桁)を確実に取得できます。ただし、小数点以下7桁の小数点を完全に表すことはできません。以下は、丸めアルゴリズムを無視し、小数点以下の有効数字7桁の計算結果のみを取得します。
0.00000011920928955078125 * 1 = 0.0000001
0.00000011920928955078125 * 2 = 0.0000002
0.00000011920928955078125 * 3 = 0.0000003
0.00000011920928955078125 * 4 = 0.0000004
0.00000011920928955078125 * 5 = 0.0000005
0.00000011920928955078125 * 6 = 0.0000007
0.00000011920928955078125 * 7 = 0.0000008
0.00000011920928955078125 * 8 = 0.0000009
0.00000011920928955078125 * 9 = 0.000001
0.0000006は表現できないことがわかりますが、四捨五入を考えた場合、他の表現が表現できない可能性があり、小数点以下7桁すべてを完全に表現することは不可能です。
まとめ
- 浮動小数点の愛と憎しみ
参考資料
[2] 2 進浮動小数点数の加算と減算
[3] 浮動小数点チュートリアル
[4] プログラマーが知っておくべき浮動小数点演算の原理の詳細な説明
[5] floatの精度が7桁である理由
[6] Java浮動小数点型floatとdoubleの主な違いは、それらの10進精度範囲のサイズは何ですか?
[8] 単精度浮動小数点数の有効桁数が7桁であり、私のカウントが明らかに6桁であるのはなぜですか?
[9] IEEE754単精度浮動小数点がなぜ7桁の精度しかないのですか?
[10] 詳細:IEEE 754乗算および加算
[11] 浮動小数点精度の透析:不正確な10進数計算+浮動小数点精度の損失
[12] float型を単精度と見なす場合、有効桁数は7桁ですが、次の例ではなぜこれらの8桁が正確なのですか?
[13] バイナリ計算機Webバージョン