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
is
のvar
パターンによるはみ出し抑制
三項演算子の条件式にis
のvar
パターンを含めても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);