二進数と数値 基礎

これから送る URL から 1 バイト取り出してみる: curl https://api.example.com/user/4242 の中の文字 4 はビットパターン 0011 0100;コード上の 32 ビット整数 42 0000 0000 0000 0000 0000 0000 0010 1010。 場所によって同じビット形状でも意味が異なる —— そしてその意味は、次にそれらを読む命令が完全に決める。 5 セクションで答えを組み立てる:ビット、バイト、16 進数 —— 土台;2 の補数 + 対話的スクラバ + Ariane 5 を爆発させたバグ;IEEE-754 浮動小数点 + ビット級ディセクタ、そして0.1 + 0.2 ≠ 0.3 となる理由;エンディアンと 16 進ダンプが逆順に見える理由;最後に クイックリファレンス —— 冷たく説明できる価値のある問い。

01

ビット、バイト、そして実際に読む 16 進数

8 ビット = 1 バイト、1 バイトに 256 個の値。この最小単位は、上のあらゆる層 —— メモリ、ネットワーク、ディスク —— が唯一アドレッシングできる対象。

1 ビット(bit)は 1 つの 2 進数の桁、0 か 1。1 バイト(byte)は 8 ビットで、 28 = 256 個の異なる値を保持できる。なぜちょうど 8 ?歴史的選択: IBM System/360(1964)が標準化した。8 ビットなら印字可能文字 1 個に十分、 2 の冪で綺麗に割れ、当時のメモリチップで経済的にアドレッシング可能な最小単位だった。 いったん世界のツールチェイン(コンパイラ、ファイル形式、ネットワークプロトコル、オペコード)が バイト粒度に固定されると、この選択は二度と動かせなくなった。 現代のあらゆるアドレッシング可能なメモリ位置 —— レジスタを除く —— の単位はバイト、 ビットではない。

8 ビットを超えると人間には 2 進数が読めない。だからプログラマは 4 ビットずつ まとめて 1 桁の 16 進数(0–9 のあと a–f)にする。1 バイトは 2 桁の 16 進数; 0xff = 255 = 1111 1111。16 進が 2 進にきれいに対応するのは、 16 = 24 で、各 16 進数字がちょうど 1 nibble、情報の隠れがないから。 10 進にはこの性質がない —— 255 という 10 進表記から、 どのビットが立っているかは分からない。だからメモリダンプ、パケットキャプチャ、 プロトコル RFC、デバッガ、Wireshark のいずれも、整数表示時のデフォルトは 16 進。

URL 文字   '4'        '2'        '/'        ' '
ASCII      52         50         47         32
16 進     0x34       0x32       0x2F       0x20
ビット     0011 0100  0011 0010  0010 1111  0010 0000

上の 4 バイトが、curl が文字列 "42/ "に対して実際に線上に書く列。文字列はバイトとしてメモリに存在し、 そのバイトの意味(ASCII テキスト? ビッグエンディアン整数? RGBA ピクセル?)は 次にそれらを読む命令が完全に決める。 CPU はバイトレベルで「型」の概念を持たない;それは上位のプログラミング言語が乗せた抽象。

UTF-8 を短く。7 ビット ASCII サブセット(コードポイント 0–127)は 1 バイトに符号化され、最上位ビットは常に 0。コードポイント 128 以上は 2–4 バイトで、 先頭の数ビットがバイト数を示す。だから純 ASCII 文字列(URL、HTTP ヘッダ、英文ソース)は ASCII と UTF-8 でビット単位に同一 —— だから Web が UTF-8 標準化した時に ASCII ツールが壊れなかった。example.com.jp に CJK コードポイントがあれば それぞれ 3 バイト、絵文字なら 4 バイト。

ビットシフトとビット演算。ハードウェアレベルで関心があるのは 3 操作:AND(ビットマスク)、OR(ビット立て)、XOR(ビット反転 / 差分検出)。さらにシフト:x << 1 = 2 倍、x >> 1 = 1/2(符号付きの場合は注意)。 上位のあらゆる操作 —— プロトコルパッキング、暗号、圧縮、ハッシュ —— はこれらから組み上がる。 Linux カーネルやあらゆるデータベースエンジンは日常的に使う; Web アプリのコードは通常使わない —— だから多くのエンジニアは、 性能プロファイラに思い出させられるまで、その存在を忘れている。

要点。「1 バイトは 8 ビット、256 個の値、 現代の CPU が一度に読み書きする最小単位。 16 進は圧縮された 2 進 —— 4 ビットが 1 桁になる —— だからあらゆるメモリダンプ、 パケットキャプチャ、プロトコル仕様で使われる。バイト自体は型を持たない; 次にそれらを読む命令が、テキスト・整数・浮動小数点・ピクセルのどれと解釈するかを決める。」

02

2 の補数 —— 符号付き演算は実際にどう動くか

CPU の加算回路は 1 つだけ。同じ回路で 42 + 17−42 + 17unsigned 200 + unsigned 30 を計算する。 その理由が 2 の補数、そしてその仕組みが成立する理由が剰余算術。

正整数を 2 進数で格納するのは自明 —— 42 = 00101010。 面白いのは −42 をどう格納するか。歴史的に 3 つ試され、最初の 2 つは悪く、 3 つ目が現代のあらゆる CPU で使われている。

符号と絶対値(sign-and-magnitude)—— 最上位ビットに符号、残りに絶対値。読みやすいが、ゼロが2 つ(+0 = 00000000−0 = 10000000)、 加算は両オペランドの符号を見て 4 通り(同符号 / 異符号 × 絶対値大 / 小)に分岐する必要がある。 ハードウェアは大きく遅く、利点ゼロ。1 の補数(one's complement)—— ビット反転で負を得る —— も同じ「2 つのゼロ」問題があり、加算には 「end-around carry」のサイクルが要る。1970 年代までに両方とも 2 の補数に敗れた。

2 の補数(two's complement)が要のトリック。 反転:全ビットをフリップ、その後 1 を足す。なぜ成立するか?剰余算術で見ると明確。 8 ビットレジスタでは全値が暗黙に modulo 28 = 256 として扱われる。−xx + (−x) = 0 を満たすようにしたい。 modulo 256 では −x ≡ 256 − x。 そして任意の 8 ビット x に対する 256 − x はまさに「ビット反転、+1」—— 全ビット反転で 255 − x、+1 で 256 − x になるから。 算術は剰余代数から自然に落ち、ビット反転規則は単に効率的な実装にすぎない。

−x は実は「奇妙に見える符号なし値」に過ぎないので、同じ加算回路が符号付きと符号なしの両方を担う。 通常の 2 進加算で 42 + (−42) を計算:00101010 + 11010110 = 1 00000000。 最上位ビットからの桁上がりは捨てられる —— modulo 256 では結果は 0。 引き算は「第 2 オペランドを符号反転して足す」になる。 CPU には独立した減算回路がない。 数え方によっては、これが 20 世紀のハードウェア vs ソフトウェアのインタフェース決定の中で最も重要な 1 つ。

2 の補数スクラバ —— 同じ 8 ビット、3 通りの解釈ゼロbit 7 (sign)2^62^52^42^32^22^12^000000000符号付き(int8)0符号なし(uint8)016 進0x00
全 0。符号付きと符号なしで読みが一致。
1 / 8
2 の補数は本質的に剰余算術:8 ビットレジスタ内では、−x ≡ 256 − x (mod 256)。「ビット反転 + 1」がアルゴリズム、 剰余算術が成立の理由。1 つの加算回路で符号付き / 符号なし両方の加算を扱えるのは、 ビットパターン自体が自分の符号を知らないから —— それを読む命令だけが知っている。

噛みつく非対称性

8 ビット符号付き整数の表現範囲は −128 から +127。 256 通りのビットパターンがこれを覆うが、範囲は対称ではない:|−128| は 128 になるが、8 ビット符号付きにはその値が存在しない。 全ての符号付き型で同じ形:INT32_MIN = −2³¹INT32_MAX = +2³¹ − 1。古典的なトラップは abs(INT_MIN) —— C/C++ では未定義動作(UB)、数学的に正しい結果が収まらないから。 多くの実装は INT_MIN をそのまま返す ——abs(−2147483648) == −2147483648 —— 「返り値は非負」と想定する下流コードを静かに壊す。

符号付きオーバーフローは UB。C と C++ 規格は符号付き整数オーバーフローを未定義動作と定める。 ハードウェアができないからではなく(現代の CPU は modulo 2nできれいに包む)、コンパイラがそれは起こらないと仮定して積極的に最適化できるようにするため。for (int i = 0; i < n; i++) は「i + 1 > iが常に成立」という仮定で激しくベクトル化され得るが、それはオーバーフローが 不可能な前提でのみ正しい。実際のプロダクション事故:SR-71 のナビゲーション システムの整数オーバーフロー、Ariane 5 ロケット 1996 年の爆発 (64 ビット float → 16 ビット int 変換オーバーフロー)、 Pac-Man のレベル 256 「kill screen」、 2014 年 12 月の YouTube 再生カウンタ上限(江南スタイルが 2³¹ − 1 再生に達し、 Google は int64 に移行)。

Y2038 問題。Unix タイムスタンプは符号付き 32 ビットの 「1970 年からの秒数」。2038-01-19 03:14:08 UTCにオーバーフローし、−2147483648 へラップ —— これを Unix は 1901-12-13 と解釈する。time_t として時刻を格納する全データベース、 ログファイル、線上プロトコルに期限がある。現代コードは 64 ビット時刻を使うが、 レガシーシステムは残る。

符号付き vs 符号なし比較 —— 静かなバグ

C/C++ では、符号付きと符号なしの値を比較する際、符号付きオペランドは暗黙に符号なしへ変換される。int x = −1; unsigned y = 1; if (x < y) ...:x4294967295 になり、1 より大きい。 コンパイラは明示的ケースで警告するが、暗黙のほとんどは見逃す。 直し方は型を一貫させる —— 両方符号付きか両方符号なしか —— そして符号付きカウンタと混在する式で size_t(符号なし)を使わないこと。 C++ コードレビューで最も多い静かなバグの 1 クラス。

要点。「2 の補数は負の数を剰余加算下で符号なし数と同じように振る舞わせる、 だから CPU は加算器 1 つで両方を扱える。代価は非対称性 —— INT_MIN に正の双子はなく、 だから abs(INT_MIN) は未定義、C++ で符号付きオーバーフローが UB な理由。 もう 1 つの罠は暗黙の符号付き→符号なし変換:−1 を符号なし値と比較すると静かに最大の符号なし値になる。」

03

IEEE-754 —— 浮動小数点はなぜ嘘をつくか

有限ビットでは無限の実数を表せない。浮動小数点は体系的かつ有界の嘘を受け入れる代わりに、 巨大なダイナミックレンジ + 1.0e−38 から 1.0e+38 までを単一の ハードウェア ALU で扱う能力を得る。

32 ビット Float(Float32、float)は 32 ビットを 3 フィールドに分割:符号 1 ビット指数 8 ビット仮数 23 ビット。64 ビット Double(Float64、double)は1 + 11 + 52。正規化値の式:

value = (−1)^sign × 1.mantissa(binary) × 2^(exp − bias)

bias = 127(Float32)、1023(Float64)

例(Float32):
  1.0  →  sign=0  exp=01111111  mantissa=00000000000000000000000
  0.5  →  sign=0  exp=01111110  mantissa=00000000000000000000000
  0.1  →  sign=0  exp=01111011  mantissa=10011001100110011001101  (不正確)

バイアスの工夫が巧妙:指数を 実際 + 127 として格納すると、 合法な格納指数はすべて非負 —— CPU が 2 浮動小数点をビットを通常の符号なし整数として 比較できる(符号ビット 1 つの調整は要る)。整数比較ハードを浮動小数点比較に再利用できる —— これは 1985 年 IEEE-754 標準化時の実際の考慮事項だった。

1.mantissa先頭の 1 は格納されない —— 暗黙。 正規化された浮動小数点は定義により 2 進数で 1. から始まるので、 格納すれば 1 ビット無駄。例外は端の場合:

  • ゼロ:全ビット 0。専用の特殊エンコード、式を適用すると1.0 × 2^(−127) になり 0 にならないから。−0.0(符号ビット 1、他全 0)もある —— ビットパターンは異なるが +0.0 と等しく比較される。
  • ±∞:指数全 1、仮数 0。オーバーフロー(1.0e38 × 100)や 0 除算から発生。比較可能、算術で伝播。
  • NaN:指数全 1、仮数 ≠ 0。0.0/0.0sqrt(−1)∞ − ∞ から発生。NaN は自身を含め何とも等しくない —— IEEE-754 準拠の全言語で NaN == NaNfalse。 NaN 検出の標準的書き方:x != x
  • サブノーマル(denormal):指数 0、仮数 ≠ 0。最小の正規化値より小さく、精度が落ちる。 これがあることで 0 への underflow が滑らかになる。多くの CPU で悪名高く遅い (通常の 10–100 倍)、性能重視コードは FTZ / DAZ フラグで吹き飛ばすことが多い。
IEEE-754 ディセクタ —— Float32、3 フィールド、8 例値: 0.0signexponent · 8 bitsmantissa · 23 bits00000000000000000000000000000000デコードsign = + exp = 0 (unbiased -127)mantissa-fraction = 0.000000 生(16 進) = 0x00000000= ±0 (special case)実際の Float320.00000000
全ビット 0。最も綺麗な値 —— そして式が壊れる唯一の値(1.0 × 2^(0−127) はあり得ない)。特殊扱い。
1 / 8
Float64(C/Java/JS のデフォルト)は同じ形:ビット数が 1 + 8 + 23 ではなく 1 + 11 + 52 になる。 バイアスは 1023、最大の正確整数は 253。 他のすべて —— 正規化形、丸め挙動、特殊値 —— はここで示すものと同じ。

なぜ 0.1 + 0.2 ≠ 0.3 なのか

10 進 0.1 の 2 進展開は 0.000110011001100110011…無限の循環分数 —— 10 進で 1/30.333… となるのと同様。 Float64 は仮数 52 ビットなので値は切り詰められ、格納結果は真の 0.1 から 約 5×10⁻¹⁷ 異なる。0.2 は同じ無限展開を 2 進で 1 桁左にずらしたもので、 同じ仕組みで丸められる。すでに丸められた 2 値を足すと、 その結果はさらに別の値で、Float64 の 0.3 の正確な表現とは異なる —— わずかに大きい:0.30000000000000004

これはバグではない。IEEE-754 浮動小数点を使うあらゆる現代言語 (主要なものすべて:C、C++、Java、JavaScript、Python、Go、Rust ……)が、 標準の丸めルールに従うため、まったく同じ値を出す。この数は誤りではない; 「0.1 に最も近い Float64 と 0.2 に最も近い Float64 の真の数学和」に最も近い Float64 値。

精度は不均一

浮動小数点値は数直線上で等間隔ではない。0 付近で密、0 から離れるほど疎。Float64 の場合:

1.0 付近:    隣接 float の差 ≈ 2.2 × 10⁻¹⁶
1.0e6 付近:  隣接 float の差 ≈ 1.2 × 10⁻¹⁰
1.0e15 付近: 隣接 float の差 ≈ 0.125(1 未満!)
1.0e16 付近: 隣接 float の差 ≈ 2.0(奇数整数を飛ばす)

Float64 の最大正確整数は 253 = 9,007,199,254,740,992。 それ以上では 1 つおきに表現不能:2^53 + 12^53 に丸まる。 JavaScript はすべての数(整数含む)を Float64 で格納するので、Number.MAX_SAFE_INTEGER = 2^53 − 1 —— BigInt が言語に追加されたのは、暗号と ID 処理コードのため。

浮動小数点が間違ったツールである場面

金銭。あらゆる入門書に書かれているのに、依然として無視される。0.1 + 0.2 ≠ 0.3 は数百万件の取引で蓄積する;丸めモードでは救えない。 固定小数点整数(セント、ドルではなく)か十進数型 (java.math.BigDecimalpython.decimal.Decimal)を使う。

等価比較。浮動小数点に a == b はほぼ常に 「ビットパターンがバイト単位に同一」を意味し、これはほぼ望むものではない。 epsilon で比較:abs(a − b) < ε、ε は関与する大きさに応じてスケール。 または ULP 距離(units in the last place)で数値的に原理的な尺度を使う。 Knuth の比較と Numerical Recipes は両方ともこの問題に丸 1 章を割いている。

要点。「Float64 は sign(1)+ exponent(11)+ mantissa(52)。 正規化値の先頭の 1 は暗黙。指数の極値での特殊エンコードが ±0、±∞、NaN、サブノーマル。0.1 + 0.2 ≠ 0.3 は両入力が無限の 2 進分数で、入力時に丸められるから。 Float64 の最大正確整数は 253。浮動小数点に == を使うな; 金銭を浮動小数点で格納するな。」

04

エンディアン —— なぜバイトが逆順に見えるか

CPU は 32 ビット整数をメモリ上の 4 バイトとして格納する。問題はどのバイトが先か。 世界は 1970 年代に互換性のない 2 つの答えを選び、固定化し、それ以来あなたはその代価を払い続けている。

32 ビット整数 0x12345678 を考える。4 バイト ——0x120x340x560x78 —— が必要で、ハードウェアはどれを最低メモリアドレスに置くか決めなければならない。

リトルエンディアン(x86、ARM デフォルト、RISC-V、あらゆる消費者 CPU)
  アドレス:  0x1000  0x1001  0x1002  0x1003
  バイト:      78      56      34      12
                  ↑ 最下位バイトが先

ビッグエンディアン(「ネットワークバイトオーダ」、PowerPC、SPARC、旧 IBM)
  アドレス:  0x1000  0x1001  0x1002  0x1003
  バイト:      12      34      56      78
                  ↑ 最上位バイトが先

リトルエンディアンが勝った。これから触るあらゆる CPU —— x86、x86-64、ARM のデフォルト構成、Apple Silicon、AMD64、Raspberry Pi —— はリトルエンディアン。ビッグエンディアンは現在ほぼ歴史的: レガシーな IBM メインフレーム、一部の古い MIPS 組み込み変種、 そして大半のネットワークプロトコルの線上形式。

なぜハードウェアでリトルエンディアン?良い性質がある:幅広い整数を狭いものへ切り詰めるのが無料。 アドレス X に 32 ビット値があり、それを uint8 として読みたいとき、X のバイトを 1 つ読むだけ —— 最下位バイトはすでに先頭にある。ビッグエンディアンでは元の幅を知ってオフセットを計算する必要がある。 他にも論点(多倍長加算の桁上がりが下から上へ自然に流れる)があるが、これが最も引用される。

なぜネットワークでビッグエンディアン?歴史的偶然。ARPANET の初期ホスト (PDP-10、IBM 360、IMP ルータ)は主にビッグエンディアンで、IETF は 1980 年代初頭、 この選択を IP、TCP、UDP、DNS プロトコルに凍結した。 リトルエンディアン x86 がデスクトップを取った頃には、線上形式は既に配備済み。ネットワークバイトオーダは単にビッグエンディアンを指す —— IP ヘッダも、TCP シーケンス番号も、UDP 長フィールドも、両端にどんな CPU があろうと ビッグエンディアンで線上を流れる。

コードでの変換

BSD 派生の socket API は至る所で見る 4 関数を提供する:

htons(x)    // host→network, short (16 ビット)
htonl(x)    // host→network, long  (32 ビット)
ntohs(x)    // network→host, short (16 ビット)
ntohl(x)    // network→host, long  (32 ビット)

ビッグエンディアンホストではすべて no-op;リトルエンディアンホスト (つまりほぼ常時)ではバイト順を入れ替える。現代コードの大半はシリアライゼーション ライブラリ(Protobuf、Cap'n Proto、MessagePack、JSON)の背後に隠す —— しかし TCP / UDP / IP ヘッダを手で扱う、または別アーキ由来のバイナリファイルを読む瞬間、 スワップは戻ってくる。GCC と Clang の組み込み __builtin_bswap16/32/64は必要なときに 1 命令でスワップを与える。

エンディアンが今も噛みつく場所

  • tcpdump / Wireshark の 16 進ダンプ。これらは線上に到着した順序(ネットワーク = ビッグエンディアン)でバイトを表示するので、 コードが 0x12345678 と出力する 32 ビット値が画面上では12 34 56 78 に見える。「普通」に見える —— しかしメモリダンプ (x86 でリトルエンディアン)からバイトをコピーすると、同じ値が 78 56 34 12 に見え、 毎回人を混乱させる。
  • バイナリファイルの可搬性。ビッグエンディアン・メインフレーム上でディスクに ダンプされた C struct を x86 で読み戻すとゴミになる。だから使う価値のあるバイナリファイル形式は すべてエンディアンを文書化する。PNG、TIFF、ELF、MIDI、WAV すべて明記している。
  • ファイルヘッダのマジックナンバー。多くのファイル形式は、エンディアンによらずバイト列が固定のマジックナンバーで始まる: PNG はバイト 89 50 4E 47("\\x89PNG" と綴る)で始まり、 JPEG は FF D8 FF で始まる。これらはバイト列であって多バイト整数ではないので、 どちらのアーキでも同じに見える。
  • ビットエンディアン。1 バイト内のビット順序は、あなたが遭遇するどの アーキテクチャでも変わらない —— ビット 7 が最上位、ビット 0 が最下位。 古い文書には「最上位ビット番号 0」vs「最下位ビット番号 0」を区別するものがあるが、 これは文書上の慣習であり、ハードウェアの違いではない。

追跡中の curl リクエストでは、URL 文字列はエンディアンフリー —— 各バイトが自身の値で、並べ替える多バイト語はない。しかしそれを運ぶ TCP セグメントには 多バイトフィールドがある:送信元ポート(16 ビット)、宛先ポート(16 ビット)、 シーケンス番号(32 ビット)、確認応答番号(32 ビット)、ウィンドウサイズ(16 ビット)、 チェックサム(16 ビット)。そのすべてがビッグエンディアンで線上を流れ、 あらゆるリトルエンディアン CPU でカーネルが解釈する前にバイトスワップされる必要がある。

要点。「リトルエンディアン(LSB が先)はあらゆる消費者 CPU が メモリで使うもの。ビッグエンディアン(MSB が先)はあらゆる標準ネットワークプロトコルが 線上で使うもの。変換関数は htonl/ntohl/htons/ntohs; リトルエンディアンホストではバイトスワップ、ビッグエンディアンホストでは no-op。 エンディアンバグを見抜く最も簡単な方法:16 進ダンプ内の多バイト値が、 その 10 進値と逆順に見える。」

05

クイックリファレンス

冷たく説明できる価値のある 6 つの核心問題と、コードレビューで一目で見抜くべき 5 つの赤旗。 まず質問を頭に刻み込む;回答骨子はその下に。

なぜ 0.1 + 0.2 ≠ 0.3 なのか?

0.10.2 も 2 進数では無限循環分数 (10 進数の 1/3 = 0.333… と同様)。Float64 は仮数 52 ビットしか格納できないので、 値はメモリに入った瞬間に最近接の表現可能 Float64 へ丸められる。 加算で 2 つの丸め誤差が累積し、結果は 0.3 に最も近い Float64 よりわずかに大きい 第 3 の Float64 —— 0.30000000000000004。 あらゆる IEEE-754 言語が完全に同じ値を出す。

Float64 の最大正確整数は?

253 = 9,007,199,254,740,992。 それ以上では隣接 float の間隔が 1 を超えるので、連続整数を両方表現できない ——253 + 1253 に丸まる。 これがちょうど JavaScript の Number.MAX_SAFE_INTEGER、 そして BigInt が言語に追加された理由。

なぜ abs(INT_MIN) == INT_MIN なのか?

2 の補数は非対称:n ビット符号付きでINT_MIN = −2n−1INT_MAX = 2n−1 − 1。 数学的な |INT_MIN|2n−1 で、n ビット符号付きに収まらない。C/C++ ではこれを未定義動作とし、 多くの実装は INT_MIN をそのまま返す。古典的バグ: 二分探索で mid = (low + high) / 2 を計算すると両方が大きいときオーバーフローする。

符号付き / 符号なし比較はいつ静かに壊れるか?

式の中で混在させ、かつ符号付き値がたまたま負のとき。 C/C++ の暗黙変換が符号付きオペランドを符号なしへ昇格させる:int x = −1; size_t y = 1; (x < y)と評価される、xUINT_MAX になるから。size_t(strlenvector::size() など由来)が最も多い元凶。 直し方は型を一貫させるか明示キャスト。

ネットワークバイトオーダとホストバイトオーダの違いは?

ネットワークバイトオーダはビッグエンディアン(最上位バイトが先)。 IETF が 1980 年代に ARPANET のホストが大半ビッグエンディアンだった頃 IP/TCP/UDP に固定。 ホストバイトオーダはローカル CPU が使うもの —— あなたが持つ全消費者機種でリトルエンディアン(最下位バイトが先)。 BSD socket API は 16 / 32 ビット変換のためにhtons/htonl(host→network)と ntohs/ntohl(network→host)を提供する。

なぜプログラマはメモリダンプに 16 進を使うのか?

16 = 24 なので、各 16 進数字が 4 ビットに正確に対応し、 情報の隠れがない。10 進はビット構造を完全に隠す(255 はどのビットが立っているかを 伝えない、0xFF は 8 ビット全てが立っていると教える)。 16 進は 1 バイトを 2 文字に収めるのでバイト境界がスキャン可能。 あらゆるデバッガ、16 進エディタ、パケットキャプチャ、プロトコル RFC が デフォルトで 16 進を使う理由。

コードレビューの赤旗

  • 浮動小数点の等価比較。if (a == b)a または bfloat / double。 ほぼ確実にバグ —— epsilon 比較または ULP 距離に置き換える。
  • abs(x)xINT_MIN になり得る。外部データから計算される符号付き整数のあらゆる場所で、これは未定義動作。(x < 0) ? -static_cast<unsigned>(x) : x か、 絶対値用にもっと広い型を使う。
  • 明示キャストなしの符号付き / 符号なしを混ぜた比較。特に for (int i = 0; i < vec.size(); ++i) —— 比較が isize_t へ昇格させる、ループカウンタが負になるまでは問題ない。size_t をカウンタにするか、static_cast<int>(vec.size())と比較する。
  • double に金銭を格納。乗算ごとに丸め誤差が累積する。 代わりに固定小数点整数(セント)または十進数型 (BigDecimalDecimal)を使う。
  • エンディアン処理なしのバイナリデータ読み取り。fread(&header, sizeof(header), 1, fp)header に多バイト フィールドがあり、ファイルが別アーキ由来。C struct はロードに成功し、ナンセンスを生む。 明示的なバイト単位シリアライゼーションかスキーマベース形式(Protobuf 等)を使う。