A=B、A=Cであるとき、B=Cとなるとは限らない


はじまり

いつものようにTwitterを見ていると、気になるツイートがありました。

「A=B、B=Cなら絶対にA=C」という性質は、「推移律」といいます。

この性質が成り立つかどうかは、「=」の定義によります。


A=B、A=Cであっても、B=Cとならない例

C言語では、「=」は代入演算子です。

代入演算子では、右辺のデータが左辺の型に変換され、変換後の値に評価されます。

この性質を利用することで、A=BおよびA=Cは真だけど、B=Cは偽となる例を作ることができます。

#include <stdio.h>


int main(void) {
int A = 0;
char B = 128;
int C = 256;

if (A=B) puts("A=B");
if (A=C) puts("A=C");
if (B=C) puts("B=C");

return 0;
}

このコードを実行すると、

A=B

A=C

と出力されました。

この環境では、256をchar型に変換すると0になるため、B=Cは偽となりました。

同様に、以下にA=BB=CだけどA=Cではない例を示します。

#include <stdio.h>


int main(void) {
char A = 1;
int B = 1;
int C = 256;

if (A=B) puts("A=B");
if (B=C) puts("B=C");
if (A=C) puts("A=C");

return 0;
}

このコードを実行すると、

A=B

B=C

と出力されました。


A==B、A==Cであっても、B==Cとならない例

先ほどの例は、「知らない人が見ると等価と誤解してしまう代入演算子」を用いており、

副作用もあるため、あまり美しくない例でした。

今度は、推移律の成り立たない等価比較演算子の例を示します。

それは、JavaScriptの==演算子です。

(ここではJavaScriptとしてSpiderMonkeyを扱います)

var A = 0;

var B = "";
var C = "0";

if (A==B) print("A==B");
if (A==C) print("A==C");
if (B==C) print("B==C");

このコードを実行すると、

A==B

A==C

と出力されました。

同様に、以下にA==BB==CだけどA==Cではない例を示します。

var A = "";

var B = 0;
var C = "0";

if (A==B) print("A==B");
if (B==C) print("B==C");
if (A==C) print("A==C");

このコードをhttps://ideone.com/gQAnM5すると、

A==B

B==C

と出力されました。

比較演算子 - JavaScript | MDN

によると、


標準的な等価演算子 (== と !=) は 2 つのオペランドの比較に抽象的等価比較アルゴリズムを使用します。


とあります。この「抽象的等価比較アルゴリズム」は、ECMAScriptの仕様書の

11.9.3 The Abstract Equality Comparison Algorithm

で定義されており、今回の例に関わる部分は



1. If Type(x) is the same as Type(y), then
(中略)
c. If Type(x) is Number, then
(中略)
iii. If x is the same Number value as y, return true.
(中略)
d. If Type(x) is String, then return true if x and y are exactly the same sequence of characters
(same length and same characters in corresponding positions). Otherwise, return false.
(中略)
(中略)
2. If Type(x) is Number and Type(y) is String,
return the result of the comparison x == ToNumber(y).
3. If Type(x) is String and Type(y) is Number,
return the result of the comparison ToNumber(x) == y.

となっています。

これに従うと、""=="0"はString同士の比較なので1のルールを使用し、文字の並びが等しくないのでfalseになります。

また、ToNumber("")ToNumber("0")も0になるため、

0==""0=="0"""==0"0"==0は、いずれも2や3のルールを使用した後、1のルールを使用し、trueになります。

ToNumberの定義は9.3.1 ToNumber Applied to the String Typeにあり、


The MV of StringNumericLiteral ::: [empty] is 0.



The MV of DecimalDigit ::: 0 or of HexDigit ::: 0 is 0.


となっています。


A==B、A==Cであっても、B==Cとならない別の例

C++のstd::string(std::basic_string<char>)とconst char*との==演算子は、文字列を比較します。

一方、const char*同士の==演算子は、ポインタの値を比較します。

この性質を用いて、以下のように、A==BおよびA==Cは真だけどB==Cは偽となる例を作ることができます。

#include <iostream>

#include <string>

int main(void) {
const char* B = "hoge\0hoge";
const char* C = B + 5;
std::string A = B;

if (A==B) std::cout << "A==B\n";
if (A==C) std::cout << "A==C\n";
if (B==C) std::cout << "B==C\n";

return 0;
}

このコードを実行すると、

A==B

A==C

と出力されました。

同様に、以下にA==BB==CだけどA==Cではない例を示します。

#include <iostream>

#include <string>

int main(void) {
const char* A = "hoge\0hoge";
std::string B = A;
const char* C = A + 5;

if (A==B) std::cout << "A==B\n";
if (B==C) std::cout << "B==C\n";
if (A==C) std::cout << "A==C\n";

return 0;
}

このコードを実行すると、

A==B

B==C

と出力されました。


まとめ

=は等価比較演算子だとは限りません。

等価比較演算子は、推移律(「A==BかつB==C」ならば「A==C」)が成り立つとは限りません。