Equivalent C# statement for this VB6 operation creating problems
Asked Answered
S

2

21

I have this code line in VB:

Dim Sqrt As Double
Sqrt = Radius ^ 2 - (CenterX - X) ^ 2

The parameters in the statement above are being passed the values below:

X=  -7.3725025845036161 Double
CenterX =0.0            Double
Radius= 8.0             Double

On executing the statement above, the value of Sqrt is below:

Sqrt    9.646205641487505   Double

Now I wrote a similar C# logic using the Math class:

double Sqrt = 0;
Sqrt = Math.Pow(Radius, 2) - Math.Pow((CenterX - X), 2);

with the same set of values, the output in C# code was:

Sqrt    9.6462056414874979  double

I need help because of this single change in C# code, all my values are getting affected. Is there anything I can do to get the similar value as of the *VB* source?

Serinaserine answered 28/9, 2016 at 7:35 Comment(8)
Is this VB6 or VB.NET? I'm not able to repro in VB.NET (.NET 4.5), I get the same result (9.6462056414874979) both times.Eachern
VB6 and partially VB.NETSerinaserine
Here's some background on the different data representations between vb6 and .net. #10147936Trixy
Like @Trixy says, this is simply due to differences between VB6 and .Net. The .Net code is more accurate, so just don't worry about this. (If the difference is causing problems for you, you must be doing something else wrong... because engineering/scientific calculations generally shouldn't be worried about such small differences)Kellby
@MatthewWatson Correction: engineering/scientific calculations generally shouldn't use Visual Basic.Bison
Here is the answer from Excel 9.64620564148759 ... gotta love floating point math.Judicator
@MatthewWhited and here's the answer from Excel if you use VBA 9.6462056414875. So basically the same.Gambeson
Sqrt is a badly named variable: it isn't a square root, it is the square.Helyn
E
32

There is a difference in the precision between the VB6 and the .NET double type. Both are IEEE 64-bit double-precision types, but the .NET CLR uses 80-bit extended-precision internally, i.e. your computations will be more accurate in .NET.

If you have to be backward-compatible with the VB6 precision, you can force your FPU (floating point unit) to use the (less accurate) 64-bit values. This can be achieved using the native _controlfp_s function.

Below is a code snippet that you can use to temporarily "downgrade" floating point precision for backward compatibility. You can use it like this:

Usage

// default floating point precision 

using (new FloatingPoint64BitPrecision())
{
    // floating-point precision is set to 64 bit
}

// floating-point precision is reset to default

Code Snippet

/// <summary>
/// This class changes floating-point precision to 64 bit
/// </summary>
internal class FloatingPoint64BitPrecision : IDisposable
{
    private readonly bool _resetRequired;

    public FloatingPoint64BitPrecision()
    {
        int fpFlags;
        var errno = SafeNativeMethods._controlfp_s(out fpFlags, 0, 0);
        if (errno != 0)
        {
            throw new Win32Exception(
                errno, "Unable to retrieve floating-point control flag.");
        }

        if ((fpFlags & SafeNativeMethods._MCW_PC) != SafeNativeMethods._PC_64)
        {
            Trace.WriteLine("Change floating-point precision to 64 bit");
            errno = SafeNativeMethods._controlfp_s(
                out fpFlags, SafeNativeMethods._PC_64, SafeNativeMethods._MCW_PC);

            if (errno != 0)
            {
                throw new Win32Exception(
                    errno, "Unable to change floating-point precision to 64 bit.");
            }

            _resetRequired = true;
        }
    }

    public void Dispose()
    {
        if (_resetRequired)
        {
            Trace.WriteLine("Resetting floating-point precision to default");
            SafeNativeMethods._fpreset();
        }
    }
}

internal static class SafeNativeMethods
{
    [DllImport("msvcr120.dll")]
    public static extern void _fpreset();

    [DllImport("msvcr120.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int _controlfp_s(
        out int currentControl, int newControl, int mask);

    public static int _CW_DEFAULT = 
        (_RC_NEAR | _PC_53 | _EM_INVALID | _EM_ZERODIVIDE | _EM_OVERFLOW 
        | _EM_UNDERFLOW | _EM_INEXACT | _EM_DENORMAL);

    public const int _MCW_EM = 0x0008001f;          // interrupt Exception Masks 
    public const int _EM_INEXACT = 0x00000001;      //   inexact (precision) 
    public const int _EM_UNDERFLOW = 0x00000002;    //   underflow 
    public const int _EM_OVERFLOW = 0x00000004;     //   overflow 
    public const int _EM_ZERODIVIDE = 0x00000008;   //   zero divide 
    public const int _EM_INVALID = 0x00000010;      //   invalid 
    public const int _EM_DENORMAL = 0x00080000;     // denormal exception mask 
                                                    // (_control87 only) 

    public const int _MCW_RC = 0x00000300;          // Rounding Control 
    public const int _RC_NEAR = 0x00000000;         //   near 
    public const int _RC_DOWN = 0x00000100;         //   down 
    public const int _RC_UP = 0x00000200;           //   up 
    public const int _RC_CHOP = 0x00000300;         //   chop 

    public const int _MCW_PC = 0x00030000;          // Precision Control 
    public const int _PC_64 = 0x00000000;           //    64 bits 
    public const int _PC_53 = 0x00010000;           //    53 bits 
    public const int _PC_24 = 0x00020000;           //    24 bits 

    public const int _MCW_IC = 0x00040000;          // Infinity Control 
    public const int _IC_AFFINE = 0x00040000;       //   affine 
    public const int _IC_PROJECTIVE = 0x00000000;   //   projective 
}
Eachern answered 28/9, 2016 at 7:59 Comment(7)
would this downgrade everywhere the double has been used?Serinaserine
You can also downgrade globally simply by placing the code from the FloatingPoint64BitPrecision constructor into your application startup routing.Eachern
do i need to place my Sqrt root function inside the using() statement ?Serinaserine
let me check and get back to you sir. Thanks for the helpSerinaserine
this when implemented in freezing my software :(Serinaserine
Try a simple sample app first and see whether it works.Eachern
Let us continue this discussion in chat.Eachern
G
5

There no need to use the Math class, simply write your calculus this way :

sqrt = Radius * Radius - (CenterX - x) * (CenterX - x);
Giarla answered 28/9, 2016 at 7:54 Comment(5)
The problem this has is that CenterX - X is calculated twice. This probably won't be an issue for this since it's only a single calculation, but in cases where this calculation is much more involved, it might be better for performance to extract this calculation to a separate line so it doesn't happen twice. Again, no issue here because subtraction isn't as intensive, but if it were something intensive, it'd be better to split it off.Nigrosine
@Nzall: Actually, the compiler should be able to optimize the expression in this case so CenterX - X is only calculated once. Still, for something far more convoluted, your advice still stands - even if it might be only for readability purposes.Lyndsaylyndsey
Does avoiding the Math class also solve the preciseness issues that Dirk points out?Frequentative
@GER: No, the precision issue is still the same, whether using Math.Pow or not.Eachern
Remark : by factoring this calculus : double res2 = (Radius - (CenterX - x)) * (Radius + (CenterX - x));, I get this as result : 9.6462056414875015Giarla

© 2022 - 2024 — McMap. All rights reserved.