Search code examples
.nettimespandouble-precision

TimeSpan and double rounding errors


I'm dealing with physical entities, such as time, speed and distance, and performing simple math, such as distance = time * speed, etc. Speed and Distance are double values rounded to 8th digit, and for Time values a .NET TimeSpan is used. Since TimeSpan rounds to a closest millisecond, I'm getting rounding errors, so I need to write custom rounding method that rounds all calculations to the closest millisecond. For example (rounding to 8th digit is omitted for simplicity):

  static void Main(string[] args) {
     var dist = 1.123451;
     var speed = 1.123452;

     var timeA = TimeSpan.FromHours(dist / speed);
     var timeB = timeA + TimeSpan.FromMilliseconds(1);

     var distA = _round(timeA.TotalHours * speed);
     var distB = _round(timeB.TotalHours * speed);

     var timeA1 = TimeSpan.FromHours(distA / speed);
     var timeB1 = TimeSpan.FromHours(distB / speed);

     // a correct implementation should give both the following vars true
     var isDistributive = distA == dist;
     var isPrecise = (timeB1 - timeA1) == TimeSpan.FromMilliseconds(1);
  }

  public static double _round(double d) {
     // Q: what should be here?
  }
  • Using Math.Round(d, 6) is distributive, but loses precision (precise up to ~4 msec)
  • Using Math.Round(d, 7) is precise to a single msec, but not distributive (distA above will be 1.1234511 != 1.123451)
  • Using the following (round to closest msec) seems to be correct, but the rounding code itself introduces its own double precision errors:

      public static double _round(double d) {
        var pre = 3600000.0;
        return Math.Round(d * pre) / pre;
      }
    

Thanks, Boris.


Solution

  • Jon Skeet is likewise unhappy with .Net time calculations, and working on a project called noda-time, which I believe might solve this problem. I don't know if the project is far enough along to be of use to you yet, but it is worth checking.

    Edit: Shamelessly invoking the name of JS in the hopes of getting people to use and improve libraries rather than (unnecessarily) reinventing the wheel.