Fixed CSVFormatter mind floating point inaccuracy when resampling
authorTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Wed, 29 Apr 2020 21:55:09 +0000 (00:55 +0300)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Wed, 29 Apr 2020 21:58:05 +0000 (00:58 +0300)
These changes prevent the resampling export mode from dropping samples
that have timestamps closer than 1e-13 sec to the last exported sample
time stamp.

gitlab #529

bundles/org.simantics.history/src/org/simantics/history/csv/CSVFormatter.java

index 6cc7a2a6f09a9bf50e1c7f27eeb74651180e1df2..b04e0dd37b37e34d0398855a451cd93cb534d6ff 100644 (file)
@@ -38,6 +38,23 @@ import org.simantics.history.util.ValueBand;
  */
 public class CSVFormatter {
 
+       /**
+        * This is the tolerance used to decide whether or not the last data point of
+        * the exported items is included in the exported material or not. If
+        * <code>0 <= (t - t(lastDataPoint) < {@value #RESAMPLING_END_TIMESTAMP_INCLUSION_TOLERANCE}</code>
+        * is true, then the last exported data point will be
+        * <code>lastDataPoint</code>, with timestamp <code>t(lastDataPoint)</code> even
+        * if <code>t > t(lastDataPoint)</code>.
+        * 
+        * <p>
+        * This works around problems where floating point inaccuracy causes a data
+        * point to be left out from the the export when it would be fair for the user
+        * to expect the data to be exported would contain a point with time stamp
+        * <code>9.999999999999996</code> when sampling with time-step <code>1.0</code>
+        * starting from time <code>0.0</code>.
+        */
+       private static final double RESAMPLING_END_TIMESTAMP_INCLUSION_TOLERANCE = 1e-13;
+
        List<Item> items = new ArrayList<Item>();
        double from = -Double.MAX_VALUE;
        double end  =  Double.MAX_VALUE;
@@ -329,6 +346,13 @@ public class CSVFormatter {
                        BigDecimal bigTime = new BigDecimal(String.valueOf(time));
                        BigDecimal bigTimeStep = new BigDecimal(String.valueOf(timeStep));
 
+                       // Loop kill-switch for the case where timeStep > 0
+                       boolean breakAfterNextWrite = false;
+
+//                     System.out.println("time:     " + time);
+//                     System.out.println("timeStep: " + timeStep);
+//                     System.out.println("_end:     " + Double.toString(_end));
+
                        for (Item i : items) i.iter.gotoTime(time);
                        do {
                                if ( monitor!=null && monitor.isCanceled() ) return;
@@ -394,10 +418,26 @@ public class CSVFormatter {
 
                                sb.append( lineFeed );
 
-                   // Read next values, and the following times
-                   if ( timeStep>0.0 ) {
-                       bigTime = bigTime.add(bigTimeStep);
-                       time = bigTime.doubleValue();
+                               if (breakAfterNextWrite)
+                                       break;
+
+                               // Read next values, and the following times
+                               if ( timeStep>0.0 ) {
+                                       bigTime = bigTime.add(bigTimeStep);
+                                       time = bigTime.doubleValue();
+
+                                       // gitlab #529: prevent last data point from getting dropped
+                                       // due to small imprecisions in re-sampling mode.
+                                       double diff = time - _end;
+                                       if (diff > 0 && diff <= RESAMPLING_END_TIMESTAMP_INCLUSION_TOLERANCE) {
+                                               time = _end;
+                                               breakAfterNextWrite = true;
+                                               // Take floating point inaccuracy into account when re-sampling
+                                               // to prevent the last data point from being left out if there
+                                               // is small-enough imprecision in the last data point time stamp
+                                               // to be considered negligible compared to expected stepped time.
+                                       }
+
                    } else {
                        // Get smallest end time that is larger than current time
                        Double nextTime = null;
@@ -420,6 +460,7 @@ public class CSVFormatter {
                        if(contains(i, time)) hasMore = true;
                }
                
+               //System.out.println("hasMore @ " + time + " (" + bigTime + ") = " + hasMore);
                if(!hasMore) break;
                
                } while (time<=_end);