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?

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Apoorv
  • 2,023
  • 1
  • 19
  • 42
  • 4
    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. – Dirk Vollmar Sep 28 '16 at 07:49
  • VB6 and partially VB.NET – Apoorv Sep 28 '16 at 07:51
  • 4
    Here's some background on the different data representations between vb6 and .net. http://stackoverflow.com/questions/10147436/calling-dll-from-vb6-and-c-sharp-give-slightly-different-results-in-double-preci – FloatingKiwi Sep 28 '16 at 07:57
  • 2
    Like @FloatingKiwi 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) – Matthew Watson Sep 28 '16 at 07:58
  • 6
    @MatthewWatson Correction: *engineering/scientific calculations generally **shouldn't use Visual Basic***. – cat Sep 28 '16 at 12:03
  • Here is the answer from Excel `9.64620564148759` ... gotta love floating point math. – Matthew Whited Sep 28 '16 at 14:21
  • @MatthewWhited and here's the answer from Excel if you use VBA `9.6462056414875`. So basically the same. – Brad Sep 28 '16 at 16:30
  • Sqrt is a badly named variable: it isn't a square root, it is the square. – cup Sep 29 '16 at 04:40

2 Answers2

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 
}
Community
  • 1
  • 1
Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
5

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

sqrt = Radius * Radius - (CenterX - x) * (CenterX - x);
romulus001
  • 318
  • 3
  • 15
  • 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. – Nzall Sep 28 '16 at 11:22
  • 3
    @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. – hoffmale Sep 28 '16 at 11:51
  • 1
    Does avoiding the Math class also solve the preciseness issues that Dirk points out? – GER Sep 28 '16 at 14:05
  • @GER: No, the precision issue is still the same, whether using `Math.Pow` or not. – Dirk Vollmar Sep 28 '16 at 19:41
  • Remark : by factoring this calculus : `double res2 = (Radius - (CenterX - x)) * (Radius + (CenterX - x));`, I get this as result : 9.6462056414875015 – romulus001 Sep 29 '16 at 08:02