🐟

オーバーフローするカウンタ差分計算をC#で正しく実装する方法

に公開

はじめに

ハードウェアカウンタをソフトウェアで実装する際、一定ビット幅でオーバーフロー(ロールオーバー)するカウンタの前回値と現在値から正しく「増分」を計算することはよくある課題です。

本稿では、以下のポイントを押さえつつ、C#での実装例を交えて解説します。

  • カウンタのオーバーフロー問題
  • 分岐+剰余演算による差分計算
  • ビットマスクを用いた高速差分計算
  • 浮動小数点と整数キャストの使い分け

1. カウンタのオーバーフロー問題

ハードウェアカウンタは、Nバイト幅(例:2バイトなら0~65535)で増加し、最大値を超えると0に戻ります。
このとき、前回値 prev → 現在値 curr の差分を単純に curr - prev すると負の値になってしまい、累積値の計算が正しく行えません。

例:16bitカウンタ(0~65535)
prev = 65530
curr = 10
単純差分 = 10 - 65530 = -65520  ← 不正

2. 分岐+剰余演算による差分計算

オーバーフロー考慮の基本として、「現在値が前回値以上なら普通に引き算、そうでなければ一周分を加えて剰余を取る」方法があります。

実装例

var byteSize = 2;
var maxValue = Math.Pow(2, 8 * byteSize); // 2^(8×バイト数)

double rawDiff = ((value - lastValue) + maxValue) % maxValue;
double delta   = Math.Floor(rawDiff * Math.Pow(10, decimalPoint)) 
                 / Math.Pow(10, decimalPoint);
  • (value - lastValue) + maxValue で負の値を回避
  • % maxValue で 0~(maxValue−1) の範囲に収める
  • Math.Floor による切り捨て丸め

3. ビットマスクを用いた高速差分計算

整数キャスト+ビットマスクを使えば、剰余演算よりも高速に差分を得られます。ただし対象は整数型に限られます。

手順

  1. 整数にキャスト

    ulong currU = (ulong)Math.Round(value);
    ulong lastU = (ulong)Math.Round(lastValue);
    
  2. マスクの生成

    int bits = 16;
    ulong mask = ((ulong)1 << bits) - 1; // 下位 bits ビットを 1 に
    
  3. 差分計算

    ulong diffU = (currU - lastU) & mask;
    double delta = (double)diffU;
    
  • currU < lastU のとき自動的にラップアラウンド
  • 下位 N×8 ビットだけを残す

4. 浮動小数点 vs 整数キャスト

方法 特徴
剰余演算 (%) double のまま計算可能。可読性高いが若干遅いことも
ビットマスク (& mask) 整数キャストが必要。高速で厳密なビット操作が可能
  • 浮動小数点の値をそのまま扱いたい場合:剰余演算
  • 厳密かつ高速に処理したい場合:整数キャスト+ビットマスク

5. C#での総合サンプル

public double GetResult(double value, int decimalPoint)
{
    // カウンタ幅と最大値
    int byteSize  = 2;
    double maxVal = Math.Pow(2, 8 * byteSize);

    // 剰余演算による差分計算
    double rawDiff = ((value - lastValue) + maxVal) % maxVal;

    // 切り捨て丸め
    double scale   = Math.Pow(10, decimalPoint);
    double delta   = Math.Floor(rawDiff * scale) / scale;

    // 前回値更新
    lastValue = value;
    return delta;
}

まとめ

  • オーバーフロー を正しく扱うには「一周分を加えてから剰余」または「ビットマスク」で下位ビットを抽出
  • 浮動小数点のまま実装するなら % maxValue が手軽
  • 高速・厳密さ重視なら整数キャスト+& mask
  • lastValue == value でもゼロが返るよう、分岐や演算式を工夫する

今回は、仕様の都合で浮動小数点についても書きましたが、基本的には整数値で取り扱う方が様々な面でメリットでます。実情にあわせ、ハードウェアカウンタ差分を実装していただければと思います。

Discussion