potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

C# 9.0 ushortのushort範囲制限加減算

ushort型の加減算のはみ出し

ushort型はint型として加減算されるため、計算結果がushortの範囲(0-0xffff)をはみ出す場合があります。単純にunchecked((ushort)...)を適用した場合、はみ出した値は... & 0xffffの結果となり想定外の値が得られる場合があります。

Console.WriteLine(unchecked((ushort)(2u - 5u))); // 65533
Console.WriteLine(unchecked((ushort)(0xffffu + 5)).ToString("X")); // 4

switch式による加減算はみ出しの抑制

このような加減算によるはみ出しを回避するには次の関数が利用できます。減算結果が0未満の場合は0、加算結果が0xffff超過の場合は0xffffに収めます。switch式(パターンマッチング)を使うことで計算回数を減らしています。

using System;

ushort ClampDiffUInt16(ushort x, ushort y)
    => (x - y) switch { int z when z > 0 => (ushort)z, _ => 0 };
ushort ClampAddUInt16(ushort x, ushort y)
    => (x + y) switch { int z when z > 0xffffu => (ushort)0xffffu, int z => (ushort)z };

Console.WriteLine(ClampDiffUInt16(2, 5));  // 0
Console.WriteLine(ClampAddUInt16((ushort)0xffffu, 5).ToString("x")); // ffff

isvarパターンによるはみ出し抑制

三項演算子の条件式にisvarパターンを含めても1行で書けます。

using System;

ushort ClampDiffUInt16(ushort x, ushort y) => (ushort)((x - y is var z && z > 0) ? z : 0);
ushort ClampAddUInt16(ushort x, ushort y) => (ushort)((x + y is var z && z > 0xffff) ? 0xffff : z);

Console.WriteLine(ClampDiffUInt16(2, 5));  // 0
Console.WriteLine(ClampAddUInt16((ushort)0xffffu, 5).ToString("x")); // ffff

Math.Clampによる例

Math.Clampを使うこともできます。ただし、インライン化されない場合は呼び出しコストがかかること、無駄な比較(加算の場合は0との比較、減算の場合は0xffffとの比較)が増えることに注意が必要かと思います。

ushort ClampDiffUInt16(ushort x, ushort y)
    => (ushort)Math.Clamp(x - y, 0, 0xffff);
ushort ClampAddUInt16(ushort x, ushort y)
    => (ushort)Math.Clamp(x + y, 0, 0xffff);