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
765ea3c
... +9 ...
c9917cc
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
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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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.
R/date.R
src/coercion.c
src/date.c
src/timezone.c
R/utils.R
src/utils.c
src/change.c
R/distance.R
src/boundary.c
R/boundary.R
src/init.c
R/change.R
R/sort.R
src/sort.c
src/distance.c
src/get.c
c9917cc
dbafd5c
a99415f
820b9c3
c386127
a73a6c8
a7564fe
4b9ffaf
5f096ab
a9db705
765ea3c