異なるシステム間のデータ交換やネットワーク通信などで重要になってくる「エンディアン」。
エンディアンの中で主要なビッグエンディアンとリトルエンディアンの相互変換を行う関数を作成します。
エンディアン変換関数
ビッグエンディアンとリトルエンディアンの変換は、簡単に言ってしまえばメモリの順番を反転してあげるだけです。
先頭にあったものが終端に、2番目にあったものが後ろから2番目に、終端にあったものが先頭になる感じです。
それを踏まえてエンディアンの変換関数は以下のようになります。
template <typename T>
T swap(T value) {
T result = 0;
for (std::size_t n = 0; n < sizeof(T); ++n) {
result |= ((value >> (n * 8)) & 0xFF) << ((sizeof(T) - 1 - n) * 8);
}
return result;
}
使用サンプルコード
エンディアン変換関数を使って変換してみます。
#include <iostream>
#include <cstring>
template <typename T>
T swap(T value) {
T result = 0;
for (std::size_t n = 0; n < sizeof(T); ++n) {
result |= ((value >> (n * 8)) & 0xFF) << ((sizeof(T) - 1 - n) * 8);
}
return result;
}
int main()
{
uint32_t val = 0x1234567;
uint32_t sval = swap(val);
std::cout << std::hex << "val:0x" << val << " swap:0x" << sval << std::endl;
return 0;
}
【出力結果】
val:0x1234567 swap:0x67452301
Floatとdoubleのエンディアン変換
FloatとDoubleのエンディアン変換は、一旦整数型へコピーしてからエンディアン変換関数を呼ぶことで実現します。
template <typename T>
T swap(T value) {
T result = 0;
for (std::size_t n = 0; n < sizeof(T); ++n) {
result |= ((value >> (n * 8)) & 0xFF) << ((sizeof(T) - 1 - n) * 8);
}
return result;
}
float swapFloat(float value) {
uint32_t intval;
std::memcpy(&intval, &value, sizeof(value));
intval = swap(intval);
float swapfloat = 0;
std::memcpy(&value, &intval, sizeof(value));
return value;
}
double swapDouble(double value) {
uint64_t intval;
std::memcpy(&intval, &value, sizeof(value));
intval = swap(intval);
float swapfloat = 0;
std::memcpy(&value, &intval, sizeof(value));
return value;
}
Floatの場合は、uint32_tの変数へmemcpy
してからswap
関数を呼び出します。
Doubleの場合は、uint64_tの変数へmemcpy
してからswap
関数を呼び出します。
FloatとDoubleのエンディアン変換サンプルコード
#include <iostream>
#include <cstring>
template <typename T>
T swap(T value) {
T result = 0;
for (std::size_t n = 0; n < sizeof(T); ++n) {
result |= ((value >> (n * 8)) & 0xFF) << ((sizeof(T) - 1 - n) * 8);
}
return result;
}
float swapFloat(float value) {
uint32_t intval;
std::memcpy(&intval, &value, sizeof(value));
intval = swap(intval);
float swapfloat = 0;
std::memcpy(&value, &intval, sizeof(value));
return value;
}
double swapDouble(double value) {
uint64_t intval;
std::memcpy(&intval, &value, sizeof(value));
intval = swap(intval);
float swapfloat = 0;
std::memcpy(&value, &intval, sizeof(value));
return value;
}
int main()
{
float val_f = 123.445f;
float sval_f = swapFloat(val_f);
float sval_f2 = swapFloat(sval_f);
std::cout << "val:" << val_f << " swap:" << sval_f << " swap2:" << sval_f2 << std::endl;
double val_d = 123432.43445;
double sval_d = swapDouble(val_d);
double sval_d2 = swapDouble(sval_d);
std::cout << "val:" << std::to_string(val_d) << " swap:" << sval_d<< " swap2:" << std::to_string(sval_d2) << std::endl;
return 0;
}
【出力結果】
val:123.445 swap:-5.01294e+14 swap2:123.445
val:123432.434450 swap:-1.74963e+139 swap2:123432.434450
valが元の値で、swapがエンディアン変換後の値、swap2がswapを更にエンディアン変換した値(元の値と同じ)です。
エンディアンについて
エンディアンとは、コンピューターがデータを記憶するときにメモリ上に配置する順番のルールです。
例えば、全4巻の漫画があったとして、本棚に左から順番に並べる(1巻→2巻→3巻→4巻)のがビッグエンディアンで、右から順番に並べる(4巻←3巻←2巻←1巻)のがリトルエンディアンです。
コンピューターの種類によって、どちらの方法を使うかが決まっています。コンピューター同士でデータをやり取りする際に、この順番を間違えていると違う値になってしまうので注意が必要です。
ビッグエンディアンとは
ビッグエンディアンは、最上位バイトから最下位バイトへと順にデータが格納されます。
異なるバイトオーダーを持つシステム間でも正しく通信できるようにするため、ネットワーク通信の標準的なデータ形式として広く使用されています。
また、人間が数字を読む順番と同じ並びのため、デバッグやデータ解析時に直感的に理解しやすいという利点もあります。
リトルエンディアンとは
リトルエンディアンは、最下位バイトから最上位バイトへと順にデータが格納されます。
Intel製のx86系CPUやAMD64、Appleシリコン(M1やM2など)、スマホなどで使用されているARMなど多くのプロセッサで使用されています。
コンピューターがデータを処理しやすいのが利点です。