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

# はじまり

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

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">A=B、A=Cであるとき必ずしもB=Cとはならないとか思ってる人間が、その謎理論で反論リプしてくる世界で人類がわかり合うことはないんだなって思う <a href="https://t.co/lsoS09jcBT">pic.twitter.com/lsoS09jcBT</a></p>&mdash; ぼのぼの＠煙草と柚子とヨーグルト (@BonoBono1031) <a href="https://twitter.com/BonoBono1031/status/1137567831443136512?ref_src=twsrc%5Etfw">2019年6月9日</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">a=bかつb=cなら<br>a=cが成り立つんだよなあ</p>&mdash; ЯXY (@Necroxis_Girl) <a href="https://twitter.com/Necroxis_Girl/status/1137669778036805633?ref_src=twsrc%5Etfw">2019年6月9日</a></blockquote>
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">A=B、A=CであるときB=Cになるとは限らないと思ってる世界で生きてるの日常生活に支障をきたしてそう</p>&mdash; ぼのぼの＠煙草と柚子とヨーグルト (@BonoBono1031) <a href="https://twitter.com/BonoBono1031/status/1137568236092788742?ref_src=twsrc%5Etfw">2019年6月9日</a></blockquote>
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">なんか別に間違ってなくね？？みたいな人がいるみたいでビビる。A=B、B=Cなら絶対にA=Cだし、こいつが出してる例は野菜⊃ニンジン、ニンジン=オレンジ、野菜⊃オレンジって話でそもそも=の例として不適切だから間違ってないように見えるだけ、中学数学ですよ</p>&mdash; ぼのぼの＠煙草と柚子とヨーグルト (@BonoBono1031) <a href="https://twitter.com/BonoBono1031/status/1137668562225524737?ref_src=twsrc%5Etfw">2019年6月9日</a></blockquote>

「A=B、B=Cなら絶対にA=C」という性質は、「推移律」といいます。
この性質が成り立つかどうかは、「=」の定義によります。

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

C言語では、「=」は代入演算子です。
代入演算子では、右辺のデータが左辺の型に変換され、変換後の値に評価されます。
この性質を利用することで、`A=B`および`A=C`は真だけど、`B=C`は偽となる例を作ることができます。

```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;
}
```

このコードを[実行](https://ideone.com/Kgzmgy)すると、

```
A=B
A=C
```

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

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

```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;
}
```

このコードを[実行](https://ideone.com/59xS8a)すると、

```
A=B
B=C
```

と出力されました。

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

先ほどの例は、「知らない人が見ると等価と誤解してしまう代入演算子」を用いており、
副作用もあるため、あまり美しくない例でした。
今度は、推移律の成り立たない等価比較演算子の例を示します。
それは、JavaScriptの`==`演算子です。
(ここではJavaScriptとしてSpiderMonkeyを扱います)

```javascript
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");
```

このコードを[実行](https://ideone.com/Hr1rB9)すると、

```
A==B
A==C
```

と出力されました。

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

```javascript
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](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
によると、

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

とあります。この「抽象的等価比較アルゴリズム」は、ECMAScriptの仕様書の
[11.9.3 The Abstract Equality Comparison Algorithm](http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3)
で定義されており、今回の例に関わる部分は

> <Pre>
> 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.
> </pre>

となっています。
これに従うと、`""=="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](http://www.ecma-international.org/ecma-262/5.1/#sec-9.3)にあり、

> 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*`との[`==`演算子](https://cpprefjp.github.io/reference/string/basic_string/op_equal.html)は、文字列を比較します。
一方、`const char*`同士の`==`演算子は、ポインタの値を比較します。
この性質を用いて、以下のように、`A==B`および`A==C`は真だけど`B==C`は偽となる例を作ることができます。

```cpp
#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;
}
```

このコードを[実行](https://ideone.com/NfLxeG)すると、

```
A==B
A==C
```

と出力されました。

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

```cpp
#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;
}
```

このコードを[実行](https://ideone.com/7eI6ah)すると、

```
A==B
B==C
```

と出力されました。

# まとめ

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