diff --git a/NEWS.md b/NEWS.md index ada3a2be..85df4d74 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # cpp11 (development version) +* `as_doubles()` and `as_integers()` now propagate missing values correctly + (#265). + * Fixed a performance issue related to nested `unwind_protect()` calls (#298). * Minor performance improvements to the cpp11 protect code. (@kevinushey) diff --git a/cpp11test/src/test-doubles.cpp b/cpp11test/src/test-doubles.cpp index 58023cd0..2d847405 100644 --- a/cpp11test/src/test-doubles.cpp +++ b/cpp11test/src/test-doubles.cpp @@ -393,6 +393,11 @@ context("doubles-C++") { e.push_back("a"); e.push_back("b"); expect_error(cpp11::as_doubles(e)); + + cpp11::writable::integers na; + na.push_back(cpp11::na()); + cpp11::doubles na2(cpp11::as_doubles(na)); + expect_true(cpp11::is_na(na2[0])); } test_that("doubles operator[] and at") { diff --git a/cpp11test/src/test-integers.cpp b/cpp11test/src/test-integers.cpp index 83d2cccf..f2e6c0f7 100644 --- a/cpp11test/src/test-integers.cpp +++ b/cpp11test/src/test-integers.cpp @@ -1,7 +1,9 @@ #include "Rversion.h" -#include "cpp11/doubles.hpp" + #include "cpp11/function.hpp" #include "cpp11/integers.hpp" + +#include "cpp11/doubles.hpp" #include "cpp11/strings.hpp" #include @@ -36,6 +38,11 @@ context("integers-C++") { expect_true(t[2] == 100000); expect_true(t[3] == 100000); expect_true(TYPEOF(t) == INTSXP); + + cpp11::writable::doubles na; + na.push_back(cpp11::na()); + cpp11::integers na2(cpp11::as_integers(na)); + expect_true(cpp11::is_na(na2[0])); } test_that("integers.push_back()") { diff --git a/inst/include/cpp11/as.hpp b/inst/include/cpp11/as.hpp index 6b4eb20f..682f12b5 100644 --- a/inst/include/cpp11/as.hpp +++ b/inst/include/cpp11/as.hpp @@ -73,7 +73,7 @@ using enable_if_c_string = enable_if_t::value, R>; // https://stackoverflow.com/a/1521682/2055486 // -inline bool is_convertable_without_loss_to_integer(double value) { +inline bool is_convertible_without_loss_to_integer(double value) { double int_part; return std::modf(value, &int_part) == 0.0; } @@ -100,7 +100,7 @@ enable_if_integral as_cpp(SEXP from) { return NA_INTEGER; } double value = REAL_ELT(from, 0); - if (is_convertable_without_loss_to_integer(value)) { + if (is_convertible_without_loss_to_integer(value)) { return value; } } diff --git a/inst/include/cpp11/doubles.hpp b/inst/include/cpp11/doubles.hpp index f2d5db1f..a20bdafa 100644 --- a/inst/include/cpp11/doubles.hpp +++ b/inst/include/cpp11/doubles.hpp @@ -135,19 +135,39 @@ typedef r_vector doubles; } // namespace writable +template <> +inline double na() { + return NA_REAL; +} + +template <> +inline bool is_na(const double& x) { + return ISNA(x); +} + +// forward declarations typedef r_vector integers; +template <> +int na(); + +template <> +int r_vector::operator[](const R_xlen_t pos) const; + inline doubles as_doubles(sexp x) { if (TYPEOF(x) == REALSXP) { return as_cpp(x); - } - - else if (TYPEOF(x) == INTSXP) { + } else if (TYPEOF(x) == INTSXP) { integers xn = as_cpp(x); - size_t len = xn.size(); - writable::doubles ret; - for (size_t i = 0; i < len; ++i) { - ret.push_back(static_cast(xn[i])); + R_xlen_t len = xn.size(); + writable::doubles ret(len); + for (R_xlen_t i = 0; i < len; ++i) { + int el = xn[i]; + if (is_na(el)) { + ret[i] = na(); + } else { + ret[i] = static_cast(el); + } } return ret; } @@ -155,13 +175,4 @@ inline doubles as_doubles(sexp x) { throw type_error(REALSXP, TYPEOF(x)); } -template <> -inline double na() { - return NA_REAL; -} - -template <> -inline bool is_na(const double& x) { - return ISNA(x); -} } // namespace cpp11 diff --git a/inst/include/cpp11/integers.hpp b/inst/include/cpp11/integers.hpp index 657d93ff..92b1fb12 100644 --- a/inst/include/cpp11/integers.hpp +++ b/inst/include/cpp11/integers.hpp @@ -145,25 +145,32 @@ inline int na() { return NA_INTEGER; } -// forward declaration - +// forward declarations typedef r_vector doubles; +template <> +bool is_na(const double& x); + +template <> +double r_vector::operator[](const R_xlen_t pos) const; + inline integers as_integers(sexp x) { if (TYPEOF(x) == INTSXP) { return as_cpp(x); } else if (TYPEOF(x) == REALSXP) { doubles xn = as_cpp(x); - size_t len = (xn.size()); - writable::integers ret = writable::integers(len); - for (size_t i = 0; i < len; ++i) { + R_xlen_t len = xn.size(); + writable::integers ret(len); + for (R_xlen_t i = 0; i < len; ++i) { double el = xn[i]; - if (!is_convertable_without_loss_to_integer(el)) { + if (is_na(el)) { + ret[i] = na(); + } else if (is_convertible_without_loss_to_integer(el)) { + ret[i] = static_cast(el); + } else { throw std::runtime_error("All elements must be integer-like"); } - ret[i] = (static_cast(el)); } - return ret; }