左から右への結合と右から左への結合の定義について混乱しています。また、左結合性および右結合性と呼ばれるものを見てきましたが、どちらがどれに対応するのか知りたいです。
A = x * y * zがa = x *(y * z)を意味するかa =(x * y)* zを意味するかなど、同じ優先順位の操作が実行される順序に関連していることを知っています。どれが左から右への連想性で、どれが右から左への連想性かわかりません。
私はそれをグーグルで試しましたが、見つけることができたのは、さまざまな演算子の結合性がc ++にあるものの表です。すべての例を見ると、混乱してしまいました。
さらに私を混乱させるのは、それです:
glm::vec4 transformedVector = translationMatrix * rotationMatrix * scaleMatrix * originalVector;
最初にスケーリング行列乗算を実行し、次に回転行列とそれに続く変換を実行します。この例では、マトリックスはすべてglm :: mat4型であり、ベクトルはglm :: vec4型です。この左から右への連想性ですか、それとも右から左への連想性ですか?これは通常の乗算と同じですか、GLMタイプの乗算は異なりますか?
通常、左から右に読みます。通常、左から右に数学を行います。これは左から右への結合性であり、最も一般的です。
ほとんどの人が解決します
_x = 23 + 34 + 45
_
それをグループ化することにより
_x = (23 + 34) + 45
_
これは左から右への結合性です。左から右に読んで数学をするので、それを覚えることができます。
数学のの加算については、それほど重要ではありません。どちらの方法でも常に同じ結果が得られます。これは、加算がassociativeであるためです。操作が結合的であると言うことは、左から右への関連付けと右から左への関連付けが同じことを意味します。プログラミングの追加では、オーバーフローと浮動小数点演算のために依然として問題になります(ただし、妥当な言語の通常サイズの整数では問題になりません)。 _a+b
_および_b+a
_を多数使用し、フリッパートを使用する場合、追加が行われた順序を覚えておいてください。
あなたの例では:
_glm::vec4 transformedVector = translationMatrix * rotationMatrix * scaleMatrix * originalVector
_
あなたは概念的に最初に右側からくすくすと動きます。ただし、C++では、_*
_は通常左から右への結合であり、これをオーバーライドすることはできません。 glmはこれをさまざまな方法で処理できます。最終ベクトルが到着するのを待って乗算するもののキャッシュを構築できますthen右から左への乗算を行います。また、(より可能性が高い)代数の定理を使用して、行列の乗算は完全に連想的であり、左から右に乗算するだけで、ドキュメントで読者が右から左と考えるのと同じであることを保証できます。ただし、前述のように実装を理解する必要があります実装が浮動小数点数を乗算する方法を選択することが重要です 。
完全を期すために、減算を検討してください。 _a - b - c
_とは何ですか?ここでは、それが実際にdoesであるかどうかは、それが左か右かを関連付けます。もちろん、数学ではb _(a - b) - c
_に定義しますが、一部の奇妙なプログラミング言語では、減算を正しい連想方式にすることを好み、常に_a - (b - c)
を意味する_a - b - c
_を使用します。この外国語には、_-
_が右結合であることを指定するドキュメントページがあります。これは、操作仕様の一部であり、オペレーターの使用を見ただけではわからないためです。
次の言葉からそれを見ることができます:
演算子を組み合わせて式を作成する場合、演算子が適用される順序は明らかではない場合があります。たとえば、a + b + cは、((a + b)+ c)または(a +(b + c))と解釈できます。オペランドが((a + b)+ c)のように左から右にグループ化される場合、+は左結合と言います。 (a +(b + c))のように、反対方向にオペランドをグループ化すると、右結合であると言います。
A.V.アホ&J.D.ウルマン1977、p。 47
私が見つけた最も単純で最も非tl; drの答え:
ほとんどのプログラミング言語では、加算、減算、乗算、および除算演算子はleft-associativeであり、割り当て、条件、および累乗演算子は右結合。
a = (x * y) * z
は左から右、a = x * (y * z)
は右から左です。
glmの行列乗算は、*
演算子をオーバーロードするため、左から右に関連付けられます。ここでの問題は、数学的結合性ではなく、幾何学的変換に関するマトリックス乗算の意味についてです。
明示的な括弧なしでこの演算子(式タイプ)をネストして使用する場合、暗黙的な括弧が左側に配置される場合、中置演算子(またはより一般的に閉じられていない左および右の部分式を持つ式タイプ)は左結合です。 _*
_はC++では左結合であるため、_a*b*c
_は_(a*b)*c
_を意味します。より深いネストの場合、暗黙的な括弧のクラスターが左端に発生します:_(((a*b)*c)*d)*e
_。
同等に、これは、この演算子の構文生成規則が左再帰的であることを意味します(つまり、左の部分式はこの規則が生成されるものと同じ構文カテゴリを持っているため、同じ規則(同じ演算子)を直接使用できます)その部分式を形成します;反対側の部分式はより制限された構文カテゴリを持ち、同じ演算子を使用するには明示的な括弧が必要です)。 C++では、 multiplicative-expression (標準のセクション5.6)の1つの生成は、 mutliplicative-expression _*
_ pm-expression (左側に multiplicative-expression を含む)。
そのため、明示的な括弧なしのネストされた使用では、左端の演算子はそのすぐ隣をオペランドとして使用し、他のインスタンスは左のオペランドとして、左のすべてによって形成される式(の結果)を使用します。
私は認めます、私はこれを少し押し続けてきました(遠すぎます)。私のポイントは、「正しい」という言葉は上記のどこにも発生せず、運動も含まれていないということです。結合性は構文であり、したがって静的な問題です。重要なのは、 where 暗黙の括弧がどこに書かれているかではなく、実際に書かれていないことです(実際、まったく書かれていないか、明示的です)。もちろん、右結合の場合は、それぞれの「左」を「右」に置き換えるだけです。
結論として、私はこの左から右への結合(またはグループ化)を呼び出す必要がある正当な理由はわかりませんが、事実は人々が行うことです(標準でさえ、明示的な構文規則もあることを考えると完全に冗長です)与えられた)。
多くの場合に行われるように、(明示的な括弧がない場合)演算子は左から右に(それぞれ右結合演算子の場合は右から左に)実行されると言うことで、これを説明することから混乱が生じます。これは、構文とセマンティクス(実行)を混同し、ボトムアップ評価(すべてのオペランドが演算子の前に評価される)の操作に対してのみ有効であるため、誤解を招きます。特別な評価ルールを持つオペレーターにとって、それは単に間違っています。演算子_&&
_(and)および_||
_(または)のセマンティクスは、最初に左のオペランドを評価し、次に演算子自体(つまり、左または右のオペランドのどちらが結果を生成するかを決定する)ですおそらく正しいオペランドの評価によって。この左から右への評価は結合性から完全に独立しています:おそらくすべての非代入二項演算子がそうであるために、演算子はたまたま左結合的ですが、_(c1 && c2) && c3
_(すでに暗黙的に存在する冗長な括弧を使用) c1 && (c2 && c3)
と同等の実行があります(つまり、false
が返されるまで、または左から右に条件を実行し、それが返されるか、true
が返されない場合)、 2つのケースで異なるコードを生成する妥当なコンパイラを想像してください。実際、正しいグループ化は式がどのように評価されるかを示唆していますが、実際には違いはありません。 or
についても同様です。
これは、条件付き(三項)演算子_? ... :
_の場合、さらに明確です。ここでは、両側に開いた部分式があるため、結合性が適用されます(冒頭の文を参照)。中間のオペランドは_?
_および_:
_で囲まれ、 never は追加の括弧を必要とします。実際、この演算子はright-associativeと宣言されています。つまり、_c1 ? x : c2 ? y : z
_は_(c1 ? x : c2) ? y : z
_ではなくc1 ? x : (c2 ? y : z)
として読み取る必要があります(暗黙的な括弧は右の方へ)。ただし、暗黙的な括弧を使用すると、2つの三項演算子がから左から右へで実行されます。説明は、三項演算子のセマンティクスがすべての部分式を最初に評価するわけではないということです。
質問の例に戻ると、左結合性(または左から右へのグループ化)は、マトリックスベクトル積が_((M1*M2)*M3)*v
_として解析されることを意味します。数学的には同等ですが、これがM1*(M2*(M3*v))
として実行されることは事実上不可能ですが、より効率的です。その理由は、浮動小数点乗算は真に連想的(ちょうど近似的)ではなく、したがって浮動小数点行列乗算でもないためです。したがって、コンパイラーは1つの式を他の式に変換できません。 _((M1*M2)*M3)*v
_では、どの行列が最初にベクトルに適用されるかを言うことができないことに注意してください。なぜなら、それらはどれも: composite 線形マップの行列が最初に計算され、 that 行列がベクトルに適用されます。結果は、_M3
_が適用されるM1*(M2*(M3*v))
の結果とほぼ等しくなり、次に_M2
_になり、最後に_M1
_になります。しかし、そのようなことをしたい場合は、それらの括弧を書く必要があります。