Compare 765ea3c ... +9 ... c9917cc

No flags found

Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.

e.g., #unittest #integration

#production #enterprise

#frontend #backend

Learn more about Codecov Flags here.

Showing 1 of 2 files from the diff.
Other files ignored by Codecov

@@ -5,8 +5,11 @@
Loading
5 5
// Helpers defined at the bottom of the file
6 6
static void validate_every(int every);
7 7
static void validate_origin(SEXP origin);
8 -
static double origin_to_days_from_epoch(SEXP origin);
9 -
static double origin_to_seconds_from_epoch(SEXP origin);
8 +
static int origin_to_days_from_epoch(SEXP origin);
9 +
static int64_t origin_to_seconds_from_epoch(SEXP origin);
10 +
static int64_t origin_to_milliseconds_from_epoch(SEXP origin);
11 +
static inline int64_t guarded_floor(double x);
12 +
static inline int64_t guarded_floor_to_millisecond(double x);
10 13
11 14
// -----------------------------------------------------------------------------
12 15
@@ -65,37 +68,37 @@
Loading
65 68
66 69
  bool needs_offset = (origin != R_NilValue);
67 70
68 -
  int origin_offset;
71 +
  int origin_offset_year;
69 72
70 73
  if (needs_offset) {
71 74
    SEXP origin_offset_sexp = PROTECT_N(get_year_offset(origin), &n_prot);
72 -
    origin_offset = INTEGER(origin_offset_sexp)[0];
75 +
    origin_offset_year = INTEGER(origin_offset_sexp)[0];
73 76
74 -
    if (origin_offset == NA_INTEGER) {
77 +
    if (origin_offset_year == NA_INTEGER) {
75 78
      r_error("warp_distance_year", "`origin` cannot be `NA`.");
76 79
    }
77 80
  }
78 81
79 82
  bool needs_every = (every != 1);
80 83
81 -
  x = PROTECT_N(get_year_offset(x), &n_prot);
82 -
  int* p_x = INTEGER(x);
84 +
  SEXP year = PROTECT_N(get_year_offset(x), &n_prot);
85 +
  int* p_year = INTEGER(year);
83 86
84 -
  R_xlen_t n_out = Rf_xlength(x);
87 +
  R_xlen_t n_out = Rf_xlength(year);
85 88
86 89
  SEXP out = PROTECT_N(Rf_allocVector(REALSXP, n_out), &n_prot);
87 90
  double* p_out = REAL(out);
88 91
89 92
  for (R_xlen_t i = 0; i < n_out; ++i) {
90 -
    int elt = p_x[i];
93 +
    int elt = p_year[i];
91 94
92 95
    if (elt == NA_INTEGER) {
93 96
      p_out[i] = NA_REAL;
94 97
      continue;
95 98
    }
96 99
97 100
    if (needs_offset) {
98 -
      elt -= origin_offset;
101 +
      elt -= origin_offset_year;
99 102
    }
100 103
101 104
    if (!needs_every) {
@@ -144,10 +147,10 @@
Loading
144 147
145 148
  bool needs_every = (every != 1);
146 149
147 -
  SEXP offset_lst = PROTECT_N(get_year_month_offset(x), &n_prot);
150 +
  SEXP x_offset_lst = PROTECT_N(get_year_month_offset(x), &n_prot);
148 151
149 -
  SEXP year = VECTOR_ELT(offset_lst, 0);
150 -
  SEXP month = VECTOR_ELT(offset_lst, 1);
152 +
  SEXP year = VECTOR_ELT(x_offset_lst, 0);
153 +
  SEXP month = VECTOR_ELT(x_offset_lst, 1);
151 154
152 155
  const int* p_year = INTEGER_RO(year);
153 156
  const int* p_month = INTEGER_RO(month);
@@ -271,7 +274,8 @@
Loading
271 274
  SEXP out = PROTECT(Rf_allocVector(REALSXP, size));
272 275
  double* p_out = REAL(out);
273 276
274 -
  double origin_offset;
277 +
  int origin_offset;
278 +
275 279
  if (needs_offset) {
276 280
    origin_offset = origin_to_days_from_epoch(origin);
277 281
  }
@@ -317,7 +321,7 @@
Loading
317 321
  bool needs_every = (every != 1);
318 322
319 323
  bool needs_offset = (origin != R_NilValue);
320 -
  double origin_offset;
324 +
  int origin_offset;
321 325
322 326
  if (needs_offset) {
323 327
    origin_offset = origin_to_days_from_epoch(origin);
@@ -331,13 +335,9 @@
Loading
331 335
      continue;
332 336
    }
333 337
334 -
    // Truncate towards 0 to get rid of any fractional date pieces.
335 -
    // We ignore them completely, you should just uses a POSIXct if you
336 -
    // need them
338 +
    // Truncate to completely ignore fractional Date parts
337 339
    int elt = x_elt;
338 340
339 -
    // `origin_offset` should be correct from `as_date()` in
340 -
    // `origin_to_days_from_epoch()`, even if it had fractional parts
341 341
    if (needs_offset) {
342 342
      elt -= origin_offset;
343 343
    }
@@ -368,7 +368,7 @@
Loading
368 368
  bool needs_every = (every != 1);
369 369
370 370
  bool needs_offset = (origin != R_NilValue);
371 -
  double origin_offset;
371 +
  int64_t origin_offset;
372 372
373 373
  if (needs_offset) {
374 374
    origin_offset = origin_to_seconds_from_epoch(origin);
@@ -380,18 +380,20 @@
Loading
380 380
  int* p_x = INTEGER(x);
381 381
382 382
  for (R_xlen_t i = 0; i < size; ++i) {
383 -
    int elt = p_x[i];
383 +
    int x_elt = p_x[i];
384 384
385 -
    if (elt == NA_INTEGER) {
385 +
    if (x_elt == NA_INTEGER) {
386 386
      p_out[i] = NA_REAL;
387 387
      continue;
388 388
    }
389 389
390 +
    // Avoid overflow
391 +
    int64_t elt = x_elt;
392 +
390 393
    if (needs_offset) {
391 394
      elt -= origin_offset;
392 395
    }
393 396
394 -
    // Integer division, then straight into `elt` with no cast needed
395 397
    if (elt < 0) {
396 398
      elt = (elt - (SECONDS_IN_DAY - 1)) / SECONDS_IN_DAY;
397 399
    } else {
@@ -422,7 +424,7 @@
Loading
422 424
  bool needs_every = (every != 1);
423 425
424 426
  bool needs_offset = (origin != R_NilValue);
425 -
  double origin_offset;
427 +
  int64_t origin_offset;
426 428
427 429
  if (needs_offset) {
428 430
    origin_offset = origin_to_seconds_from_epoch(origin);
@@ -441,17 +443,16 @@
Loading
441 443
      continue;
442 444
    }
443 445
446 +
    int64_t elt = guarded_floor(x_elt);
447 +
444 448
    if (needs_offset) {
445 -
      x_elt -= origin_offset;
449 +
      elt -= origin_offset;
446 450
    }
447 451
448 -
    int elt;
449 -
450 -
    // Double division, then integer cast into `elt`
451 -
    if (x_elt < 0) {
452 -
      elt = (floor(x_elt) - (SECONDS_IN_DAY - 1)) / SECONDS_IN_DAY;
452 +
    if (elt < 0) {
453 +
      elt = (elt - (SECONDS_IN_DAY - 1)) / SECONDS_IN_DAY;
453 454
    } else {
454 -
      elt = x_elt / SECONDS_IN_DAY;
455 +
      elt = elt / SECONDS_IN_DAY;
455 456
    }
456 457
457 458
    if (!needs_every) {
@@ -536,10 +537,10 @@
Loading
536 537
  bool needs_every = (every != 1);
537 538
538 539
  bool needs_offset = (origin != R_NilValue);
539 -
  double origin_offset;
540 +
  int origin_offset;
540 541
541 542
  if (needs_offset) {
542 -
    origin_offset = origin_to_days_from_epoch(origin) * HOURS_IN_DAY;
543 +
    origin_offset = origin_to_days_from_epoch(origin);
543 544
  }
544 545
545 546
  for (R_xlen_t i = 0; i < size; ++i) {
@@ -550,12 +551,12 @@
Loading
550 551
      continue;
551 552
    }
552 553
553 -
    elt = elt * HOURS_IN_DAY;
554 -
555 554
    if (needs_offset) {
556 555
      elt -= origin_offset;
557 556
    }
558 557
558 +
    elt *= HOURS_IN_DAY;
559 +
559 560
    if (!needs_every) {
560 561
      p_out[i] = elt;
561 562
      continue;
@@ -585,10 +586,10 @@
Loading
585 586
  bool needs_every = (every != 1);
586 587
587 588
  bool needs_offset = (origin != R_NilValue);
588 -
  double origin_offset;
589 +
  int origin_offset;
589 590
590 591
  if (needs_offset) {
591 -
    origin_offset = origin_to_days_from_epoch(origin) * HOURS_IN_DAY;
592 +
    origin_offset = origin_to_days_from_epoch(origin);
592 593
  }
593 594
594 595
  for (R_xlen_t i = 0; i < size; ++i) {
@@ -599,19 +600,15 @@
Loading
599 600
      continue;
600 601
    }
601 602
602 -
    // Truncate towards 0 to get rid of any fractional date pieces.
603 -
    // We ignore them completely, you should just uses a POSIXct if you
604 -
    // need them
603 +
    // Truncate to completely ignore fractional Date parts
605 604
    int elt = x_elt;
606 605
607 -
    elt = elt * HOURS_IN_DAY;
608 -
609 -
    // `origin_offset` should be correct from `as_date()` in
610 -
    // `origin_to_days_from_epoch()`, even if it had fractional parts
611 606
    if (needs_offset) {
612 607
      elt -= origin_offset;
613 608
    }
614 609
610 +
    elt *= HOURS_IN_DAY;
611 +
615 612
    if (!needs_every) {
616 613
      p_out[i] = elt;
617 614
      continue;
@@ -640,7 +637,7 @@
Loading
640 637
  bool needs_every = (every != 1);
641 638
642 639
  bool needs_offset = (origin != R_NilValue);
643 -
  double origin_offset;
640 +
  int64_t origin_offset;
644 641
645 642
  if (needs_offset) {
646 643
    origin_offset = origin_to_seconds_from_epoch(origin);
@@ -652,18 +649,20 @@
Loading
652 649
  int* p_x = INTEGER(x);
653 650
654 651
  for (R_xlen_t i = 0; i < size; ++i) {
655 -
    int elt = p_x[i];
652 +
    int x_elt = p_x[i];
656 653
657 -
    if (elt == NA_INTEGER) {
654 +
    if (x_elt == NA_INTEGER) {
658 655
      p_out[i] = NA_REAL;
659 656
      continue;
660 657
    }
661 658
659 +
    // Avoid overflow
660 +
    int64_t elt = x_elt;
661 +
662 662
    if (needs_offset) {
663 663
      elt -= origin_offset;
664 664
    }
665 665
666 -
    // Integer division, then straight into `elt` with no cast needed
667 666
    if (elt < 0) {
668 667
      elt = (elt - (SECONDS_IN_HOUR - 1)) / SECONDS_IN_HOUR;
669 668
    } else {
@@ -694,7 +693,7 @@
Loading
694 693
  bool needs_every = (every != 1);
695 694
696 695
  bool needs_offset = (origin != R_NilValue);
697 -
  double origin_offset;
696 +
  int64_t origin_offset;
698 697
699 698
  if (needs_offset) {
700 699
    origin_offset = origin_to_seconds_from_epoch(origin);
@@ -713,17 +712,16 @@
Loading
713 712
      continue;
714 713
    }
715 714
715 +
    int64_t elt = guarded_floor(x_elt);
716 +
716 717
    if (needs_offset) {
717 -
      x_elt -= origin_offset;
718 +
      elt -= origin_offset;
718 719
    }
719 720
720 -
    int elt;
721 -
722 -
    // Double division, then integer cast into `elt`
723 -
    if (x_elt < 0) {
724 -
      elt = (floor(x_elt) - (SECONDS_IN_HOUR - 1)) / SECONDS_IN_HOUR;
721 +
    if (elt < 0) {
722 +
      elt = (elt - (SECONDS_IN_HOUR - 1)) / SECONDS_IN_HOUR;
725 723
    } else {
726 -
      elt = x_elt / SECONDS_IN_HOUR;
724 +
      elt = elt / SECONDS_IN_HOUR;
727 725
    }
728 726
729 727
    if (!needs_every) {
@@ -808,10 +806,10 @@
Loading
808 806
  bool needs_every = (every != 1);
809 807
810 808
  bool needs_offset = (origin != R_NilValue);
811 -
  double origin_offset;
809 +
  int origin_offset;
812 810
813 811
  if (needs_offset) {
814 -
    origin_offset = origin_to_days_from_epoch(origin) * MINUTES_IN_DAY;
812 +
    origin_offset = origin_to_days_from_epoch(origin);
815 813
  }
816 814
817 815
  for (R_xlen_t i = 0; i < size; ++i) {
@@ -822,12 +820,12 @@
Loading
822 820
      continue;
823 821
    }
824 822
825 -
    elt = elt * MINUTES_IN_DAY;
826 -
827 823
    if (needs_offset) {
828 824
      elt -= origin_offset;
829 825
    }
830 826
827 +
    elt *= MINUTES_IN_DAY;
828 +
831 829
    if (!needs_every) {
832 830
      p_out[i] = elt;
833 831
      continue;
@@ -857,10 +855,10 @@
Loading
857 855
  bool needs_every = (every != 1);
858 856
859 857
  bool needs_offset = (origin != R_NilValue);
860 -
  double origin_offset;
858 +
  int origin_offset;
861 859
862 860
  if (needs_offset) {
863 -
    origin_offset = origin_to_days_from_epoch(origin) * MINUTES_IN_DAY;
861 +
    origin_offset = origin_to_days_from_epoch(origin);
864 862
  }
865 863
866 864
  for (R_xlen_t i = 0; i < size; ++i) {
@@ -871,19 +869,15 @@
Loading
871 869
      continue;
872 870
    }
873 871
874 -
    // Truncate towards 0 to get rid of any fractional date pieces.
875 -
    // We ignore them completely, you should just uses a POSIXct if you
876 -
    // need them
872 +
    // Truncate to completely ignore fractional Date parts
877 873
    int elt = x_elt;
878 874
879 -
    elt = elt * MINUTES_IN_DAY;
880 -
881 -
    // `origin_offset` should be correct from `as_date()` in
882 -
    // `origin_to_days_from_epoch()`, even if it had fractional parts
883 875
    if (needs_offset) {
884 876
      elt -= origin_offset;
885 877
    }
886 878
879 +
    elt *= MINUTES_IN_DAY;
880 +
887 881
    if (!needs_every) {
888 882
      p_out[i] = elt;
889 883
      continue;
@@ -912,7 +906,7 @@
Loading
912 906
  bool needs_every = (every != 1);
913 907
914 908
  bool needs_offset = (origin != R_NilValue);
915 -
  double origin_offset;
909 +
  int64_t origin_offset;
916 910
917 911
  if (needs_offset) {
918 912
    origin_offset = origin_to_seconds_from_epoch(origin);
@@ -924,18 +918,20 @@
Loading
924 918
  int* p_x = INTEGER(x);
925 919
926 920
  for (R_xlen_t i = 0; i < size; ++i) {
927 -
    int elt = p_x[i];
921 +
    int x_elt = p_x[i];
928 922
929 -
    if (elt == NA_INTEGER) {
923 +
    if (x_elt == NA_INTEGER) {
930 924
      p_out[i] = NA_REAL;
931 925
      continue;
932 926
    }
933 927
928 +
    // Avoid overflow
929 +
    int64_t elt = x_elt;
930 +
934 931
    if (needs_offset) {
935 932
      elt -= origin_offset;
936 933
    }
937 934
938 -
    // Integer division, then straight into `elt` with no cast needed
939 935
    if (elt < 0) {
940 936
      elt = (elt - (SECONDS_IN_MINUTE - 1)) / SECONDS_IN_MINUTE;
941 937
    } else {
@@ -966,7 +962,7 @@
Loading
966 962
  bool needs_every = (every != 1);
967 963
968 964
  bool needs_offset = (origin != R_NilValue);
969 -
  double origin_offset;
965 +
  int64_t origin_offset;
970 966
971 967
  if (needs_offset) {
972 968
    origin_offset = origin_to_seconds_from_epoch(origin);
@@ -985,17 +981,16 @@
Loading
985 981
      continue;
986 982
    }
987 983
984 +
    int64_t elt = guarded_floor(x_elt);
985 +
988 986
    if (needs_offset) {
989 -
      x_elt -= origin_offset;
987 +
      elt -= origin_offset;
990 988
    }
991 989
992 -
    int elt;
993 -
994 -
    // Double division, then integer cast into `elt`
995 -
    if (x_elt < 0) {
996 -
      elt = (floor(x_elt) - (SECONDS_IN_MINUTE - 1)) / SECONDS_IN_MINUTE;
990 +
    if (elt < 0) {
991 +
      elt = (elt - (SECONDS_IN_MINUTE - 1)) / SECONDS_IN_MINUTE;
997 992
    } else {
998 -
      elt = x_elt / SECONDS_IN_MINUTE;
993 +
      elt = elt / SECONDS_IN_MINUTE;
999 994
    }
1000 995
1001 996
    if (!needs_every) {
@@ -1080,10 +1075,10 @@
Loading
1080 1075
  bool needs_every = (every != 1);
1081 1076
1082 1077
  bool needs_offset = (origin != R_NilValue);
1083 -
  double origin_offset;
1078 +
  int origin_offset;
1084 1079
1085 1080
  if (needs_offset) {
1086 -
    origin_offset = origin_to_days_from_epoch(origin) * SECONDS_IN_DAY;
1081 +
    origin_offset = origin_to_days_from_epoch(origin);
1087 1082
  }
1088 1083
1089 1084
  for (R_xlen_t i = 0; i < x_size; ++i) {
@@ -1094,14 +1089,15 @@
Loading
1094 1089
      continue;
1095 1090
    }
1096 1091
1097 -
    // Convert to int64_t here to hold `elt * SECONDS_IN_DAY`
1098 -
    // Can't be double because we still need integer division later
1099 -
    int64_t elt = x_elt * SECONDS_IN_DAY;
1092 +
    // Avoid overflow
1093 +
    int64_t elt = x_elt;
1100 1094
1101 1095
    if (needs_offset) {
1102 1096
      elt -= origin_offset;
1103 1097
    }
1104 1098
1099 +
    elt *= SECONDS_IN_DAY;
1100 +
1105 1101
    if (!needs_every) {
1106 1102
      p_out[i] = elt;
1107 1103
      continue;
@@ -1131,10 +1127,10 @@
Loading
1131 1127
  bool needs_every = (every != 1);
1132 1128
1133 1129
  bool needs_offset = (origin != R_NilValue);
1134 -
  double origin_offset;
1130 +
  int origin_offset;
1135 1131
1136 1132
  if (needs_offset) {
1137 -
    origin_offset = origin_to_days_from_epoch(origin) * SECONDS_IN_DAY;
1133 +
    origin_offset = origin_to_days_from_epoch(origin);
1138 1134
  }
1139 1135
1140 1136
  for (R_xlen_t i = 0; i < x_size; ++i) {
@@ -1145,17 +1141,16 @@
Loading
1145 1141
      continue;
1146 1142
    }
1147 1143
1148 -
    // Truncate towards 0 to get rid of the fractional pieces
1144 +
    // Truncate to completely ignore fractional Date parts
1145 +
    // `int64_t` to avoid overflow
1149 1146
    int64_t elt = x_elt;
1150 1147
1151 -
    elt = elt * SECONDS_IN_DAY;
1152 -
1153 -
    // `origin_offset` should be correct from `as_date()` in
1154 -
    // `origin_to_days_from_epoch()`, even if it had fractional parts
1155 1148
    if (needs_offset) {
1156 1149
      elt -= origin_offset;
1157 1150
    }
1158 1151
1152 +
    elt *= SECONDS_IN_DAY;
1153 +
1159 1154
    if (!needs_every) {
1160 1155
      p_out[i] = elt;
1161 1156
      continue;
@@ -1182,7 +1177,7 @@
Loading
1182 1177
  bool needs_every = (every != 1);
1183 1178
1184 1179
  bool needs_offset = (origin != R_NilValue);
1185 -
  double origin_offset;
1180 +
  int64_t origin_offset;
1186 1181
1187 1182
  if (needs_offset) {
1188 1183
    origin_offset = origin_to_seconds_from_epoch(origin);
@@ -1194,15 +1189,14 @@
Loading
1194 1189
  int* p_x = INTEGER(x);
1195 1190
1196 1191
  for (R_xlen_t i = 0; i < x_size; ++i) {
1197 -
    // Starts as `int`, since that is what `x` is
1198 1192
    int x_elt = p_x[i];
1199 1193
1200 1194
    if (x_elt == NA_INTEGER) {
1201 1195
      p_out[i] = NA_REAL;
1202 1196
      continue;
1203 1197
    }
1204 1198
1205 -
    // Convert to `int64_t` in case `elt -= origin_offset` goes OOB
1199 +
    // Avoid overflow
1206 1200
    int64_t elt = x_elt;
1207 1201
1208 1202
    if (needs_offset) {
@@ -1233,7 +1227,7 @@
Loading
1233 1227
  bool needs_every = (every != 1);
1234 1228
1235 1229
  bool needs_offset = (origin != R_NilValue);
1236 -
  double origin_offset;
1230 +
  int64_t origin_offset;
1237 1231
1238 1232
  if (needs_offset) {
1239 1233
    origin_offset = origin_to_seconds_from_epoch(origin);
@@ -1252,16 +1246,12 @@
Loading
1252 1246
      continue;
1253 1247
    }
1254 1248
1249 +
    int64_t elt = guarded_floor(x_elt);
1250 +
1255 1251
    if (needs_offset) {
1256 -
      x_elt -= origin_offset;
1252 +
      elt -= origin_offset;
1257 1253
    }
1258 1254
1259 -
    // Always floor() to get rid of fractional seconds, whether `x_elt` is
1260 -
    // negative or positive. Need int64_t here because of the integer
1261 -
    // division later. Flooring takes the fractional seconds into account,
1262 -
    // which we want to do.
1263 -
    int64_t elt = floor(x_elt);
1264 -
1265 1255
    if (!needs_every) {
1266 1256
      p_out[i] = elt;
1267 1257
      continue;
@@ -1342,10 +1332,10 @@
Loading
1342 1332
  bool needs_every = (every != 1);
1343 1333
1344 1334
  bool needs_offset = (origin != R_NilValue);
1345 -
  double origin_offset;
1335 +
  int origin_offset;
1346 1336
1347 1337
  if (needs_offset) {
1348 -
    origin_offset = origin_to_days_from_epoch(origin) * MILLISECONDS_IN_DAY;
1338 +
    origin_offset = origin_to_days_from_epoch(origin);
1349 1339
  }
1350 1340
1351 1341
  for (R_xlen_t i = 0; i < x_size; ++i) {
@@ -1356,14 +1346,15 @@
Loading
1356 1346
      continue;
1357 1347
    }
1358 1348
1359 -
    // Convert to int64_t here to hold `elt * MILLISECONDS_IN_DAY`
1360 -
    // Can't be double because we still need integer division later
1361 -
    int64_t elt = x_elt * MILLISECONDS_IN_DAY;
1349 +
    // `int64_t` to avoid overflow
1350 +
    int64_t elt = x_elt;
1362 1351
1363 1352
    if (needs_offset) {
1364 1353
      elt -= origin_offset;
1365 1354
    }
1366 1355
1356 +
    elt *= MILLISECONDS_IN_DAY;
1357 +
1367 1358
    if (!needs_every) {
1368 1359
      p_out[i] = elt;
1369 1360
      continue;
@@ -1393,10 +1384,10 @@
Loading
1393 1384
  bool needs_every = (every != 1);
1394 1385
1395 1386
  bool needs_offset = (origin != R_NilValue);
1396 -
  double origin_offset;
1387 +
  int origin_offset;
1397 1388
1398 1389
  if (needs_offset) {
1399 -
    origin_offset = origin_to_days_from_epoch(origin) * MILLISECONDS_IN_DAY;
1390 +
    origin_offset = origin_to_days_from_epoch(origin);
1400 1391
  }
1401 1392
1402 1393
  for (R_xlen_t i = 0; i < x_size; ++i) {
@@ -1407,17 +1398,16 @@
Loading
1407 1398
      continue;
1408 1399
    }
1409 1400
1410 -
    // Truncate towards 0 to get rid of the fractional pieces
1401 +
    // Truncate to completely ignore fractional Date parts
1402 +
    // `int64_t` to avoid overflow
1411 1403
    int64_t elt = x_elt;
1412 1404
1413 -
    elt = elt * MILLISECONDS_IN_DAY;
1414 -
1415 -
    // `origin_offset` should be correct from `as_date()` in
1416 -
    // `origin_to_days_from_epoch()`, even if it had fractional parts
1417 1405
    if (needs_offset) {
1418 1406
      elt -= origin_offset;
1419 1407
    }
1420 1408
1409 +
    elt *= MILLISECONDS_IN_DAY;
1410 +
1421 1411
    if (!needs_every) {
1422 1412
      p_out[i] = elt;
1423 1413
      continue;
@@ -1446,10 +1436,10 @@
Loading
1446 1436
  bool needs_every = (every != 1);
1447 1437
1448 1438
  bool needs_offset = (origin != R_NilValue);
1449 -
  double origin_offset;
1439 +
  int64_t origin_offset;
1450 1440
1451 1441
  if (needs_offset) {
1452 -
    origin_offset = origin_to_seconds_from_epoch(origin);
1442 +
    origin_offset = origin_to_milliseconds_from_epoch(origin);
1453 1443
  }
1454 1444
1455 1445
  SEXP out = PROTECT(Rf_allocVector(REALSXP, x_size));
@@ -1458,16 +1448,16 @@
Loading
1458 1448
  int* p_x = INTEGER(x);
1459 1449
1460 1450
  for (R_xlen_t i = 0; i < x_size; ++i) {
1461 -
    // Starts as `int`, since that is what `x` is
1462 1451
    int x_elt = p_x[i];
1463 1452
1464 1453
    if (x_elt == NA_INTEGER) {
1465 1454
      p_out[i] = NA_REAL;
1466 1455
      continue;
1467 1456
    }
1468 1457
1469 -
    // Convert to `int64_t` to guard against overflow
1470 -
    // No need to worry about precision issues here
1458 +
    // `int64_t` to avoid overflow
1459 +
    // Note - Have to do `* MILLISECONDS_IN_SECOND` before the
1460 +
    // offset subtraction because the offset is already in milliseconds
1471 1461
    int64_t elt = x_elt * MILLISECONDS_IN_SECOND;
1472 1462
1473 1463
    if (needs_offset) {
@@ -1498,10 +1488,10 @@
Loading
1498 1488
  bool needs_every = (every != 1);
1499 1489
1500 1490
  bool needs_offset = (origin != R_NilValue);
1501 -
  double origin_offset;
1491 +
  int64_t origin_offset;
1502 1492
1503 1493
  if (needs_offset) {
1504 -
    origin_offset = origin_to_seconds_from_epoch(origin) * MILLISECONDS_IN_SECOND;
1494 +
    origin_offset = origin_to_milliseconds_from_epoch(origin);
1505 1495
  }
1506 1496
1507 1497
  SEXP out = PROTECT(Rf_allocVector(REALSXP, x_size));
@@ -1517,15 +1507,12 @@
Loading
1517 1507
      continue;
1518 1508
    }
1519 1509
1520 -
    x_elt = x_elt * MILLISECONDS_IN_SECOND;
1510 +
    int64_t elt = guarded_floor_to_millisecond(x_elt);
1521 1511
1522 1512
    if (needs_offset) {
1523 -
      x_elt -= origin_offset;
1513 +
      elt -= origin_offset;
1524 1514
    }
1525 1515
1526 -
    // Always floor() to get rid of fractional pieces
1527 -
    int64_t elt = floor(x_elt);
1528 -
1529 1516
    if (!needs_every) {
1530 1517
      p_out[i] = elt;
1531 1518
      continue;
@@ -1574,7 +1561,9 @@
Loading
1574 1561
  }
1575 1562
}
1576 1563
1577 -
static double origin_to_days_from_epoch(SEXP origin) {
1564 +
// `as_date()` will always return a double with no fractional component,
1565 +
// and the double will always fit inside an int
1566 +
static int origin_to_days_from_epoch(SEXP origin) {
1578 1567
  origin = PROTECT(as_date(origin));
1579 1568
1580 1569
  double out = REAL(origin)[0];
@@ -1584,18 +1573,110 @@
Loading
1584 1573
  }
1585 1574
1586 1575
  UNPROTECT(1);
1587 -
  return out;
1576 +
  return (int) out;
1588 1577
}
1589 1578
1590 -
static double origin_to_seconds_from_epoch(SEXP origin) {
1579 +
static int64_t origin_to_seconds_from_epoch(SEXP origin) {
1591 1580
  origin = PROTECT(as_datetime(origin));
1592 1581
1593 -
  double out = REAL(origin)[0];
1582 +
  double origin_value = REAL(origin)[0];
1594 1583
1595 -
  if (out == NA_REAL) {
1584 +
  if (origin_value == NA_REAL) {
1596 1585
    r_error("origin_to_seconds_from_epoch", "`origin` must not be `NA`.");
1597 1586
  }
1598 1587
1588 +
  int64_t out = guarded_floor(origin_value);
1589 +
1599 1590
  UNPROTECT(1);
1600 1591
  return out;
1601 1592
}
1593 +
1594 +
static int64_t origin_to_milliseconds_from_epoch(SEXP origin) {
1595 +
  origin = PROTECT(as_datetime(origin));
1596 +
1597 +
  double origin_value = REAL(origin)[0];
1598 +
1599 +
  if (origin_value == NA_REAL) {
1600 +
    r_error("origin_to_milliseconds_from_epoch", "`origin` must not be `NA`.");
1601 +
  }
1602 +
1603 +
  int64_t out = guarded_floor_to_millisecond(origin_value);
1604 +
1605 +
  UNPROTECT(1);
1606 +
  return out;
1607 +
}
1608 +
1609 +
/*
1610 +
 * `double` values are represented with 64 bits:
1611 +
 * - 1 sign bit
1612 +
 * - 11 exponent bits
1613 +
 * - 52 significand bits
1614 +
 *
1615 +
 * The 52 significand bits are the ones that store the true value, this
1616 +
 * corresponds to about 15 stable significand digits, with everything after
1617 +
 * that being garbage.
1618 +
 *
1619 +
 * Internally doubles are represented with scientific notation to put them in
1620 +
 * the exponent-significand representation. So the following date, which
1621 +
 * is represented as a double, really looks like this in scientific notation:
1622 +
 *
1623 +
 * unclass(as.POSIXct("2011-05-01 17:55:23.123456"))
1624 +
 * =
1625 +
 * 1304286923.1234560013
1626 +
 * =
1627 +
 * 1.3042869231234560013e+09
1628 +
 *                ^ 15th digit
1629 +
 *
1630 +
 * Because only 15 digits are stable, this is where we draw the line on
1631 +
 * assuming that the user might have some valuable information stored here.
1632 +
 * This corresponds to the place right before microseconds. Sure, we could use
1633 +
 * a date that has less digits before the decimal to get more fractional
1634 +
 * precision (see below) but most dates are in this form: 10 digits before
1635 +
 * the decimal representing whole seconds, meaning 5 stable digits after it.
1636 +
 *
1637 +
 * The other part of the story is that not all floating point numbers can be
1638 +
 * represented exactly in binary. For example:
1639 +
 *
1640 +
 * unclass(as.POSIXct("1969-12-31 23:59:59.998", "UTC"))
1641 +
 * =
1642 +
 * -0.002000000000002444267
1643 +
 *
1644 +
 * Because of this, `floor()` will give results that (to us) are incorrect if
1645 +
 * we were to try and floor to milliseconds. We would first times by 1000 to
1646 +
 * get milliseconds of `-2.000000000002444267`, and then `floor()` would give
1647 +
 * us -3, not -2 which is the correct group.
1648 +
 *
1649 +
 * To get around this, we need to guard against this floating point error. The
1650 +
 * best way I can come up with is to add a small value before flooring, which
1651 +
 * would push us into the -1.9999999 range, which would floor correctly.
1652 +
 *
1653 +
 * I chose the value of 1 microsecond because that is generally where the 16th
1654 +
 * digit falls for most dates (10 digits of whole seconds, 5 of stable
1655 +
 * fractional seconds). This seems to work well for the millisecond grouping,
1656 +
 * and we apply it to anywhere that uses seconds "just in case", but it is hard
1657 +
 * to come up with tests for them.
1658 +
 */
1659 +
static inline double guard_with_microsecond(double x) {
1660 +
  return x + 0.000001;
1661 +
}
1662 +
1663 +
static inline int64_t guarded_floor(double x) {
1664 +
  x = guard_with_microsecond(x);
1665 +
  x = floor(x);
1666 +
  return (int64_t) x;
1667 +
}
1668 +
1669 +
// The order here is slightly different. We want to convert
1670 +
// seconds to milliseconds while still guarding correctly.
1671 +
// - Apply the guard to the seconds first at the correct decimal place
1672 +
// - Then scale up to milliseconds and floor
1673 +
#define MILLISECONDS_IN_SECOND 1000
1674 +
1675 +
static inline int64_t guarded_floor_to_millisecond(double x) {
1676 +
  x = guard_with_microsecond(x);
1677 +
  x *= MILLISECONDS_IN_SECOND;
1678 +
  x = floor(x);
1679 +
  return (int64_t) x;
1680 +
}
1681 +
1682 +
#undef MILLISECONDS_IN_SECOND

Learn more Showing 19 files with coverage changes found.

Changes in R/date.R
+2
Loading file...
Changes in src/coercion.c
-14
+14
Loading file...
Changes in src/date.c
-27
+18
Loading file...
Changes in src/timezone.c
-24
-3
Loading file...
Changes in R/utils.R
+1
Loading file...
Changes in src/utils.c
-40
-2
Loading file...
Changes in src/change.c
-11
Loading file...
Changes in R/distance.R
-1
Loading file...
Changes in src/boundary.c
New
Loading file...
Changes in R/boundary.R
-1
Loading file...
Changes in src/init.c
New
Loading file...
Changes in R/change.R
-1
Loading file...
R/dots.R
Loading file...
src/divmod.c
Loading file...
R/divmod.R
Loading file...
New file R/sort.R
New
Loading file...
New file src/sort.c
New
Loading file...
Changes in src/distance.c
-276
-12
Loading file...
Changes in src/get.c
-102
-14
Loading file...
Files Coverage
R -16.82% 59.38%
src -1.57% 93.14%
Project Totals (16 files) 92.41%
Loading