空腹おやじのログと備忘録

VBA(主にExcel)でいろいろな実験的な事とか、Linuxのコマンドとか設定とかについて忘れないように、あれこれと・・・

【VBA】特定の範囲内の任意の整数をインクリメント、デクリメントして次の値を求める

条件

  • 任意の3つの整数において、以下の条件を満たす事(負の値があっても可)
    • 最小値 <= 現在値 <= 最大値
  • 範囲の最大値をインクリメントした場合、範囲の最小値を返す事。
  • 範囲の最小値をデクリメントした場合、範囲の最大値を返す事。

求める式

インクリメント、デクリメントというからには、普通に考えれば増減値は1なので、以下のようになる。

要素数 = 最大値 - 最小値 + 1

'インクリメントする場合
次の値 = ( ( 現在値 + 1 - 最小値 ) Mod 要素数 ) + 最小値

'デクリメントする場合
次の値 = ( ( 現在値 + ( 要素数 - 1 ) - 最小値 ) Mod 要素数 ) + 最小値
補足

インクリメントの場合
現在値に1を加算し、要素数の剰余を利用する。
範囲の最小値が0でない場合、最小値を0に補正して剰余を求めた後、補正した分を戻す。

デクリメントの場合
インクリメントでは、要素数に1を加算したが、デクリメントの場合は、
(要素数 - 1 )を加算し、同様に計算する。

増減値が2以上の場合、前述の式の1の部分を変えれば良い。

要素数 = 最大値 - 最小値 + 1

'インクリメントする場合
次の値 = ( ( 現在値 + 加算する値 - 最小値 ) Mod 要素数 ) + 最小値

'デクリメントする場合
次の値 = ( ( 現在値 + ( 要素数 - 減算する値 ) - 最小値 ) Mod 要素数 ) + 最小値

ただし、以下の条件を満たす必要がある。

加算する値 >= 0
減算する値 >= 0
かつ
減算する値 <= 要素数

コード

Private Const ERROR_INVALID_PARAMETER   As Long = 87


Public Function incrementBetweenNM(ByVal lCurrentValue As Long, _
                                   ByVal lMinValue As Long, _
                                   ByVal lMaxValue As Long, _
                          Optional ByVal lOffsetValue As Long = 1) As Long

    Dim lElements   As Long

    If lMinValue > lMaxValue Then
        Err.Raise ERROR_INVALID_PARAMETER
    ElseIf lMaxValue < lCurrentValue Then
        Err.Raise ERROR_INVALID_PARAMETER
    ElseIf lMinValue > lCurrentValue Then
        Err.Raise ERROR_INVALID_PARAMETER
    End If

    If lOffsetValue < 0 Then
        Err.Raise ERROR_INVALID_PARAMETER
    ElseIf lOffsetValue > lMaxValue - lMinValue + 1 Then
        Err.Raise ERROR_INVALID_PARAMETER
    End If

    lElements = lMaxValue - lMinValue + 1

    incrementBetweenNM = ((lCurrentValue + lOffsetValue - lMinValue) Mod lElements) + lMinValue

End Function

Public Function decrementBetweenNM(ByVal lCurrentValue As Long, _
                                   ByVal lMinValue As Long, _
                                   ByVal lMaxValue As Long, _
                          Optional ByVal lOffsetValue As Long = 1) As Long

    Dim lElements   As Long

    If lMinValue > lMaxValue Then
        Err.Raise ERROR_INVALID_PARAMETER
    ElseIf lMaxValue < lCurrentValue Then
        Err.Raise ERROR_INVALID_PARAMETER
    ElseIf lMinValue > lCurrentValue Then
        Err.Raise ERROR_INVALID_PARAMETER
    End If

    If lOffsetValue < 0 Then
        Err.Raise ERROR_INVALID_PARAMETER
    ElseIf lOffsetValue > lMaxValue - lMinValue + 1 Then
        Err.Raise ERROR_INVALID_PARAMETER
    End If

    lElements = lMaxValue - lMinValue + 1

    decrementBetweenNM = ((lCurrentValue + (lElements - lOffsetValue) - lMinValue) Mod lElements) + lMinValue

End Function

実行例

テストコード
Public Sub getNext()

    Dim l As Long
    Dim u As Long
    Dim i As Long

    l = -3
    u = 2

    Debug.Print "最小値:" & l
    Debug.Print "最大値:" & u
    Debug.Print ""

    Debug.Print "現在値", "次の値", "前の値"

    For i = l To u
        Debug.Print i, incrementBetweenNM(i, l, u), decrementBetweenNM(i, l, u)
    Next

End Sub
実行結果
call getNext
最小値:-3
最大値:2

現在値        次の値        前の値
-3            -2             2 
-2            -1            -3 
-1             0            -2 
 0             1            -1 
 1             2             0 
 2            -3             1 

おまけのテスト その1

call getNext
最小値:1
最大値:1

現在値        次の値        前の値
 1             1             1 

最大値 = 最小値 だと、次の値も前の値も、みんな同じ・・・
あたりまえか。

おまけのテスト その2

Debug.Print i, incrementBetweenNM(i, l, u, 2), decrementBetweenNM(i, l, u, 3)

として、加算、減算する値を変えてみると

call getNext
最小値:-3
最大値:2

現在値        次の値        前の値
-3            -1             0 
-2             0             1 
-1             1             2 
 0             2            -3 
 1            -3            -2 
 2            -2            -1