Search code examples
c#linear-interpolationmath.netnumerics

Incorrect linear interpolation with large x values using Math.Net Numerics


I'm trying to use Math.NET Numerics to do interpolation of a DateTime - Value series. I started off with linear interpolation, but am getting some very off looking results.

Running this test:

public class script{

public void check_numerics()
  {
    var ticks = DateTime.Now.Ticks;
    Console.WriteLine("Ticks: " + ticks);
    var xValues = new double[] { ticks, ticks + 1000, ticks + 2000, ticks + 3000, ticks + 4000, ticks + 5000 };
    var yValues = new double[] {0, 1, 2, 3, 4, 5};
    var spline = Interpolate.LinearBetweenPoints(xValues, yValues);
    var ticks2 = ticks;
    for (int i = 0; i < 10; i++)
    {
      ticks2 += 500;
      Console.WriteLine(spline.Interpolate(ticks2));
    }
  }
}

This gives:

Ticks: 635385235576843379
0.5
1
1.5
2
2.42857142857143 // this should be 2.5
3
3.5
4
4.5
5

Notice that 2.4285 is fairly wrong. At a different time (different ticks value) a different value will be "wrong". Is there a "bug" with large x values in Math.NET or am I expecting too much?


Solution

  • Just confirming the comments above as the maintainer of Math.NET Numerics:

    The distance (epsilon) between the closest numbers of this magnitude that can be represented at double precision is 128:

    Precision.EpsilonOf(ticks); // 128
    

    This means that if you add or substract 128/2-1 = 63 from this number, you get back exactly the same number:

    long ticks = DateTime.Now.Ticks // 635385606515570758
    ((long)(double)ticks)           // 635385606515570816
    ((long)(63+(double)ticks))      // 635385606515570816
    ((long)(-63+(double)ticks))     // 635385606515570816
    ((long)(65+(double)ticks))      // 635385606515570944
    ((long)(-65+(double)ticks))     // 635385606515570688
    

    The incremental steps of 500 are very close to these 128 and effectively get rounded to multiples of 128 (e.g. 512), so it's not surprising that there will be some artifacts like this.

    If you reduce the time precision to milliseconds by dividing the ticks by 10000, as suggested by James, you get an epsilon of 0.0078125, and accurate results even for steps of 1 instead of 500.

    Precision.EpsilonOf(ticks/10000); // 0.0078125