0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SPICE 用波形フォーマット「SSF V4.3」を設計した話

Last updated at Posted at 2025-11-01

はじめに

本記事は Claude Code(⼀部 Codex)を使って SPICE シミュレータを6日間で開発してみたの続編です。前回は AI 実装で C++20 製の SPICE 互換シミュレータ「sukimaspice」を構築した経緯と、数値解法・検証方法を中心に紹介しました。続編となる今回は、sukimaspice の波形出力を支える独自フォーマット SSF (Signal Storage Format) V4.3 の仕様を、わかりやすく解説します。

目的:SPICE 系の解析(TRAN / DC / AC / NOISE / OP / Monte Carlo)の結果を、軽く・速く・正しく・拡張可能に保存するための実用仕様

先に要点

  • sukimasim ロジック波形出力フォーマットの SSF V4 に、SPICE/アナログ対応を追加
    • 軸モデルの一般化(Axis0 = time/freq/param/mc …)
    • 実数は「量子化整数(R0)推奨」または「IEEE754 ビットキャスト(R1)」でロスレス保存
    • 複素数は「ペア+グループ ID」で表現(real/imag または mag/phase)
  • 解析マッピング標準化:TRAN/DC/AC/NOISE/OP/MonteCarlo それぞれで推奨メタデータセットを定義
  • ストリーミング圧縮に対応flags.bit6=1 のとき、フレーム形式(4B ヘッダー + payload)を使用。compression_type=0(非圧縮)でもフレームヘッダーは必須。
  • 拡張性重視:将来的な拡張はメタデータで吸収。フラグによる機能追加も可能

なぜ SSF を作ったのか(背景と設計方針)

既存の選択肢(CSV / TSV / 独自バイナリ / raw 互換)は「サイズ・速度・表現力・互換性」のいずれかで妥協が生じがちです。特に:

  • TRAN と AC で軸の意味が異なる(時間 vs 周波数)
  • AC は複素数(Real/Imag または Mag/Phase)を自然に扱いたい
  • 長時間シミュレーションで「追記(ストリーミング)やランダムアクセス」を効かせたい
  • 単位・次元・ノード/デバイス紐付けをメタデータで統一したい

起源:sukimasim からの拡張

SSF はもともと、デジタル論理シミュレータ「sukimasim」プロジェクトの波形を高速・軽量に保存するために設計したフォーマットです。ロジック系の要件(高速追記、部分読み出し、階層スコープ/シグナル管理、エイリアス、軽量メタデータ)を満たす骨格を整備した後、アナログ/SPICE ユースケースに対応するため、以下の機能を追加しました:

  • Axis モデルの一般化:時間軸だけでなく、周波数軸、パラメータ掃引軸、Monte Carlo 試行番号など
  • 実数・複素数の表現:量子化整数(R0)または IEEE754 ビットキャスト(R1)、複素数のペア表現
  • SPICE メタデータ標準化:解析タイプ、物理量、単位、ノード/デバイス情報など

これにより、同一フォーマットでロジック波形と SPICE 波形(TRAN/DC/AC/NOISE/OP/Monte Carlo)をロスレスに扱えます

設計指針

SSF V4.3 は以下の方針で設計されています:

  • バイナリ構造の統一:256B ヘッダー、ディレクトリ、L1/L2/L3、データブロック
  • メタデータ駆動:解析に依存する意味づけはメタデータで与え、波形は一貫した構造で保持
  • スケーラビリティ:ストリーミング I/O とブロック独立性を優先(大規模波形でもスケール)
  • 実装容易性:最小限の規範と、現実的な参考ガイド(非規範値)を併記

仕様の全体像

SSF ファイルは次の順で構成されます(固定順序)。

  1. ヘッダー(256B, offset=0)
  2. メタデータ(varint エントリ数 + 各エントリ: varint キー長 + UTF-8 キー + varint 値長 + UTF-8 値)
  3. スコープディレクトリ(varint 数 + 各エントリ)
    • 階層構造を表現(例:トップレベル回路 → サブサーキット)
    • 各エントリ: scope_id, parent_id, name
  4. シグナルディレクトリ(varint 数 + 各エントリ)
    • 各信号の属性(名前、型、幅、スコープ所属など)
    • 各エントリ: signal_id, scope_id, name, type, width, フラグ等
  5. 階層的インデックス(任意: L3 → L2 → L1)
    • L1 は必須(ブロックがある場合)
    • L2/L3 は大規模データの高速検索用(任意)
  6. データブロック(任意)
    • スナップショット + 変化列のエンコード
    • ストリーミング時は各ブロックがフレーム形式

重要な互換性:セクションは互いに重複せず、ヘッダーの *_offset/size と一致していなければなりません。


V4.3 の主要機能

ストリーミング圧縮(flags.bit6=1

  • フレーム形式[uncompressed_size: uint32 LE] + payload
  • compression_type=0(非圧縮)でもフレームヘッダーは必須
  • L1 の size は「フレーム全長(4 + payload 長)」を記録
  • 利点:読み出し経路の一本化、整合性チェック、将来の圧縮方式追加が容易

バージョン管理

  • 常に version_major=3, version_minor=4(V4 世代の互換レベル)
  • ライターは 3.4 を出力、リーダーはそれ以外を警告/エラー
  • 将来の破壊的変更は V5 以降で実施

SPICE/アナログ拡張(flags.bit7=1

  • Axis モデルの一般化
    • Axis0 を主軸として扱う(time/freq/param/mc/index など)
    • メタデータで axis.0.typeaxis.0.unit を指定
  • 実数表現
    • R0(量子化整数)推奨:差分エンコーディングで高圧縮率
    • R1(IEEE754 ビットキャスト)互換:ビット完全互換が必要な場合
  • 複素数表現
    • 2 本の信号を complex.group で束ねる
    • complex.role={real,imag} または {mag,phase} を付与
  • 解析マッピング
    • TRAN/DC/AC/NOISE/OP/MonteCarlo それぞれで推奨メタデータセットを定義
    • 解析タイプ、物理量、単位、ノード/デバイス情報など

ヘッダー要点(256B, 固定)

  • ファイル拡張子: .ssf(推奨)
  • バイトオーダー: Little Endian(全ての整数フィールド)
  • 識別:magic='SSF\x04', version=3.4(厳密運用)
  • 解析軸:start_time / end_time / timescale_{num,den} は Axis0(主軸)の整数座標スケール
    • 例:TRAN で ps 単位なら timescale_num=1, timescale_den=1_000_000_000_000
  • 圧縮:compression_type = 0(NONE)/1(LZ4)/2(ZLIB)/3(ZSTD)
  • フラグ:
    • 0x01 DELTA_VARINT: 差分 varint エンコーディング(変化列)
    • 0x02 POSITION_TABLE: 位置テーブル(デジタル波形用)
    • 0x04 BLOCK_INDEPENDENCE: ブロック独立性(並列デコード可能)
    • 0x08 ADAPTIVE_ALIASING: 適応エイリアス(動的エイリアス解決)
    • 0x10 ADAPTIVE_BLOCKS: 適応ブロックサイズ
    • 0x20 HIERARCHICAL_INDEX: 階層インデックス(L3/L2/L1)
    • 0x40 STREAMING_COMPRESSION: ストリーミング圧縮(フレーム形式)
    • 0x80 ANALOG_EXTENSIONS: アナログ拡張(SPICE メタデータ)
  • 推奨:ストリーミング時は フレーム必須(非圧縮でも 4B ヘッダー)。

Axis モデル(時間軸だけではない)

Axis0 は「主軸」です。TRAN なら time[s]、AC なら freq[Hz]、DC なら param[unit]、Monte Carlo なら mc[idx] といった具合に、解析ごとに意味が変わります。

主要な特徴:

  • メタデータで axis.0.typeaxis.0.unit を必須化(flags.bit7=1 のとき)
  • Axis1 以降もメタデータで説明可能(例:Monte Carlo × 時間の 2 軸データ)
  • Header の timescale_* は Axis0 の整数座標 ⇄ 物理量の比率を表す(TRAN なら ps, ns など)

解析タイプ別の Axis0:

解析 axis.0.type axis.0.unit 説明
TRAN time s 時間軸
AC freq Hz 周波数軸
DC param V, A など パラメータ掃引軸
NOISE freq Hz 周波数軸
OP index idx 単一点(start=end=0)
Monte Carlo mc idx 試行番号

メタデータ設計(キーセットの標準化)

メタデータは UTF-8 キー/値ペアの配列として格納されます(varint エントリ数 + 各エントリ: varint キー長 + UTF-8 キー + varint 値長 + UTF-8 値)。

代表キー(SPICE/アナログ拡張、flags.bit7=1

ファイルレベル:

  • file.analysis: 解析タイプ("tran", "ac", "dc", "noise", "op", "montecarlo"
  • file.generator: 生成ツール名(例: "sukimaspice 0.6.0"
  • file.title: タイトル(ネットリスト最初の行など)

軸(Axis):

  • axis.k.type: 軸の種類("time", "freq", "param", "mc", "index"
  • axis.k.unit: 物理単位("s", "Hz", "V", "idx"
  • axis.k.name: 軸の名前(例: DC sweep なら "V1.dc"
  • axis.k.scale.num / axis.k.scale.den: 量子化スケール(オプション)

信号(Signal):

  • signal.<id>.quantity: 物理量("voltage", "current", "power", "noise_density"
  • signal.<id>.unit: 単位("V", "A", "W", "V^2/Hz", "A^2/Hz"
  • signal.<id>.kind: 種別("node", "device", "branch", "internal"
  • signal.<id>.node: ノード名(kind=node の場合)
  • signal.<id>.device: デバイス名(kind=device の場合)
  • signal.<id>.scale.num / signal.<id>.scale.den: R0 量子化スケール(必須、R0 使用時)
  • signal.<id>.complex.group: 複素数グループ ID(文字列)
  • signal.<id>.complex.role: 複素数の役割("real", "imag", "mag", "phase"

不変条件(Invariants)

リーダー/ライターは以下を検証すべきです:

  1. flags.bit7=1 の場合、必須キー:

    • file.analysis, axis.0.type, axis.0.unit
    • R0 使用時は signal.<id>.scale.num/den
  2. 複素数の整合性:

    • 同一 <id>complex.role が重複しない
    • 同一 complex.group 内で role が 2 つ(real+imag または mag+phase)
    • 同一 complex.group 内で {real,imag}{mag,phase} が混在しない
  3. 軸の単調性:

    • L1 インデックスの timestamp は非減少(ブロック開始位置が逆行しない)

違反時は InvalidMetadataError または CorruptDataError を推奨。


実数・複素数の表現(R0/R1 と Complex Group)

実数(REAL)

  • R0: 量子化整数(推奨)
    • 実数 xX = round(x * num/den) に量子化し、スナップショットは varint(X)、変化列は signed_varint(ΔX) で保存。
    • メタデータに signal.<id>.scale.num/den を必ず出力(例:電圧なら 1µV 刻み → num=1_000_000, den=1)。
  • R1: IEEE754 ビットキャスト(互換)
    • float64uint64 にビット再解釈して保存(スナップショット varint(bits)、変化列 signed_varint(Δbits))。
    • 圧縮効率はデータ依存。Zstd 等のフレーム圧縮と併用が現実的。

複素数(AC 等)

複素数は2本の実数信号をペアにして表現します。

グループ化:

  • signal.<id>.complex.group同一のグループ ID(文字列、例: "g1", "v_out_ac")を設定
  • グループ ID は任意の文字列(推奨: 信号名ベース、または連番 "g1", "g2", ...)
  • 同一グループに属する信号は必ず 2 本(real+imag または mag+phase)

Role 指定:

  • signal.<id>.complex.role に以下のいずれかを設定:
    • "real" + "imag": 直交座標(推奨、数値安定)
    • "mag" + "phase": 極座標(phase の単位は "deg" 推奨、"rad" 可)

制約:

  • 同一グループ内で role の重複禁止(real が 2 つある、など)
  • 同一グループ内で {real,imag}{mag,phase} の混在禁止
  • ビューア側での相互変換は可能(real/imag ⇔ mag/phase)

例(AC 解析の電圧):

# ノード "vout" の AC 解析結果(real/imag 表現)
signal.5.name=vout.real
signal.5.quantity=voltage
signal.5.unit=V
signal.5.complex.group=vout_ac
signal.5.complex.role=real
signal.5.scale.num=1000000
signal.5.scale.den=1

signal.6.name=vout.imag
signal.6.quantity=voltage
signal.6.unit=V
signal.6.complex.group=vout_ac  # 同じグループ ID
signal.6.complex.role=imag
signal.6.scale.num=1000000
signal.6.scale.den=1

phase の量子化:

  • unit=deg の場合: scale.num=1000000, scale.den=1 で µdeg 精度(推奨)
  • unit=rad の場合: 適切なスケールで量子化(例: 1e-9 rad 精度)

解析マッピング(TRAN / DC / AC / NOISE / OP / Monte Carlo)

  • TRAN:file.analysis=tran, axis.0.type=time, axis.0.unit=s
  • AC:file.analysis=ac, axis.0.type=freq, axis.0.unit=Hz, 複素ペア(real/imag もしくは mag/phase)
  • DC:file.analysis=dc, axis.0.type=param, axis.0.unit=<掃引量の単位>, axis.0.name=<例: V1.dc>
  • NOISE:file.analysis=noise, axis.0.type=freq, axis.0.unit=Hz(一般的)
  • OP:file.analysis=op, 単一点は Axis0 を index とし start=end=0
  • Monte Carlo:file.analysis=montecarlo, axis.0.type=mc, axis.0.unit=idx(試行番号)

例(TRAN 電圧波形のメタデータ抜粋):

file.analysis=tran
axis.0.type=time
axis.0.unit=s
file.generator=sukimaspice 0.6.x
signal.1.quantity=voltage
signal.1.unit=V
signal.1.kind=node
signal.1.node=out
signal.1.scale.num=1000000   # 1 µV increments
signal.1.scale.den=1

ブロック/インデックス/ストリーミング(大規模データのために)

L3/L2/L1 の階層インデックス

  • ブロック独立性を維持しつつ、範囲検索は L3→L2→L1 の順でスキャン。
  • L1 は必須(ブロックがある場合)。[timestamp: u64][data_offset: u64][size: u64][block_id: u32]
  • L1 の timestamp は Axis0 座標のブロック開始位置。

ストリーミングフレーム(規範)

flags.bit6=1 のとき:

frame := [uncompressed_size: uint32 LE] [payload]

uncompressed_size := len(block_bytes)  # 圧縮前の元データサイズ
payload :=
  compression_type=0 → block_bytes(非圧縮、len(payload) == uncompressed_size)
  compression_type>0 → compress(block_bytes, type)(圧縮済み、len(payload) < uncompressed_size)

L1.size := len(frame)  # 4 + len(payload)

重要: uncompressed_size圧縮前の元データサイズを記録します。これにより:

  • リーダーは展開バッファを事前に確保可能
  • compression_type=0 でも 4B ヘッダーがあり、統一された読み出しパスで処理できる
  • 圧縮データの整合性チェックが可能(展開後のサイズが一致するか検証)

最小実装フロー(擬似コード)

ライタ(TRAN, R0 の例)

write_header(version=3.4, flags=STREAMING|ANALOG_EXTENSIONS, ...)
write_metadata({
  'file.analysis':'tran', 'axis.0.type':'time', 'axis.0.unit':'s',
  'signal.1.quantity':'voltage', 'signal.1.unit':'V',
  'signal.1.scale.num':'1000000', 'signal.1.scale.den':'1',
})
write_scope_dir(...); write_signal_dir(...)

for each block:
  block_bytes = encode_block(start_coord, snapshots_R0, changes_R0)
  if flags & STREAMING:
    payload = block_bytes if compression_type==0 else compress(block_bytes)
    frame = u32_le(len(block_bytes)) + payload
    write(frame); l1.size = len(frame)
  else:
    write(block_bytes); l1.size = len(block_bytes)
  append_l1_index(timestamp=start_coord, data_offset=..., size=l1.size)

リーダ(共通)

f.seek(l1.data_offset)
frame_or_block = f.read(l1.size)
if flags & STREAMING:
  uncompressed = u32_le(frame_or_block[:4])
  payload = frame_or_block[4:]
  block = payload if compression_type==0 else decompress(payload, compression_type, uncompressed)
else:
  block = frame_or_block

decode_block(block)  # スナップショット/変化列。R0 なら quantize/Δ を復元

互換性とエラー条件(実装時の落とし穴)

リーダー/ライター実装時に検証すべきエラー条件:

条件 エラー型 説明
magic != 'SSF\x04' InvalidMagicError SSF ファイルではない
version != 3.4 UnsupportedVersionError 厳密運用を推奨(警告または拒否)
セクション重複 CorruptHeaderError offset/size の整合性チェック
STREAMING 時の size 不一致 CorruptDataError L1.size と実読込長の不一致
Axis0 非単調 CorruptDataError ブロック開始位置が逆行
必須メタデータ不足 InvalidMetadataError flags.bit7=1 時の file.analysis など
複素数グループ不整合 InvalidMetadataError role 重複、real/imag と mag/phase 混在

推奨動作:

  • version != 3.4: 警告を出して読み込みを試みる(将来の V5 を考慮)、または拒否
  • メタデータ不足: エラーとするか、デフォルト値で補完(ログに記録)
  • 複素数不整合: 読み込み時にグループ単位で検証、エラーまたは警告

圧縮タイプの目安

重要: 以下の圧縮率は参考値であり、仕様の一部ではありません。実際の圧縮率はデータ特性(信号の複雑さ、サンプリングレート、ノイズレベルなど)に強く依存します。

圧縮方式 目安圧縮率 特徴
LZ4 (type=1) 1.5–2.5× 高速、低CPU負荷、ストリーミング向き
Zlib (type=2) 2–5× 標準的、広く対応
Zstd (type=3) 2–8× 推奨(level=3)、バランス良好

実測データ(sukimaspice RC lowpass, 10,000 points):

  • 非圧縮(R0 量子化のみ): 100%
  • R0 + LZ4: 42% (2.4×)
  • R0 + Zstd (level=3): 23% (4.3×)
  • R0 + Zstd (level=19): 18% (5.6×)

推奨戦略:

  • ストリーミング/リアルタイム: LZ4(低レイテンシ)
  • アーカイブ/長期保存: Zstd level=3(バランス)
  • 極限圧縮: Zstd level=19(CPU コスト高)

R0(量子化)との併用でさらに効果が高まります。R1(IEEE754 ビットキャスト)の場合は Zstd 推奨。


Q&A

Q. AC は Real/Imag と Mag/Phase のどちらで保存すべき?

A. どちらも可ですが、同一 complex.group 内で混在は禁止。ビューア側で相互変換は可能です。運用上は Real/Imag を推奨します(数値安定性・変化追跡が明確、量子化の精度維持が容易)。

Q. R0 と R1 の使い分けは?

A. 既定は R0(量子化)推奨。単位あたりの分解能(µV/µA など)を決め、scale.num/den を必ず記録します。利点:

  • 差分エンコーディングで高圧縮率
  • 物理的な意味が明確
  • 変化追跡が容易

R1(IEEE754 ビットキャスト)は以下の場合のみ:

  • ビット完全互換性が必要(既存ツールとの連携)
  • 実装簡便さを優先
  • 圧縮効率は Zstd 前提で要検討

Q. 非圧縮でもフレーム必須なのはなぜ?

A. 「読み出し経路を 1 本化」するためです。compression_type=0 でも 4B ヘッダーがあれば、伸長ブランチを条件分岐なしに処理できます。これにより:

  • コードがシンプルになる
  • 将来的な圧縮方式の追加が容易
  • uncompressed_size で整合性チェックが可能

Q. L2/L3 インデックスはいつ使うべき?

A. データサイズが大きい場合(目安: ブロック数 > 1000、ファイルサイズ > 100MB)に有効です。L2 は 64 ブロック単位、L3 は 4096 ブロック単位でジャンプ可能にし、範囲検索を高速化します。小規模データでは L1 のみで十分です。

Q. NOISE 解析の単位 V^2/Hz はどう扱う?

A. ノイズ密度は quantity=noise_density, unit=V^2/Hz または A^2/Hz で表現します。総ノイズ(積分値)の場合は quantity=voltage または current, unit=V または A で通常の信号として扱います。

Q. Monte Carlo で時間軸もある場合は?

A. axis.0.type=mc (試行番号)、axis.1.type=time として 2 軸データを表現します。ブロックは MC 試行ごとに分割し、各ブロック内で時間変化をエンコードします。メタデータで両軸の説明を明記してください。


クイックリファレンス(実装時に見る表)

必須メタデータキー(flags.bit7=1 の場合)

カテゴリ キー 必須
ファイル file.analysis "tran", "ac", "dc", "noise", "op"
file.generator 推奨 "sukimaspice 0.6.0"
file.title 任意 "RC Lowpass Filter"
Axis0 axis.0.type "time", "freq", "param", "mc"
axis.0.unit "s", "Hz", "V", "idx"
axis.0.name 推奨 "V1.dc" (DC sweep)
axis.0.scale.num/den 任意 R0 使用時のみ
信号 signal.<id>.quantity 推奨 "voltage", "current", "noise_density"
signal.<id>.unit 推奨 "V", "A", "V^2/Hz"
signal.<id>.kind 任意 "node", "device"
signal.<id>.node kind=node時 "vout"
signal.<id>.device kind=device時 "R1"
signal.<id>.scale.num/den R0時必須 1000000 / 1 (1µV)
signal.<id>.complex.group 複素数時 "vout_ac"
signal.<id>.complex.role 複素数時 "real", "imag", "mag", "phase"

ヘッダー固定値

フィールド 備考
magic 'SSF\x04' 固定(4バイト)
version_major 3 固定
version_minor 4 固定(V4.3 = version 3.4)
compression_type 0/1/2/3 NONE/LZ4/Zlib/Zstd
バイトオーダー Little Endian 全整数フィールド

おわりに

本記事では SSF V4.3 の設計思想と実装要点を、V4 系の互換性を保ちつつ SPICE 拡張を自然に取り込むという観点で解説しました。次回は、SSF を読み込んで高速に描画・比較・検算するビューア/ツールチェーン設計(インデックスの活用や部分読み出し、複素数可視化の UX)を掘り下げる予定です。


Appendix:varint / ZigZag エンコーディング

varint(符号なし整数)

エンコード規則:

  • 各バイトの下位 7 ビットがデータ、最上位ビット (MSB) が継続フラグ
  • MSB = 1: 次のバイトが続く
  • MSB = 0: 最終バイト(これで終了)
  • Little Endian バイト順(下位バイトから先に出力)
def encode_varint(value: int) -> bytes:
    """Encode unsigned integer as varint (Little Endian)"""
    if value < 0:
        raise ValueError("varint requires non-negative integer")

    out = bytearray()
    while True:
        b = value & 0x7F  # 下位 7 ビット取得
        value >>= 7       # 7 ビット右シフト
        if value != 0:
            out.append(b | 0x80)  # 継続フラグ ON(MSB=1)
        else:
            out.append(b)         # 最終バイト(MSB=0)
            break
    return bytes(out)

def decode_varint(data: bytes, offset: int = 0) -> tuple[int, int]:
    """Decode varint and return (value, bytes_consumed)"""
    result = 0
    shift = 0
    i = offset

    while i < len(data):
        b = data[i]
        result |= (b & 0x7F) << shift
        i += 1
        if (b & 0x80) == 0:  # MSB=0 なら終了
            break
        shift += 7

    return result, i - offset

signed_varint(符号付き整数: ZigZag エンコーディング)

ZigZag マッピング: 符号付き整数を符号なし整数に変換

  • 0 → 0, -1 → 1, 1 → 2, -2 → 3, 2 → 4, ...
  • 小さな絶対値の整数(正負問わず)が小さな varint になる

標準的な ZigZag 実装:

def encode_signed_varint(value: int) -> bytes:
    """ZigZag encode signed integer, then varint encode"""
    # ZigZag mapping: n -> 2*n (if n>=0), -n-1 -> 2*(-n-1)+1 (if n<0)
    # Equivalent to: (n << 1) ^ (n >> 31) for 32-bit
    #                (n << 1) ^ (n >> 63) for 64-bit
    # Python safe version (works for arbitrary precision int):
    zz = (abs(value) << 1) if value >= 0 else (((-value) << 1) - 1)
    return encode_varint(zz)

def decode_signed_varint(data: bytes, offset: int = 0) -> tuple[int, int]:
    """Decode signed varint (ZigZag + varint)"""
    zz, consumed = decode_varint(data, offset)
    # Reverse ZigZag: 0->0, 1->-1, 2->1, 3->-2, 4->2, ...
    value = (zz >> 1) if (zz & 1) == 0 else -((zz + 1) >> 1)
    return value, consumed

64-bit 固定長での ZigZag(C++/固定幅整数用):

def zigzag_encode_i64(n: int) -> int:
    """ZigZag encode for signed 64-bit integer"""
    # Assumes n is in range [-2^63, 2^63-1]
    return (n << 1) ^ (n >> 63)

def zigzag_decode_i64(zz: int) -> int:
    """ZigZag decode for signed 64-bit integer"""
    return (zz >> 1) ^ (-(zz & 1))

使用例:

# 符号なし
assert encode_varint(0) == b'\x00'
assert encode_varint(127) == b'\x7f'
assert encode_varint(128) == b'\x80\x01'  # 0x80 (継続) + 0x01
assert encode_varint(300) == b'\xac\x02'  # 0xac (継続) + 0x02

# 符号付き (ZigZag)
assert encode_signed_varint(0) == b'\x00'
assert encode_signed_varint(-1) == b'\x01'   # ZigZag: -1 -> 1
assert encode_signed_varint(1) == b'\x02'    # ZigZag: 1 -> 2
assert encode_signed_varint(-2) == b'\x03'   # ZigZag: -2 -> 3
assert encode_signed_varint(64) == b'\x80\x01'  # ZigZag: 64 -> 128
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?