C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[19]: 桁不足でしたね…


(過去ログ 18 を表示中)

[トピック内 29 記事 (1 - 20 表示)]  << 0 | 1 >>

■6994 / inTopicNo.1)  Long型の乱数を生成する自作クラスをつくりたい
  
□投稿者/ ぼのぼの (72回)-(2007/08/28(Tue) 15:26:55)

分類:[.NET 全般] 

こんにちは。

業務上どうしても必要ってわけでもなく、勉強目的なのでぜんぜんいそぎでも重要でもないのですが、
引数で最小値と最大値を受け取ってLong型の乱数を返すメソッドを作りたいと思っています。
#当方の開発環境はVS2005で、言語はVBかC#のどちらかで考えてます。

Int32型ならSystem.RandomクラスのNextメソッドで取得できますが、
これだと最小値と最大値の範囲が-2,147,483,648〜2,147,483,647になってしまうので、
扱える範囲をInt64の-9,223,372,036,854,775,808〜9,223,372,036,854,775,807に拡張したいのです。

まず、Int32の範囲を超える乱数を取得する方法としては、
Int32の乱数2つを文字列として繋ぎ合わせる方法、
System.Random.NextDoubleの結果に10のべき乗をかける方法、
System.Random.NextBytesで生成したバイト列から変換する方法、
などが考えられます。

次に、入力パターンとしては、
最小値、最大値共にInt32の範囲内に収まっている場合、
最小値がInt32.MinValueより小さく最大値がInt32.MaxValueより小さい正の数の場合、
最小値、最大値共にInt32.MaxValueより大きい場合、
などなどいくつかのパターンに分けられます。
#要は正か負か、Int32の範囲に収まっているかいないかの組み合わせですね。

これらの組み合わせで、偏らない整数を高速に次々と生成するアルゴリズムとしては、
どんな方法がベストかな〜と1人考えてたら脳が煮えてきたので、スレッドを作ってみました。

自分でも引き続き考えてみますが、もし良いアイデアなどありましたらご意見ください。
よろしくお願いします。
引用返信 編集キー/
■6996 / inTopicNo.2)  Re[1]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ シャノン (207回)-(2007/08/28(Tue) 15:44:55)
No6994 (ぼのぼの さん) に返信
精度が適当でいいんであれば組み合わせも適当でいいでしょうけど、精度を気にするんであれば、SFMTを.NETにポーティングでもした方がいいんじゃないだろうか。

参考
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index-jp.html
http://www001.upp.so-net.ne.jp/isaku/rand2.html
引用返信 編集キー/
■6997 / inTopicNo.3)  Re[1]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ 魔界の仮面弁士 (392回)-(2007/08/28(Tue) 15:49:15)
No6994 (ぼのぼの さん) に返信
> 扱える範囲をInt64の-9,223,372,036,854,775,808〜9,223,372,036,854,775,807に拡張したいのです。

Dim r As New Random()
Dim b() As Byte = BitConverter.GetBytes(0L)

r.NextBytes(b)
Dim l As Long = BitConverter.ToInt64(b, 0)
引用返信 編集キー/
■6999 / inTopicNo.4)  Re[2]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ 囚人 (149回)-(2007/08/28(Tue) 15:56:49)
乱数のアルゴリズムを一から考えるのは相当骨が折れそうなので、既知のものを使ってゴニョゴニョする方がいいですよね。
C++ の boost であればメルセンヌツイスタの他にもいろいろ使えるので、C++/CLI でラップしちゃうのはどうでしょうか。

>#当方の開発環境はVS2005で、言語はVBかC#のどちらかで考えてます。

この条件を満たせませんが…。
引用返信 編集キー/
■7004 / inTopicNo.5)  Re[3]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ ぼのぼの (73回)-(2007/08/28(Tue) 17:09:22)
あ、そうか、範囲内になるように乱数を生成するのではなく、
範囲指定無しで乱数を生成してから範囲内の数値に変換すればいいのか orz

精度はそんなに気にしないのと、今回はC#2005 OR VB2005の標準機能のみで実装したいので、
乱数生成は魔界の仮面弁士さんの方法でいこうと思います。

あとは絞込みの方法を考えるのみ。

ところで試していて気づいたのですが、
Dim l As Long = Long.MinValue
l = Math.Abs(l)
とやると例外飛ぶんですね。理由はわかるけど以外と盲点…
#ちょっと脱線

引用返信 編集キー/
■7008 / inTopicNo.6)  Re[4]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ ぼのぼの (74回)-(2007/08/28(Tue) 18:04:38)
2007/08/28(Tue) 18:08:00 編集(投稿者)
とりあえず、こんなのできました。突っ込みなどありましたら遠慮なくお願いします。
#一応解決済みつけときますが、消えちゃっても構いません。

Public Class LongRandom

    Private _Random As New Random

    Public Function [Next](ByVal min As Long, ByVal max As Long) As Long
        '最小値の方が大きかったら例外を投げる
        If min > max Then Throw New ArgumentOutOfRangeException()
        '最小値と最大値が同じならその数を返す
        If min = max Then Return min
        'Long型の乱数を生成
        Dim b() As Byte = BitConverter.GetBytes(0L)
        _Random.NextBytes(b)
        Dim l As Long = BitConverter.ToInt64(b, 0)
        '値の絞込み
        If min <= l AndAlso l <= max Then
            '範囲内に収まっている場合はそのまま返す
            Return l
        Else
            '範囲内に収まっていない場合、まず絶対値の大きい方で割った余りを求める
            Dim biggerAbs As Long
            If min = Long.MinValue Then
                biggerAbs = Math.Max(Math.Abs(min + 1), Math.Abs(max))
            Else
                biggerAbs = Math.Max(Math.Abs(min), Math.Abs(max))
            End If
            l = l Mod biggerAbs
            '範囲内になったらそのまま返す
            If min <= l AndAlso l <= max Then
                Return l
            End If
            '最小値と最大値の符号が異なる場合は、-1をかけた値を返す
            If min < 0 AndAlso max <= 0 Then
                Return l * -1
            End If
            '最小値と最大値の符号が同じ場合
            Dim isMinus As Boolean = False
            If min < 0 AndAlso max < 0 Then
                isMinus = True
                If min = Long.MinValue Then min = (min + 1) * -1 Else min = min * -1
                max = max * -1
                Dim tmp As Long = max
                max = min
                min = tmp
            End If
            If l = Long.MinValue Then l = (l + 1) * -1 Else l = Math.Abs(l)
            l = min + l Mod (max - min)
            If isMinus Then
                Return l * -1
            End If
            Return l
        End If
    End Function

End Class

解決済み
引用返信 編集キー/
■7010 / inTopicNo.7)  Re[5]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ れい (78回)-(2007/08/28(Tue) 19:52:17)
2007/08/28(Tue) 19:53:27 編集(投稿者)
No7008 (ぼのぼの さん) に返信
> 2007/08/28(Tue) 18:08:00 編集(投稿者)
>
> とりあえず、こんなのできました。突っ込みなどありましたら遠慮なくお願いします。
> #一応解決済みつけときますが、消えちゃっても構いません。

一様性はいらないのですか?
この方法だと、
かなり値が偏ると思いますよ。

追記
解決済みって、継承してくれない仕様のようですね。
今まで迷惑かけてたかも。
すみません。
解決済み
引用返信 編集キー/
■7013 / inTopicNo.8)  Re[6]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ ぼのぼの (75回)-(2007/08/28(Tue) 20:58:06)
No7010 (れい さん) に返信
> 一様性はいらないのですか?
> この方法だと、
> かなり値が偏ると思いますよ。

一応これでも偏らないようにせいいっぱい工夫したつもりだったのですが(^^;
具体的に、maxとminと_Random.NextBytesの結果がどのような条件のときに偏るのでしょうか?

全カバレージを通すようなちゃんとしたテストはしていませんが、
疎通レベルの確認ではそれなりに一様性も確保できてる感じでした。

> 追記
> 解決済みって、継承してくれない仕様のようですね。
> 今まで迷惑かけてたかも。
> すみません。

自分で消しちゃってみたりしてw
引用返信 編集キー/
■7015 / inTopicNo.9)  Re[7]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ れい (81回)-(2007/08/28(Tue) 21:21:56)
No7013 (ぼのぼの さん) に返信
> ■No7010 (れい さん) に返信
>>一様性はいらないのですか?
>>この方法だと、
>>かなり値が偏ると思いますよ。
>
> 一応これでも偏らないようにせいいっぱい工夫したつもりだったのですが(^^;
> 具体的に、maxとminと_Random.NextBytesの結果がどのような条件のときに偏るのでしょうか?

ある領域に均一に分布してるものを、
他の領域に均一に分布させるのに、
切ったり張ったりしてたらどうやってもできません。

数学では実数だとか有理数個の要素があればできますが、
有限個では無理。

たとえば、

min=Long.MinValue
max=Long.MaxValue-1

の場合、1の確率が他の2倍になってます。

引用返信 編集キー/
■7016 / inTopicNo.10)  Re[8]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ ぼのぼの (76回)-(2007/08/28(Tue) 21:43:02)
No7015 (れい さん) に返信
> たとえば、
>
> min=Long.MinValue
> max=Long.MaxValue-1
>
> の場合、1の確率が他の2倍になってます。
うーんなるほど。
正直言うと、それくらいは許容範囲かな〜と妥協しちゃってたんですよねw

他に思いつく方法としては、minとmaxにLong.MinValueを足した値をUInt64型の変数に突っ込んで、
最小値+乱数%(最大値−最小値)
を求めた後Long.MinValueを差し引いてInt64型で返す、というもの。

ただ、Long.MinValueの加減をオーバーフローを発生させずに行ううまい方法が
思いつかなかったというか、それを真面目に考える気力が無かったのですよね。
#冒頭に書いた通りの状況なので、結論をいそいでない分スタンスも不真面目めです(^^;
引用返信 編集キー/
■7017 / inTopicNo.11)  Re[9]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ れい (82回)-(2007/08/28(Tue) 21:52:24)
No7016 (ぼのぼの さん) に返信
> ■No7015 (れい さん) に返信
>>たとえば、
>>
>>min=Long.MinValue
>>max=Long.MaxValue-1
>>
>>の場合、1の確率が他の2倍になってます。
> うーんなるほど。
> 正直言うと、それくらいは許容範囲かな〜と妥協しちゃってたんですよねw

min=-1
max=1

のときは?
引用返信 編集キー/
■7019 / inTopicNo.12)  Re[10]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ ぼのぼの (77回)-(2007/08/28(Tue) 22:10:38)
No7017 (れい さん) に返信
> min=-1
> max=1
>
> のときは?
!Σ( ̄□ ̄;

解決済み外して正解だった orz
つづきは後日…
引用返信 編集キー/
■7022 / inTopicNo.13)  Re[11]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ れい (84回)-(2007/08/28(Tue) 23:46:48)
No6994 (ぼのぼの さん) に返信
> 業務上どうしても必要ってわけでもなく、勉強目的なのでぜんぜんいそぎでも重要でもないのですが、
> 引数で最小値と最大値を受け取ってLong型の乱数を返すメソッドを作りたいと思っています。

乱数を作って遊ぶなら、方向は二つかな?

・MTだとか、線形合同法だとか、カオスだとか楕円方程式だとかを使って、ランダムなバイト列を作る方法を考える。
・ランダムなバイト列から、自分の用途にあった乱数に変形する方法を考える。

前者は奥が深いです。作って実際に役に立てられる人はレアですが。
後者はプログラムの勉強ですね。
アルゴリズムの本などにも載ってますので、ほとんどの答えも手に入ります。
欲しい分布の乱数をライブラリが用意してくれてないときなど、
その場で作らなきゃいけなくなるので、一度やっておくのはお勧めです。

> 自分でも引き続き考えてみますが、もし良いアイデアなどありましたらご意見ください。

そんなわけなので、たぶん、かなりの人は答えを知っています。
言っちゃったら答えになってしまいます。
自分でいろいろ考えるのが楽しいですよ。

ダメだしは得意なので、
ダメが欲しければぜひ呼んで下さい。
引用返信 編集キー/
■7041 / inTopicNo.14)  Re[12]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ ぼのぼの (78回)-(2007/08/29(Wed) 12:09:38)
No7022 (れい さん) に返信
> 前者は奥が深いです。作って実際に役に立てられる人はレアですが。
そこなんですよね。経営的視点で見ると、そこに時間を割いても会社の利益に直接結びつかない。
となると技術者としての自己満足の世界になってくるわけで、個人的に楽しめるかが重要になる。
自分の場合は、最近はこういう数学的に深いとこよりもユーザビリティとかの方に興味があるので、
ここにエネルギーをかける気力は今のところあまりないんですよね(^^;

> 後者はプログラムの勉強ですね。
> アルゴリズムの本などにも載ってますので、ほとんどの答えも手に入ります。
> 欲しい分布の乱数をライブラリが用意してくれてないときなど、
> その場で作らなきゃいけなくなるので、一度やっておくのはお勧めです。
ユーザビリティというのは何も画面の操作性に限ったことではなくて、
広義では比較的技術レベルの低い人でも読み易く理解し易いソースコードってのも含まれると思うのです。
#開発者が既存ソースを保守拡張することを「使う」と表現していいのであれば
そういう意味では魔界の仮面弁士さんが示してくださった乱数生成のコードは秀逸だと思っていて。
精度的にも今回はこれで十分という認識なので、そのまま使わせて頂いています。

> 自分でいろいろ考えるのが楽しいですよ。
> 
> ダメだしは得意なので、
> ダメが欲しければぜひ呼んで下さい。

UInt64で計算するバージョンを作ってみました。
もし穴が空いていたら遠慮なく突っ込んでください。

Public Class LongRandom

    Private _Random As New Random

    Public Function [Next](ByVal min As Long, ByVal max As Long) As Long
        '最小値の方が大きかったら例外を投げる
        If min > max Then Throw New ArgumentOutOfRangeException()
        '最小値と最大値が同じならその数を返す
        If min = max Then Return min
        'UInt64型の乱数を生成
        Dim b() As Byte = BitConverter.GetBytes(0UL)
        _Random.NextBytes(b)
        Dim ul As UInt64 = BitConverter.ToUInt64(b, 0)
        '乱数を範囲内に変換
        Dim umin As UInt64 = LongToUInt64(min)
        Dim umax As UInt64 = LongToUInt64(max)
        If umin <> UInt64.MinValue OrElse umax <> UInt64.MaxValue Then
            ul = umin + ul Mod (umax - umin + 1UL)
        End If
        'Long型に変換
        Return UInt64ToLong(ul)
    End Function

    Private Function LongToUInt64(ByVal longVal As Long) As UInt64
        Dim retval As UInt64
        If longVal >= 0 Then
            retval = Convert.ToUInt64(longVal)
            retval += Convert.ToUInt64(Long.MaxValue)
            retval += 1UL
        Else
            longVal += Long.MaxValue
            longVal += 1
            retval = Convert.ToUInt64(longVal)
        End If
        Return retval
    End Function

    Private Function UInt64ToLong(ByVal uint64Val As UInt64) As Long
        Dim retval As Long
        If uint64Val > Convert.ToUInt64(Long.MaxValue) Then
            uint64Val -= Convert.ToUInt64(Long.MaxValue)
            uint64Val -= 1UL
            retval = Convert.ToInt64(uint64Val)
        Else
            retval = Convert.ToInt64(uint64Val)
            retval -= Long.MaxValue
            retval -= 1
        End If
        Return retval
    End Function

End Class

引用返信 編集キー/
■7076 / inTopicNo.15)  Re[13]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ れい (85回)-(2007/08/29(Wed) 21:48:57)
No7041 (ぼのぼの さん) に返信
> ■No7022 (れい さん) に返信
> そこなんですよね。経営的視点で見ると、そこに時間を割いても会社の利益に直接結びつかない。
> となると技術者としての自己満足の世界になってくるわけで、個人的に楽しめるかが重要になる。
> 自分の場合は、最近はこういう数学的に深いとこよりもユーザビリティとかの方に興味があるので、
> ここにエネルギーをかける気力は今のところあまりないんですよね(^^;
>
> UInt64で計算するバージョンを作ってみました。
> もし穴が空いていたら遠慮なく突っ込んでください。

やっぱり一様性はあんまりよくないですね。

> ul = umin + ul Mod (umax - umin + 1UL)

こうやって余りを計算してしまうと、
どうしても偏りが出てしまいます。

umin = 0
umax = ULong.MaxValue*2/3

となるとULong.MaxValue/3を超える数と超えない数の出現確率は
倍違うことになります。

切ったり貼ったりでは、
どうやっても一様性が保てません。

サイコロで1〜5の乱数が欲しいとき、
6を1として扱ってしまっては、いけないんです。

・6が出たときにサイコロを振りなおす
・はじめから20面体のサイコロを使う

など、違う方法を考えないといけません。




引用返信 編集キー/
■7091 / inTopicNo.16)  Re[14]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ ぼのぼの (80回)-(2007/08/30(Thu) 10:45:42)
No7076 (れい さん) に返信
> やっぱり一様性はあんまりよくないですね。

(´・ω・`)

実をいうと、このクラスを作ろうと思った当初の目的は、
10〜1000件程度のテストデータの作成だったので、
この程度の偏りは全然許容範囲だったりします。

むしろ完全な一様性を確保するために処理速度が劣化する方が嫌なので、
とりあえず「欲しいモノはできた」というステータスにはなったのですが、
突っ込まれたまま終わるのもなんか悔しいので、もうちょっと考えてみます。
引用返信 編集キー/
■7096 / inTopicNo.17)  Re[15]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ ぼのぼの (81回)-(2007/08/30(Thu) 11:34:49)
No7076 (れい さん) に返信
>>ul = umin + ul Mod (umax - umin + 1UL)
> 
> こうやって余りを計算してしまうと、
> どうしても偏りが出てしまいます。
> 
> umin = 0
> umax = ULong.MaxValue*2/3
> 
> となるとULong.MaxValue/3を超える数と超えない数の出現確率は
> 倍違うことになります。

えーと、この状況をわかりやすく図示すると。

min               max
0|--------+--------|--------|MAX

こういう状態のときは、剰余分は折り返されるイメージだから、

min               max
0|--------+--------|         MAX
  --------|
     ↑ここが2倍になってしまう

ということですね。

max-minがもうちょっと小さい場合でも

min max
0|---|---|---|---|---|---|--|MAX

端数分を折り返すと
0|---|---|---|---|---|---|   MAX
  --|
  ↑ここだけ2倍になる

min    max
0|------|------+------+------|MAX

こういう風にぴったり分割される場合はOK。


ということは、乱数が折り返しのときだけ
サイコロを振りなおすようにする、とすると…

'乱数を範囲内に変換
If umin <> UInt64.MinValue OrElse umax <> UInt64.MaxValue Then
    Dim divVal As UInt64 = umax - umin + 1UL
    While UInt64.MaxValue Mod (divVal) <> (umax - umin) _
    AndAlso Convert.ToUInt64(ul / divVal) = Convert.ToUInt64(UInt64.MaxValue / divVal)
        _Random.NextBytes(b)
        ul = BitConverter.ToUInt64(b, 0)
    End While
    ul = umin + ul Mod divVal
End If

こうかな。う〜んあってるのかな?

ループが最も多く発生するのはumax-uminがUInt64.MaxValueの半分より
ちょびっとだけ大きい値の場合だけど、
NextBytesの一様性がある程度のものならこの場合でもNextBytesを呼ぶ回数は約2倍。
と期待されるので、そんなに激しい性能劣化は無いと思われまする。

もっといい方法があるのでしょうか…

引用返信 編集キー/
■7110 / inTopicNo.18)  Re[16]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ れい (88回)-(2007/08/30(Thu) 12:42:05)
No7096 (ぼのぼの さん) に返信
> ■No7076 (れい さん) に返信
> >>ul = umin + ul Mod (umax - umin + 1UL)
>>
>>こうやって余りを計算してしまうと、
>どうしても偏りが出てしまいます。
>
> ということは、乱数が折り返しのときだけ
> サイコロを振りなおすようにする、とすると…
>

考え方は正解です。

どんなに大きなビット数の乱数でも、どんなに精度がよい乱数でも、
乱数の範囲を変えるときはかならず同じ問題に遭遇します。
普通乱数の実装は2進数の整数で組まれていますから、
任意の範囲に対して「振り直し無し」の方法は理論上ありません。

忘れがちですが、DoubleからInt型を作り出すときも同様で、
情報が足りなくならないよう気をつける必要があります。

せっかくMT使ってるのに、範囲変更でmoduloとっただけの実装をたまにみますが、
猫に小判ですね。
乱数を使うような場合、かなりの確率で統計的なデータ処理を行いますが、
このミスは統計的に効いてきます。

> こうかな。う〜んあってるのかな?

コードは…変です。

> ループが最も多く発生するのはumax-uminがUInt64.MaxValueの半分より
> ちょびっとだけ大きい値の場合だけど、
> NextBytesの一様性がある程度のものならこの場合でもNextBytesを呼ぶ回数は約2倍。
> と期待されるので、そんなに激しい性能劣化は無いと思われまする。

乱数が範囲内にある確率をAとすると、乱数を生成する回数の期待値Sは
S = A + (1-A)*(S+1) = 1/A
なので、ワーストケースでの期待値は約2回です。

NextBytesは1バイト毎にサイコロ振ってますので、
トータルで期待値として16回サイコロを振ってることになります。

> もっといい方法があるのでしょうか…

コード的にはいろいろあるでしょうが、
考え方はこれが最良です。
引用返信 編集キー/
■7112 / inTopicNo.19)  Re[13]: Long型の乱数を生成する自作クラスをつくりたい
□投稿者/ れい (89回)-(2007/08/30(Thu) 12:51:05)
2007/08/30(Thu) 12:56:43 編集(投稿者)

No7041 (ぼのぼの さん) に返信
> ■No7022 (れい さん) に返信
> ユーザビリティというのは何も画面の操作性に限ったことではなくて、
> 広義では比較的技術レベルの低い人でも読み易く理解し易いソースコードってのも含まれると思うのです。
> #開発者が既存ソースを保守拡張することを「使う」と表現していいのであれば
> そういう意味では魔界の仮面弁士さんが示してくださった乱数生成のコードは秀逸だと思っていて。
> 精度的にも今回はこれで十分という認識なので、そのまま使わせて頂いています。

見た目はもちろんですが、
コードの利用者が、「こう動くだろうな」と期待する仕様どおりに動くことも
ユーザビリティですよね。

今回の乱数の件などは、
将来あちこちで使われる可能性のある、汎用的なコードだといえます。
乱数生成に他のアルゴリズムを使っても、
その範囲を変えるのは同じルーチンでできます。

コピペなどで、作者の元を離れ、容易に伝播していきそうなコードです。
そう考えるなら、
きちんと一様性のあるアルゴリズムで実装しておくか、
一様性の無いことをきちんと認識し、コードに書いておかなくては
大変なことになりませんか?

引用返信 編集キー/
■7120 / inTopicNo.20)  Re[14]: Long型の乱数を生成する自作クラスをつくりたい
 
□投稿者/ ぼのぼの (82回)-(2007/08/30(Thu) 13:40:09)
No7112 (れい さん) に返信
> コピペなどで、作者の元を離れ、容易に伝播していきそうなコードです。
> そう考えるなら、
> きちんと一様性のあるアルゴリズムで実装しておくか、
> 一様性の無いことをきちんと認識し、コードに書いておかなくては
> 大変なことになりませんか?

そんなの関係ねぇ!と言ってしまうのは簡単ですが。

■No7110 (れい さん) に返信
> 考え方は正解です。
> 
> (中略)
> 
> コードは…変です。

やっぱり気になりますね(^^;


簡単に考えるために、仮にUInt64.MaxValueを99と仮定します。
となると、生成される乱数は0〜99の100通りあることになります。

で、例えばuminが0、umaxが9の場合はumax-umin+1=10できっちり分割されるので、
サイコロを振りなおす必要はないわけです。
この「きっちり分割されるケースかどうか」の判定に関しては、
100÷10の余りが0かどうか、で判定すれば良いのですが、
MaxValueである99に1を足すとオーバーフローが発生してしまうので、
99÷10の余りが9かどうか、という判定方法を使っています。

これをコード化すると、

    Dim divVal As UInt64 = umax - umin + 1UL
    If UInt64.MaxValue Mod (divVal) = (umax - umin) Then 振りなおす必要なし

となります。

次に、例えばuminが0、umaxが10の場合、umax-umin+1=11なので、

余  |  0  1  2  3  4  5  6  7  8  9 10
----+----------------------------------
商 0|  0  1  2  3  4  5  6  7  8  9 10
   1| 11 12 13 14 15 16 17 18 19 20 21
   2| 22 23 24 25 26 27 28 29 30 31 32
   :
   8| 88 89 90 91 92 93 94 95 96 97 98
   9| 99

余りが0のケースだけ1回多い。つまり99のときだけ
サイコロが振りなおされれば良いことになります。

これを拡げて考えると、「最下行のときは振りなおす」となり、
この「最下行かどうか」の判定は「商がMaxValueを割った商と同じか」でできるので、

    Dim divVal As UInt64 = umax - umin + 1UL
    If ul / divVal = UInt64.MaxValue / divVal Then 振りなおし!

というコードが出てきます。
ただし、VBだと/演算子は小数の結果を返すので、実際には比較前に整数化しないといけません。
#そういえば、\演算子なんてのがあったことに今気がついた

つまり、「きっちり分割できないパターンでかつ最下行なら振りなおせ」となり、
その結果、

Dim divVal As UInt64 = umax - umin + 1UL
While UInt64.MaxValue Mod (divVal) <> (umax - umin) _
AndAlso Convert.ToUInt64(ul / divVal) = Convert.ToUInt64(UInt64.MaxValue / divVal)
    _Random.NextBytes(b)
    ul = BitConverter.ToUInt64(b, 0)
End While

となったのですが、どっか変でしょうか?

引用返信 編集キー/

次の20件>
トピック内ページ移動 / << 0 | 1 >>

管理者用

- Child Tree -