(VB)Integerを2つのShortに分割する方法

PCソフト開発

「Integer型の値を2つのShort型に分割する」という簡単な作業でハマってしまったお話です。

経緯

とある案件で通信系のボードを経由してInteger型の値を読み書きする必要がありました。ところが、ボードに付属しているライブラリ関数は、Short型の配列しかサポートしていませんでした。

Integerのビットイメージを16bitずつ分けるだけの関数を作成しました。

VB
    Private _shorts(1) As Short

    ''Intを2つのShortに変換する関数
    Public Function IntToShorts(value As Integer) As Short()
        _shorts(0) = value And &HFFFF          ''Lower 16 bits
        _shorts(1) = (value >> 16) And &HFFFF  ''Upper 16 bits
        Return _shorts
    End Function

デバッグするまでもないと思っていたのですが、一応テストしてみたところ、Integerの値によって例外が発生するケースがありました。下記はその時のスクリーンショットです。

例外の発生箇所は、下位16bitのビットANDしてShortに代入している行でした。

この時の引数の値-123を例にして説明します。
まず、-123は16進数で:&HFFFFFF85なので、&HFFFFとビットANDをとると&H0000FF85になります。Integer同士のビットANDでは例外は発生しないので、Short型に代入する時に発生していることになります。

オーバーフローが起きる理由

調査の結果、&H0000FF85は、10進数で65413です。この値を代入する際Short型の取りうる値の範囲(-32768~32767)に収まっていないということで、オーバーフローとなっているのでした。ちなみに明示的にキャストしてみたのですが、同じ結果でした。

引数が負数の場合は、上位16ビット、下位16ビットどちらの最上位ビットも1になるので、オーバーフローになりますが、正数の場合でも下位16ビットが1になる可能性があるので、オーバーフローとなります。例)&H00008000(10進 32768)

解決策

この状況を打開する方法がわからなくて困った末、クライアントに報告したところ程なくサンプルプログラムを送っていただきました。

VB
    Private _shorts(1) As Short
    
    Public Function IntToShorts(value As Integer) As Short()
        Dim v0 As Integer = value And &HFFFF
        If (v0 And &H8000) <> 0 Then
            'Short型のマイナス値表現に変換
            v0 = (&HFFFF - v0 + 1) * -1
        End If
        
        Dim v1 As Integer = (value >> 16) And &HFFFF
        If (v1 And &H8000) <> 0 Then
            'Short型のマイナス値表現に変換
            v1 = (&HFFFF - v1 + 1) * -1
        End If

        _shorts(0) = v0
        _shorts(1) = v1

        Return _shorts
    End Function

なるほど、最上位ビットが経っている場合は、負数なのでShortの負数表現に変換するという方法です。計算としては、補数を取って-1を掛けるというシンプルな手法で、計算速度もそれほど遅くなりません。私には全く思いつかなかったので冷や汗をかきました。

C#ではどうなるか

同じことをC#でやってみるどうなるかやってみたら、意外な結果になりました。

C#
private short[] _shorts = new short[2];

//Intを2つのshortに変換
public short[] IntToShorts(int value)
{
    _shorts[0] = (short)(value & 0xFFFF);         // Lower 16 bits
    _shorts[1] = (short)((value >> 16) & 0xFFFF); // Upper 16 bits
    return _shorts;
}

VBと違うところは、intからshortへの明示的なキャストをしているところです。これは言語仕様上必要な処置です。その他は書き方の違いのみで同じです。

これを-123に対して実施しても、オーバーフロー例外は発生せず、_short[0]には-123、_short[1]には-1が入ります。

先に説明したように、VBの場合は、-123の16進数である&HFF85が65413として扱かわれるため例外となりました。これに対してC#の場合は、&HFF85は符号拡張されて-123となっているということです。

どちらも.Net Framework4.8を使用したので、同じ動作をすると思っていたのですが、言語仕様の違いのほうが優先されることがあるのですね。

やっぱり、VBよりC#のほうが私には合っていると思います。(負け惜しみ?)

(終わり)