none
BigInteger の計算(四則演算など)結果を小数点型で欲しいのですが,どのような方法がありますか? RRS feed

  • 質問

  • BigInteger の計算(四則演算など)結果を小数点型で欲しいのですが,どのような方法がありますか?

    Dim Int128Max As System.Numerics.BigInteger = 2 ^ 127 - 1
    Dim d As Double = (Int128Max / 2) / Int128Max

    変数 d に 0.5 という値を期待しているのですが,0 となってしまいます。

    2016年1月20日 4:17

回答

  • VB .NET には割り算の演算子が2種類あります。
    1つは「 / 」でこれは演算するときに分子と分母をdoubleに変換してから割り算を行って、結果もdoubleになります。
    もう一つは「 \ 」で、こちらは変換されずにそのまま演算するため、両方とも整数であれば端数切捨ての割り算になり、結果も整数になります。
    これに対して、BigIntegerの割り算はVBの割り算のように2種類の方法が定義されているわけではないので、整数のままの割り算で端数切り捨てになり、結果も整数になります。
    ですからVB.Netの「 / 」と同じような考えでBigIntegerの割り算を行うと異なる結果になってしまいます。

    あと、2^127-1とやってますが、これは2^127という大きな値の結果のdouble型から1という小さい値を引いているために、差の桁数がdouble型の有効桁数を超過しており、情報落ちが発生しまています。
    そのため2^127から1を引いた値になっていません。これは1の位が奇数になっているかで簡単に判定できます。
    正しく演算するにはBigInteger型から1を引くという計算をする必要があります。

    また、掛け算を使う分には整数のまま計算できるので、割り算を使う回数を減らした方が演算誤差は少なくなります。
    ですから分子を2で割るよりも、分母に2を掛ける方が正しい値に近くなります。
    精度良く計算するには計算順も考慮するようにしましょう。

    Imports System.Numerics
    Imports System.Numerics.BigInteger
    Module Module1
    
        Sub Main()
    
            Dim Int128MaxError As System.Numerics.BigInteger = 2 ^ 127 - 1
            Dim Int128Max As System.Numerics.BigInteger = BigInteger.Pow(2, 127) - 1
            Console.WriteLine(Int128MaxError)
            Console.WriteLine(Int128Max)
    
            Dim d As Double = CDbl(Int128Max) / (CDbl(Int128Max) * 2)
            Console.WriteLine("d={0}", d)
    
            '余りをつかって小数点以下を別に計算する方法
            Dim 小数桁用 As BigInteger = 2 ^ 128
            Dim 余り As BigInteger = 0
            Dim dv As Double = CDbl(BigInteger.DivRem(Int128Max, Int128Max * 2, 余り))
            Dim re As Double = CDbl(BigInteger.Divide(余り * 小数桁用, Int128Max * 2)) / CDbl(小数桁用)
            d = dv + re '整数部と小数部を加算して最終結果にする
            Console.WriteLine("d={0}", d)
        End Sub
    
    End Module


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2016年1月20日 9:49
    • 回答としてマーク VB User1 2016年1月20日 13:00
    2016年1月20日 9:44

すべての返信

  • BigIntegerの「Integer」は整数です。整数の世界で 1 / 2 = 0 であり 0.5 にはなり得ません。

    2016年1月20日 4:31
  • 質問者さんは Integer の場合と同じ結果になることを期待したと思いますが、BigInteger は違う結果になるようです。

    Dim intValue As Integer = 100
    Dim d As Double = (intValue / 2) / intValue
    Console.WriteLine("d = {0}", d)
    
    Dim Int128Max As System.Numerics.BigInteger = 100
    d = (Int128Max / 2) / Int128Max 'Option Strict On は外さないとここでエラー
    Console.WriteLine("d = {0}", d)
    
    '結果は:
    'd = 0.5
    'd = 0

    何故違う結果になるかは VB.NET に詳しい方に説明をお願いしたいと思います。


    #実は、最初の方の d が 0.5 になるのはつい最近知りました。C# を使っている自分としては驚き。

    【追伸】

    肝心の質問の答えを忘れていました。

    d = CType(Int128Max / 2, Double) / CType(Int128Max, Double)
    Console.WriteLine("d = {0}", d)
    
    '結果は:
    'd = 0.5

    詳しくは以下の記事を見てください。

    BigInteger Narrowing 変換 (BigInteger to Double)
    https://msdn.microsoft.com/ja-jp/library/dd268205(v=vs.110).aspx?cs-save-lang=1&cs-lang=vb#code-snippet-2

    • 編集済み SurferOnWww 2016年1月20日 5:21 追伸追加
    2016年1月20日 5:02
  • 上記だと、「Int128Max / 2」が割り切れない時にズレると思います。

    ※「Int128Max / 2」の結果がSystem.Numerics.BigInteger型で判断される為、この時点で端数が落ちる

    d = (CType(Int128Max, Double) / 2D) / CType(Int128Max, Double)
    が良いと思います
    2016年1月20日 5:43
  • VB User1 さま よろしく。

    暗黙的な型変換 を用いずに、明示的な型変換表記 を使った方が良いと思いますし、
    個人的にはそう書きたいです。
     3-5 データ型変換  (3-5-2 明示的な型変換表記) 
     https://msdn.microsoft.com/ja-jp/library/dd314347.aspx
    つまり、左辺右辺の変数や定数を皆同じ型に変換して記述します。

    あれ、投稿したら、 これは、aviator__ さまと同じ事になりますね。 重複です。


    • 編集済み ShiroYuki_Mot 2016年1月20日 7:00 追記 と ハイパーリンク化
    2016年1月20日 6:57
  • VB .NET には割り算の演算子が2種類あります。
    1つは「 / 」でこれは演算するときに分子と分母をdoubleに変換してから割り算を行って、結果もdoubleになります。
    もう一つは「 \ 」で、こちらは変換されずにそのまま演算するため、両方とも整数であれば端数切捨ての割り算になり、結果も整数になります。
    これに対して、BigIntegerの割り算はVBの割り算のように2種類の方法が定義されているわけではないので、整数のままの割り算で端数切り捨てになり、結果も整数になります。
    ですからVB.Netの「 / 」と同じような考えでBigIntegerの割り算を行うと異なる結果になってしまいます。

    あと、2^127-1とやってますが、これは2^127という大きな値の結果のdouble型から1という小さい値を引いているために、差の桁数がdouble型の有効桁数を超過しており、情報落ちが発生しまています。
    そのため2^127から1を引いた値になっていません。これは1の位が奇数になっているかで簡単に判定できます。
    正しく演算するにはBigInteger型から1を引くという計算をする必要があります。

    また、掛け算を使う分には整数のまま計算できるので、割り算を使う回数を減らした方が演算誤差は少なくなります。
    ですから分子を2で割るよりも、分母に2を掛ける方が正しい値に近くなります。
    精度良く計算するには計算順も考慮するようにしましょう。

    Imports System.Numerics
    Imports System.Numerics.BigInteger
    Module Module1
    
        Sub Main()
    
            Dim Int128MaxError As System.Numerics.BigInteger = 2 ^ 127 - 1
            Dim Int128Max As System.Numerics.BigInteger = BigInteger.Pow(2, 127) - 1
            Console.WriteLine(Int128MaxError)
            Console.WriteLine(Int128Max)
    
            Dim d As Double = CDbl(Int128Max) / (CDbl(Int128Max) * 2)
            Console.WriteLine("d={0}", d)
    
            '余りをつかって小数点以下を別に計算する方法
            Dim 小数桁用 As BigInteger = 2 ^ 128
            Dim 余り As BigInteger = 0
            Dim dv As Double = CDbl(BigInteger.DivRem(Int128Max, Int128Max * 2, 余り))
            Dim re As Double = CDbl(BigInteger.Divide(余り * 小数桁用, Int128Max * 2)) / CDbl(小数桁用)
            d = dv + re '整数部と小数部を加算して最終結果にする
            Console.WriteLine("d={0}", d)
        End Sub
    
    End Module


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2016年1月20日 9:49
    • 回答としてマーク VB User1 2016年1月20日 13:00
    2016年1月20日 9:44
  • 佐祐理さま、SurferOnWwwさま
    aviator__さま、ShiroYuki_Motさま
    gekkaさま

    コメントいただき、ありがとうございます。

    IntegerとBigIntegerの動作の違いに面食らってしまい、型変換の必要性に気づくことができませんでした。

    VBの暗黙の型変換に慣れきってしまっており、まったくもってお恥ずかしい限りです。

    とても参考になりまして、理解も深まりました。

    この度は誠にありがとうございました。大変感謝しております。

    2016年1月20日 13:00
  • 回答している方たちはこの辺のことは前提知識とした上で書かれていると思いますが、gekkaさんが書かれているように、数値型には有効桁数や値の表現範囲や基数による表現の制限などの制約があります。

    BigIntegerのような有効桁数や表現範囲を他の数値型で確実に保持することはできませんので、型変換などを行って計算する方法でも当然ながら何らかの問題をはらんでいます。

    ※値の範囲や有効桁数によっては、正常に動作しない、オーバーフローで例外は発生する、等々

    というわけで、この辺は注意してください。

    2016年1月21日 0:48
  • > VBの暗黙の型変換に慣れきってしまっており、

    Option Strict On に設定しておくことをお勧めします。

    2016年1月21日 2:38