15

免責事項: IEEE 浮動小数点変数では 0.025 を正確に表すことができないため、丸めによって期待どおりの値が返されない可能性があることはわかっています。それは私の質問ではありません!


.NET で VBA 算術演算子の動作をシミュレートすることは可能ですか?

たとえば、VBA では、次の式は次のようになります3

Dim myInt32 As Long
myInt32 = CLng(0.025 * 100)      ' yields 3

ただし、VB.NET では、次の式が生成されます2

Dim myInt32 As Integer
myInt32 = CInt(0.025 * 100)      ' yields 2

仕様によると、どちらも同じ値を返す必要があります。

  • Long (VBA) と Integer (VB.NET) は 32 ビット整数型です。
  • VBA 仕様によると、CLng は Long への Let 型強制を実行し、数値型間の Let 型強制は Banker の丸めを使用します。VB.NET の CIntも同様です。
  • 0.025どちらの場合も倍精度 IEEE 浮動小数点定数です。

したがって、浮動小数点乗算演算子または整数変換演算子の一部の実装の詳細が変更されました。ただし、従来の VBA システムとの互換性のために、.NET アプリケーションで VBA の数学的な動作を再現する必要があります (たとえそれが間違っていたとしても)。

それを行う方法はありますか?Microsoft.VBA.Math誰かがライブラリを書きましたか?または、正確なVBAアルゴリズムがどこかに文書化されているので、自分でそれを行うことができますか?

4

2 に答える 2

15

VBA と VB.NET の動作は異なります。これは、VBA が中間浮動小数点計算に80 ビットの「拡張」精度Doubleを使用するため ( 64 ビット型であっても)、VB.NET は常に 64 ビット精度を使用するためです。80 ビット精度を使用する場合、0.025 * 100 の値は 2.5 よりわずかに大きいためCLng(0.025 * 100)、3 に切り上げます。

残念ながら、VB.NET は 80 ビット精度の演算を提供していないようです。回避策として、Visual C++ を使用してネイティブの Win32 DLL を作成し、P/Invoke を介して呼び出すことができます。例えば:

#include <cmath>
#include <float.h>

#pragma comment(linker, "/EXPORT:MultiplyAndRound=_MultiplyAndRound@16")

extern "C" __int64 __stdcall MultiplyAndRound(double x, double y)
{
    unsigned int cw = _controlfp(0, 0);
    _controlfp(_PC_64, _MCW_PC); // use 80-bit precision (64-bit significand)
    double result = floor(x * y + 0.5);
    if (result - (x * y + 0.5) == 0 && fmod(result, 2))
        result -= 1.0; // round down to even if halfway between even and odd
    _controlfp(cw, _MCW_PC); // restore original precision
    return (__int64)result;
}

そしてVB.NETでは:

Declare Function MultiplyAndRound Lib "FPLib.dll" (ByVal x As Double, ByVal y As Double) As Long

Console.WriteLine(MultiplyAndRound(2.5, 1))       ' 2
Console.WriteLine(MultiplyAndRound(0.25, 10))     ' 2
Console.WriteLine(MultiplyAndRound(0.025, 100))   ' 3
Console.WriteLine(MultiplyAndRound(0.0025, 1000)) ' 3
于 2013-10-21T16:19:16.250 に答える
5

VBA が Banker の丸めを使用することになっていることを考えると、バグが実際には VBA 側にあることは一目瞭然です。バンカーの丸めは中点 (.5) で丸められるため、結果の桁は偶数になります。したがって、バンカーの丸めを正しく行うには、2.5を 3 ではなく2 に丸める必要があります。これは、VBA の結果ではなく、.Net の結果と一致します。

ただし、現在削除されている回答から引き出された情報に基づいて、VBA でこの結果を確認することもできます。

Dim myInt32 As Integer
myInt32 = CInt(2.5) ' 2
myInt32 = CInt(0.025 * 100) ' 3

これにより、VBA の丸めが正しいように見えますが、乗算演算の結果は 2.5 より大きくなります。もはや中間点ではないため、銀行家のルールは適用されず、切り上げて 3 になります。

したがって、この問題を解決するには、VBA コードがその乗算命令で実際に何を行っているかを把握する必要があります。文書化されている内容に関係なく、観察結果は、VBA がこの部分を .Net とは異なる方法で処理していることを証明しています。何が起こっているのかを正確に把握できれば、運が良ければその動作をシミュレートできます。

考えられるオプションの 1 つは、浮動小数点数の古いスタンバイに戻ることです。中間点の小さなデルタ内にいるかどうかを確認し、そうであれば、単に中間点を使用します。これを行うための(テストされていない)単純なコードを次に示します。

Dim result As Double = 0.025 * 100
Dim delta As Double = Double.Epsilon
Dim floor As Integer = Math.Floor(result)
If Math.Abs(result - (CDbl(floor) + 0.5)) <= delta Then
   result = floor + 0.5
End

テストされていないことを強調します。この時点で、コンピューターの小さな丸め誤差による結果に既におかしな処理を行っているからです。このような状況での素朴な実装は、十分である可能性は低いです。少なくとも、デルタには 3 倍または 4 倍のイプシロンを使用することをお勧めします。また、このコードから期待できる最善のことは、VBA を強制的に .Net に一致させることができることです。実際に求めているのはその逆です。

于 2013-10-21T16:31:38.447 に答える