diff --git a/ChangeLog b/ChangeLog index 183695dd7..43385e9d6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +2026-02-26 Iñaki Ucar + + * inst/include/Rcpp/sugar/tools/safe_math.h: New header implementing safe + versions of add/sub/mul operations for integral types + * inst/include/Rcpp/sugar/sugar.h: Includes the previous header + + * inst/include/Rcpp/sugar/functions/cumprod.h: Use the previous operations + * inst/include/Rcpp/sugar/functions/cumsum.h: Idem + * inst/include/Rcpp/sugar/functions/diff.h: Idem + * inst/include/Rcpp/sugar/functions/rowSums.h: Idem + * inst/include/Rcpp/sugar/functions/sum.h: Idem + * inst/include/Rcpp/sugar/operators/minus.h: Idem + * inst/include/Rcpp/sugar/operators/plus.h: Idem + * inst/include/Rcpp/sugar/operators/times.h: Idem + + * inst/tinytest/test_sugar.R: New tests covering the new operations + * inst/tinytest/cpp/sugar.cpp: Idem + * inst/tinytest/cpp/sugar_safe_math.cpp: Idem + * inst/tinytest/cpp/sugar_safe_math_fallback.cpp: Idem + 2026-02-17 Dirk Eddelbuettel * DESCRIPTION (Version, Date): Roll micro version and date diff --git a/inst/include/Rcpp/sugar/functions/cumprod.h b/inst/include/Rcpp/sugar/functions/cumprod.h index 59cf8d4bd..0ad8936fd 100644 --- a/inst/include/Rcpp/sugar/functions/cumprod.h +++ b/inst/include/Rcpp/sugar/functions/cumprod.h @@ -2,7 +2,8 @@ // // cumsum.h: Rcpp R/C++ interface class library -- cumsum // -// Copyright (C) 2010 - 2011 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -33,23 +34,23 @@ class Cumprod : public Lazy< Rcpp::Vector, Cumprod > { typedef Rcpp::Vector VECTOR; Cumprod(const VEC_TYPE& object_) : object(object_) {} - + VECTOR get() const { R_xlen_t n = object.size(); VECTOR result(n, Rcpp::traits::get_na()); STORAGE current = object[0]; - + if (Rcpp::traits::is_na(current)) return result; result[0] = current; for (R_xlen_t i = 1; i < n; i++) { current = object[i]; if (Rcpp::traits::is_na(current)) return result; - result[i] = result[i-1] * current; + result[i] = RCPP_SAFE_MUL(result[i-1], current); } return result ; } private: - const VEC_TYPE& object; + const VEC_TYPE& object; }; } // sugar @@ -72,5 +73,5 @@ inline sugar::Cumprod cumprod(const VectorBase& } // Rcpp -#endif // Rcpp__sugar__cumprod_h +#endif // Rcpp__sugar__cumprod_h diff --git a/inst/include/Rcpp/sugar/functions/cumsum.h b/inst/include/Rcpp/sugar/functions/cumsum.h index e42e5bc35..cb8e53886 100644 --- a/inst/include/Rcpp/sugar/functions/cumsum.h +++ b/inst/include/Rcpp/sugar/functions/cumsum.h @@ -2,7 +2,8 @@ // // cumsum.h: Rcpp R/C++ interface class library -- cumsum // -// Copyright (C) 2010 - 2011 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -45,7 +46,7 @@ class Cumsum : public Lazy< Rcpp::Vector , Cumsum > { current = object[i] ; if( Rcpp::traits::is_na(current) ) return result ; - result[i] = result[i-1] + current ; + result[i] = RCPP_SAFE_ADD(result[i-1], current); } return result ; } diff --git a/inst/include/Rcpp/sugar/functions/diff.h b/inst/include/Rcpp/sugar/functions/diff.h index 5d9910c72..aa1c9d7d6 100644 --- a/inst/include/Rcpp/sugar/functions/diff.h +++ b/inst/include/Rcpp/sugar/functions/diff.h @@ -2,7 +2,8 @@ // // diff.h: Rcpp R/C++ interface class library -- diff // -// Copyright (C) 2010 - 2013 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -51,7 +52,7 @@ class Diff : public Rcpp::VectorBase< RTYPE, LHS_NA , Diff > set_previous(i+1, y ) ; return traits::get_na() ; // NA } - STORAGE res = y - previous ; + STORAGE res = RCPP_SAFE_SUB(y, previous); set_previous( i+1, y) ; return res ; } @@ -81,7 +82,7 @@ class Diff : public Rcpp::VectorBase< REALSXP, LHS_NA, D inline double operator[]( R_xlen_t i ) const { double y = lhs[i+1] ; if( previous_index != i ) previous = lhs[i] ; - double res = y - previous ; + double res = RCPP_SAFE_SUB(y, previous); previous = y ; previous_index = i+1 ; return res ; @@ -105,10 +106,10 @@ class Diff : public Rcpp::VectorBase< RTYPE, false , Diff::type stored_type; \ \ - struct bit { \ - unsigned char x : 1; \ - }; \ - \ public: \ RowSumsImpl(const MatrixBase<__RTYPE__, NA, T>& ref_) \ : ref(ref_) \ @@ -191,29 +167,22 @@ public: R_xlen_t i, j, nr = ref.nrow(), nc = ref.ncol(); \ return_vector res(nr); \ \ - std::vector na_flags(nr); \ - \ for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ - if (detail::check_na(ref(i, j))) { \ - na_flags[i].x |= 0x1; \ - } \ - detail::incr(&res[i], ref(i, j)); \ - } \ - } \ - \ - for (i = 0; i < nr; i++) { \ - if (na_flags[i].x) { \ - res[i] = NA_INTEGER; \ + if (traits::is_na<__RTYPE__>(res[i])) \ + continue; \ + if (traits::is_na<__RTYPE__>(ref(i, j))) \ + res[i] = traits::get_na<__RTYPE__>(); \ + else detail::incr(&res[i], ref(i, j)); \ } \ } \ \ return res; \ } \ -}; +}; -ROW_SUMS_IMPL_KEEPNA(LGLSXP) -ROW_SUMS_IMPL_KEEPNA(INTSXP) +ROW_SUMS_IMPL_KEEPNA(LGLSXP) +ROW_SUMS_IMPL_KEEPNA(INTSXP) #undef ROW_SUMS_IMPL_KEEPNA @@ -246,7 +215,7 @@ class RowSumsImpl : for (j = 0; j < nc; j++) { for (i = 0; i < nr; i++) { current = ref(i, j); - if (!detail::check_na(current)) { + if (!traits::is_na(current)) { detail::incr(&res[i], current); } } @@ -287,7 +256,7 @@ public: for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ current = ref(i, j); \ - if (!detail::check_na(current)) { \ + if (!traits::is_na<__RTYPE__>(current)) { \ detail::incr(&res[i], current); \ } \ } \ @@ -295,10 +264,10 @@ public: \ return res; \ } \ -}; +}; -ROW_SUMS_IMPL_RMNA(LGLSXP) -ROW_SUMS_IMPL_RMNA(INTSXP) +ROW_SUMS_IMPL_RMNA(LGLSXP) +ROW_SUMS_IMPL_RMNA(INTSXP) #undef ROW_SUMS_IMPL_RMNA @@ -362,10 +331,6 @@ private: typedef typename return_traits::type return_vector; \ typedef typename traits::storage_type::type stored_type; \ \ - struct bit { \ - unsigned char x : 1; \ - }; \ - \ public: \ ColSumsImpl(const MatrixBase<__RTYPE__, NA, T>& ref_) \ : ref(ref_) \ @@ -375,20 +340,13 @@ public: R_xlen_t i, j, nr = ref.nrow(), nc = ref.ncol(); \ return_vector res(nc); \ \ - std::vector na_flags(nc); \ - \ for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ - if (detail::check_na(ref(i, j))) { \ - na_flags[j].x |= 0x1; \ - } \ - detail::incr(&res[j], ref(i, j)); \ - } \ - } \ - \ - for (j = 0; j < nc; j++) { \ - if (na_flags[j].x) { \ - res[j] = NA_INTEGER; \ + if (traits::is_na<__RTYPE__>(res[j])) \ + continue; \ + if (traits::is_na<__RTYPE__>(ref(i, j))) \ + res[j] = traits::get_na<__RTYPE__>(); \ + else detail::incr(&res[j], ref(i, j)); \ } \ } \ \ @@ -396,11 +354,11 @@ public: } \ }; -COL_SUMS_IMPL_KEEPNA(LGLSXP) -COL_SUMS_IMPL_KEEPNA(INTSXP) +COL_SUMS_IMPL_KEEPNA(LGLSXP) +COL_SUMS_IMPL_KEEPNA(INTSXP) #undef COL_SUMS_IMPL_KEEPNA - + // ColSums // na.rm = TRUE // default input @@ -430,7 +388,7 @@ class ColSumsImpl : for (j = 0; j < nc; j++) { for (i = 0; i < nr; i++) { current = ref(i, j); - if (!detail::check_na(current)) { + if (!traits::is_na(current)) { detail::incr(&res[j], current); } } @@ -471,7 +429,7 @@ public: for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ current = ref(i, j); \ - if (!detail::check_na(current)) { \ + if (!traits::is_na<__RTYPE__>(current)) { \ detail::incr(&res[j], current); \ } \ } \ @@ -481,8 +439,8 @@ public: } \ }; -COL_SUMS_IMPL_RMNA(LGLSXP) -COL_SUMS_IMPL_RMNA(INTSXP) +COL_SUMS_IMPL_RMNA(LGLSXP) +COL_SUMS_IMPL_RMNA(INTSXP) #undef COL_SUMS_IMPL_RMNA @@ -553,10 +511,6 @@ private: typedef typename return_traits::type return_vector; \ typedef typename traits::storage_type::type stored_type; \ \ - struct bit { \ - unsigned char x : 1; \ - }; \ - \ public: \ RowMeansImpl(const MatrixBase<__RTYPE__, NA, T>& ref_) \ : ref(ref_) \ @@ -566,34 +520,29 @@ public: R_xlen_t i, j, nr = ref.nrow(), nc = ref.ncol(); \ return_vector res(nr); \ \ - std::vector na_flags(nc); \ - \ for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ - if (detail::check_na(ref(i, j))) { \ - na_flags[i].x |= 0x1; \ - } \ - detail::incr(&res[i], ref(i, j)); \ + if (traits::is_na(res[i])) \ + continue; \ + if (traits::is_na<__RTYPE__>(ref(i, j))) \ + res[i] = traits::get_na(); \ + else detail::incr(&res[i], ref(i, j)); \ } \ } \ \ - for (i = 0; i < nr; i++) { \ - if (!na_flags[i].x) { \ + for (i = 0; i < nr; i++) \ + if (!traits::is_na(res[i])) \ detail::div(&res[i], nc); \ - } else { \ - res[i] = NA_REAL; \ - } \ - } \ \ return res; \ } \ }; -ROW_MEANS_IMPL_KEEPNA(LGLSXP) -ROW_MEANS_IMPL_KEEPNA(INTSXP) +ROW_MEANS_IMPL_KEEPNA(LGLSXP) +ROW_MEANS_IMPL_KEEPNA(INTSXP) #undef ROW_MEANS_IMPL_KEEPNA - + // RowMeans // na.rm = TRUE // default input @@ -625,7 +574,7 @@ class RowMeansImpl : for (j = 0; j < nc; j++) { for (i = 0; i < nr; i++) { current = ref(i, j); - if (!detail::check_na(current)) { + if (!traits::is_na(current)) { detail::incr(&res[i], ref(i, j)); ++n_ok[i]; } @@ -674,7 +623,7 @@ public: \ for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ - if (!detail::check_na(ref(i, j))) { \ + if (!traits::is_na<__RTYPE__>(ref(i, j))) { \ detail::incr(&res[i], ref(i, j)); \ ++n_ok[i]; \ } \ @@ -693,8 +642,8 @@ public: } \ }; -ROW_MEANS_IMPL_RMNA(LGLSXP) -ROW_MEANS_IMPL_RMNA(INTSXP) +ROW_MEANS_IMPL_RMNA(LGLSXP) +ROW_MEANS_IMPL_RMNA(INTSXP) #undef ROW_MEANS_IMPL_RMNA @@ -762,10 +711,6 @@ private: typedef typename return_traits::type return_vector; \ typedef typename traits::storage_type::type stored_type; \ \ - struct bit { \ - unsigned char x : 1; \ - }; \ - \ public: \ ColMeansImpl(const MatrixBase<__RTYPE__, NA, T>& ref_) \ : ref(ref_) \ @@ -775,31 +720,26 @@ public: R_xlen_t i, j, nr = ref.nrow(), nc = ref.ncol(); \ return_vector res(nc); \ \ - std::vector na_flags(nc); \ - \ for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ - if (detail::check_na(ref(i, j))) { \ - na_flags[j].x |= 0x1; \ - } \ - detail::incr(&res[j], ref(i, j)); \ + if (traits::is_na(res[j])) \ + continue; \ + if (traits::is_na<__RTYPE__>(ref(i, j))) \ + res[j] = traits::get_na(); \ + else detail::incr(&res[j], ref(i, j)); \ } \ } \ \ - for (j = 0; j < nc; j++) { \ - if (!na_flags[j].x) { \ + for (j = 0; j < nc; j++) \ + if (!traits::is_na(res[j])) \ detail::div(&res[j], nr); \ - } else { \ - res[j] = NA_REAL; \ - } \ - } \ \ return res; \ } \ }; -COL_MEANS_IMPL_KEEPNA(LGLSXP) -COL_MEANS_IMPL_KEEPNA(INTSXP) +COL_MEANS_IMPL_KEEPNA(LGLSXP) +COL_MEANS_IMPL_KEEPNA(INTSXP) #undef COL_MEANS_IMPL_KEEPNA @@ -834,7 +774,7 @@ class ColMeansImpl : for (j = 0; j < nc; j++) { for (i = 0; i < nr; i++) { current = ref(i, j); - if (!detail::check_na(current)) { + if (!traits::is_na(current)) { detail::incr(&res[j], ref(i, j)); ++n_ok[j]; } @@ -883,7 +823,7 @@ public: \ for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ - if (!detail::check_na(ref(i, j))) { \ + if (!traits::is_na<__RTYPE__>(ref(i, j))) { \ detail::incr(&res[j], ref(i, j)); \ ++n_ok[j]; \ } \ @@ -902,11 +842,11 @@ public: } \ }; -COL_MEANS_IMPL_RMNA(LGLSXP) -COL_MEANS_IMPL_RMNA(INTSXP) +COL_MEANS_IMPL_RMNA(LGLSXP) +COL_MEANS_IMPL_RMNA(INTSXP) #undef COL_MEANS_IMPL_RMNA - + // ColMeans // Input with template parameter NA = false // ColMeansImpl<..., NA_RM = false> diff --git a/inst/include/Rcpp/sugar/functions/sum.h b/inst/include/Rcpp/sugar/functions/sum.h index 601a65a73..fac5d10d4 100644 --- a/inst/include/Rcpp/sugar/functions/sum.h +++ b/inst/include/Rcpp/sugar/functions/sum.h @@ -2,7 +2,8 @@ // // sum.h: Rcpp R/C++ interface class library -- sum // -// Copyright (C) 2010 - 2011 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -42,7 +43,7 @@ class Sum : public Lazy< typename Rcpp::traits::storage_type::type , Sum< current = object[i] ; if( Rcpp::traits::is_na(current) ) return Rcpp::traits::get_na() ; - result += current ; + result = RCPP_SAFE_ADD(result, current); } return result ; } @@ -84,7 +85,7 @@ class Sum : public Lazy< typename Rcpp::traits::storage_type( x ) ) return x ; STORAGE y = rhs[i] ; - return Rcpp::traits::is_na( y ) ? y : ( x - y ) ; + return Rcpp::traits::is_na( y ) ? y : RCPP_SAFE_SUB(x, y); } inline R_xlen_t size() const { return lhs.size() ; } @@ -91,7 +92,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE y = rhs[i] ; if( Rcpp::traits::is_na( y ) ) return y ; - return lhs[i] - y ; + return RCPP_SAFE_SUB(lhs[i], y); } inline R_xlen_t size() const { return lhs.size() ; } @@ -140,7 +141,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE x = lhs[i] ; if( Rcpp::traits::is_na( x ) ) return x ; - return x - rhs[i] ; + return RCPP_SAFE_SUB(x, rhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -188,7 +189,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_.get_ref()) {} inline STORAGE operator[]( R_xlen_t i ) const { - return lhs[i] - rhs[i] ; + return RCPP_SAFE_SUB(lhs[i], rhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -238,7 +239,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { if( rhs_na ) return rhs ; STORAGE x = lhs[i] ; - return Rcpp::traits::is_na(x) ? x : (x - rhs) ; + return Rcpp::traits::is_na(x) ? x : RCPP_SAFE_SUB(x, rhs); } inline R_xlen_t size() const { return lhs.size() ; } @@ -284,7 +285,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { if( rhs_na ) return rhs ; STORAGE x = lhs[i] ; - return Rcpp::traits::is_na(x) ? x : (x - rhs) ; + return Rcpp::traits::is_na(x) ? x : RCPP_SAFE_SUB(x, rhs); } inline R_xlen_t size() const { return lhs.size() ; } @@ -333,7 +334,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { if( lhs_na ) return lhs ; - return lhs - rhs[i] ; + return RCPP_SAFE_SUB(lhs, rhs[i]); } inline R_xlen_t size() const { return rhs.size() ; } @@ -370,14 +371,14 @@ namespace sugar{ public: typedef typename Rcpp::VectorBase VEC_TYPE ; typedef typename traits::storage_type::type STORAGE ; - typedef typename Rcpp::traits::Extractor::type VEC_EXT ; + typedef typename Rcpp::traits::Extractor::type VEC_EXT ; Minus_Primitive_Vector( STORAGE lhs_, const VEC_TYPE& rhs_ ) : lhs(lhs_), rhs(rhs_.get_ref()), lhs_na( Rcpp::traits::is_na(lhs_) ) {} inline STORAGE operator[]( R_xlen_t i ) const { if( lhs_na ) return lhs ; - return lhs - rhs[i] ; + return lhs - rhs[i]; } inline R_xlen_t size() const { return rhs.size() ; } diff --git a/inst/include/Rcpp/sugar/operators/plus.h b/inst/include/Rcpp/sugar/operators/plus.h index 70f9f0e44..31a32ab92 100644 --- a/inst/include/Rcpp/sugar/operators/plus.h +++ b/inst/include/Rcpp/sugar/operators/plus.h @@ -2,7 +2,8 @@ // // plus.h: Rcpp R/C++ interface class library -- operator+ // -// Copyright (C) 2010 - 2011 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -42,7 +43,7 @@ namespace sugar{ STORAGE lhs_ = lhs[i] ; if( traits::is_na(lhs_) ) return lhs_ ; STORAGE rhs_ = rhs[i] ; - return traits::is_na(rhs_) ? rhs_ : (lhs_ + rhs_) ; + return traits::is_na(rhs_) ? rhs_ : RCPP_SAFE_ADD(lhs_, rhs_); } inline R_xlen_t size() const { return lhs.size() ; } @@ -99,7 +100,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE rhs_ = rhs[i] ; if( traits::is_na(rhs_) ) return rhs_ ; - return lhs[i] + rhs_ ; + return RCPP_SAFE_ADD(lhs[i], rhs_); } inline R_xlen_t size() const { return lhs.size() ; } @@ -152,7 +153,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE lhs_ = lhs[i] ; if( traits::is_na(lhs_) ) return lhs_ ; - return lhs_ + rhs[i] ; + return RCPP_SAFE_ADD(lhs_, rhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -204,7 +205,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_.get_ref()){} inline STORAGE operator[]( R_xlen_t i ) const { - return lhs[i] + rhs[i] ; + return RCPP_SAFE_ADD(lhs[i], rhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -250,7 +251,6 @@ namespace sugar{ public: typedef typename Rcpp::VectorBase VEC_TYPE ; typedef typename traits::storage_type::type STORAGE ; - typedef typename Rcpp::traits::Extractor< RTYPE, NA, T>::type EXT ; Plus_Vector_Primitive( const VEC_TYPE& lhs_, STORAGE rhs_ ) : @@ -260,7 +260,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { if( rhs_na ) return rhs ; STORAGE x = lhs[i] ; - return Rcpp::traits::is_na(x) ? x : (x + rhs) ; + return Rcpp::traits::is_na(x) ? x : RCPP_SAFE_ADD(x, rhs); } inline R_xlen_t size() const { return lhs.size() ; } @@ -308,7 +308,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_), rhs_na( Rcpp::traits::is_na(rhs_) ) {} inline STORAGE operator[]( R_xlen_t i ) const { - return rhs_na ? rhs : (rhs + lhs[i] ) ; + return rhs_na ? rhs : (rhs + lhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -360,7 +360,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE x = lhs[i] ; - return Rcpp::traits::is_na(x) ? x : (x + rhs) ; + return Rcpp::traits::is_na(x) ? x : RCPP_SAFE_ADD(x, rhs); } inline R_xlen_t size() const { return lhs.size() ; } @@ -407,7 +407,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_) {} inline STORAGE operator[]( R_xlen_t i ) const { - return rhs + lhs[i] ; + return RCPP_SAFE_ADD(rhs, lhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } diff --git a/inst/include/Rcpp/sugar/operators/times.h b/inst/include/Rcpp/sugar/operators/times.h index d1f335992..d172d7741 100644 --- a/inst/include/Rcpp/sugar/operators/times.h +++ b/inst/include/Rcpp/sugar/operators/times.h @@ -2,7 +2,8 @@ // // times.h: Rcpp R/C++ interface class library -- operator* // -// Copyright (C) 2010 - 2011 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -43,7 +44,7 @@ namespace sugar{ STORAGE lhs_ = lhs[i] ; if( traits::is_na(lhs_) ) return lhs_ ; STORAGE rhs_ = rhs[i] ; - return traits::is_na(rhs_) ? rhs_ : (lhs_ * rhs_) ; + return traits::is_na(rhs_) ? rhs_ : RCPP_SAFE_MUL(lhs_, rhs_) ; } inline R_xlen_t size() const { return lhs.size() ; } @@ -96,7 +97,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE rhs_ = rhs[i] ; if( traits::is_na(rhs_) ) return rhs_ ; - return lhs[i] * rhs_ ; + return RCPP_SAFE_MUL(lhs[i], rhs_); } inline R_xlen_t size() const { return lhs.size() ; } @@ -148,7 +149,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE lhs_ = lhs[i] ; if( traits::is_na(lhs_) ) return lhs_ ; - return lhs_ * rhs[i] ; + return RCPP_SAFE_MUL(lhs_, rhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -197,7 +198,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_.get_ref()){} inline STORAGE operator[]( R_xlen_t i ) const { - return lhs[i] * rhs[i] ; + return RCPP_SAFE_MUL(lhs[i], rhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -247,7 +248,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { if( rhs_na ) return rhs ; STORAGE x = lhs[i] ; - return Rcpp::traits::is_na(x) ? x : (x * rhs) ; + return Rcpp::traits::is_na(x) ? x : RCPP_SAFE_MUL(x, rhs) ; } inline R_xlen_t size() const { return lhs.size() ; } @@ -293,7 +294,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_), rhs_na( Rcpp::traits::is_na(rhs_) ) {} inline STORAGE operator[]( R_xlen_t i ) const { - return rhs_na ? rhs : (rhs * lhs[i] ) ; + return rhs_na ? rhs : (rhs * lhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -345,7 +346,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE x = lhs[i] ; - return Rcpp::traits::is_na(x) ? x : (x * rhs) ; + return Rcpp::traits::is_na(x) ? x : RCPP_SAFE_MUL(x, rhs); } inline R_xlen_t size() const { return lhs.size() ; } @@ -391,7 +392,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_) {} inline STORAGE operator[]( R_xlen_t i ) const { - return rhs * lhs[i] ; + return RCPP_SAFE_MUL(rhs, lhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } diff --git a/inst/include/Rcpp/sugar/sugar.h b/inst/include/Rcpp/sugar/sugar.h index 0e2c2e37b..1bf160ab9 100644 --- a/inst/include/Rcpp/sugar/sugar.h +++ b/inst/include/Rcpp/sugar/sugar.h @@ -2,7 +2,8 @@ // // sugar.h: Rcpp R/C++ interface class library -- main file for Rcpp::sugar // -// Copyright (C) 2010 - 2012 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -23,6 +24,7 @@ #define RCPP_SUGAR_H #include +#include #include #include diff --git a/inst/include/Rcpp/sugar/tools/safe_math.h b/inst/include/Rcpp/sugar/tools/safe_math.h new file mode 100644 index 000000000..46774850d --- /dev/null +++ b/inst/include/Rcpp/sugar/tools/safe_math.h @@ -0,0 +1,134 @@ +// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; tab-width: 8 -*- +// +// safe_math.h: Rcpp R/C++ interface class library -- +// +// Copyright (C) 2026 Iñaki Ucar +// +// This file is part of Rcpp. +// +// Rcpp is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// Rcpp is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Rcpp. If not, see . + +#ifndef Rcpp__sugar__tools_safe_math_h +#define Rcpp__sugar__tools_safe_math_h + +#ifndef safe_math__has_builtin +# ifdef __has_builtin +# define safe_math__has_builtin(x) __has_builtin(x) +# else +# define safe_math__has_builtin(x) 0 +# endif +#endif + +#define RCPP_SAFE_ADD(a, b) Rcpp::sugar::detail::safe_add(a, b, __func__) +#define RCPP_SAFE_SUB(a, b) Rcpp::sugar::detail::safe_sub(a, b, __func__) +#define RCPP_SAFE_MUL(a, b) Rcpp::sugar::detail::safe_mul(a, b, __func__) + +namespace Rcpp { +namespace sugar { +namespace detail { + + inline void stop_overflow(const char* caller) { + if (caller) + Rcpp::stop("[%s] Integer overflow!", caller); + Rcpp::stop("Integer overflow!"); + } + + // Addition + template + inline typename std::enable_if::value, T>::type + safe_add(T a, T b, const char* caller = nullptr) { + #if safe_math__has_builtin(__builtin_add_overflow) + T result; + if (__builtin_add_overflow(a, b, &result)) + stop_overflow(caller); + return result; + #else // fallback + if (std::is_signed::value) { + if ((b > 0 && a > std::numeric_limits::max() - b) || + (b < 0 && a < std::numeric_limits::min() - b)) + stop_overflow(caller); + } else { + if (a > std::numeric_limits::max() - b) + stop_overflow(caller); + } + return a + b; + #endif + } + + template + inline typename std::enable_if::value, T>::type + safe_add(T a, T b, const char* caller = nullptr) { return a + b; } + + // Subtraction + template + inline typename std::enable_if::value, T>::type + safe_sub(T a, T b, const char* caller = nullptr) { + #if safe_math__has_builtin(__builtin_sub_overflow) + T result; + if (__builtin_sub_overflow(a, b, &result)) + stop_overflow(caller); + return result; + #else // fallback + if (std::is_signed::value) { + if ((b < 0 && a > std::numeric_limits::max() + b) || + (b > 0 && a < std::numeric_limits::min() + b)) + stop_overflow(caller); + } else { + if (a < b) + stop_overflow(caller); + } + return a - b; + #endif + } + + template + inline typename std::enable_if::value, T>::type + safe_sub(T a, T b, const char* caller = nullptr) { return a - b; } + + // Multiplication + template + inline typename std::enable_if::value, T>::type + safe_mul(T a, T b, const char* caller = nullptr) { + #if safe_math__has_builtin(__builtin_mul_overflow) + T result; + if (__builtin_mul_overflow(a, b, &result)) + stop_overflow(caller); + return result; + #else // fallback + if (a == 0 || b == 0) return 0; + if (std::is_signed::value) { + if ((a > 0 && b > 0 && a > std::numeric_limits::max() / b) || + (a > 0 && b < 0 && b < std::numeric_limits::min() / a) || + (a < 0 && b > 0 && a < std::numeric_limits::min() / b) || + (a < 0 && b < 0 && a < std::numeric_limits::max() / b)) + stop_overflow(caller); + } else { + if (b > 0 && a > std::numeric_limits::max() / b) + stop_overflow(caller); + } + return a * b; + #endif + } + + template + inline typename std::enable_if::value, T>::type + safe_mul(T a, T b, const char* caller = nullptr) { return a * b; } + +} // namespace detail +} // namespace sugar +} // namespace Rcpp + +#undef safe_math__has_builtin + +#endif diff --git a/inst/tinytest/cpp/sugar.cpp b/inst/tinytest/cpp/sugar.cpp index d436559b9..19e771ef7 100644 --- a/inst/tinytest/cpp/sugar.cpp +++ b/inst/tinytest/cpp/sugar.cpp @@ -1,8 +1,8 @@ // sugar.cpp: Rcpp R/C++ interface class library -- sugar unit tests // -// Copyright (C) 2012 - 2025 Dirk Eddelbuettel and Romain Francois -// Copyright (C) 2025 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar +// Copyright (C) 2012 - 2024 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2025 - 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -285,6 +285,21 @@ List runit_minus( IntegerVector xx ){ ) ; } +// [[Rcpp::export]] +IntegerVector runit_minus_ivv( IntegerVector x, IntegerVector y ){ + return x - y; +} + +// [[Rcpp::export]] +IntegerVector runit_minus_ivp( IntegerVector x, int y ){ + return x - y; +} + +// [[Rcpp::export]] +IntegerVector runit_minus_ipv( int x, IntegerVector y ){ + return x - y; +} + // [[Rcpp::export]] LogicalVector runit_any_equal_not( NumericVector xx, NumericVector yy){ return any( !( xx == yy) ) ; @@ -300,6 +315,21 @@ List runit_plus( IntegerVector xx ){ ) ; } +// [[Rcpp::export]] +IntegerVector runit_plus_ivv( IntegerVector x, IntegerVector y ){ + return x + y; +} + +// [[Rcpp::export]] +IntegerVector runit_plus_ivp( IntegerVector x, int y ){ + return x + y; +} + +// [[Rcpp::export]] +IntegerVector runit_plus_ipv( int x, IntegerVector y ){ + return x + y; +} + // [[Rcpp::export]] List runit_plus_seqlen(){ return List::create( @@ -309,6 +339,24 @@ List runit_plus_seqlen(){ ) ; } +// [[Rcpp::export]] +List runit_minus_seqlen(){ + return List::create( + seq_len(10) - 10, + 10 - seq_len(10), + seq_len(10) - seq_len(10) + ) ; +} + +// [[Rcpp::export]] +List runit_times_seqlen(){ + return List::create( + seq_len(10) * 10, + 10 * seq_len(10), + seq_len(10) * seq_len(10) + ) ; +} + // [[Rcpp::export]] LogicalVector runit_plus_all( IntegerVector xx ){ return all( (xx+xx) < 10 ) ; @@ -428,6 +476,21 @@ List runit_times( IntegerVector xx ){ ) ; } +// [[Rcpp::export]] +IntegerVector runit_times_ivv( IntegerVector x, IntegerVector y ){ + return x * y; +} + +// [[Rcpp::export]] +IntegerVector runit_times_ivp( IntegerVector x, int y ){ + return x * y; +} + +// [[Rcpp::export]] +IntegerVector runit_times_ipv( int x, IntegerVector y ){ + return x * y; +} + // [[Rcpp::export]] List runit_divides( NumericVector xx ){ return List::create( @@ -604,16 +667,27 @@ List runit_log1p( NumericVector xx){ } // [[Rcpp::export]] -double runit_sum( NumericVector xx){ +double runit_sum_nv( NumericVector xx){ return sum( xx ) ; } // [[Rcpp::export]] -NumericVector runit_cumsum( NumericVector xx ){ +int runit_sum_iv( IntegerVector xx){ + return sum( xx ) ; +} + +// [[Rcpp::export]] +NumericVector runit_cumsum_nv( NumericVector xx ){ NumericVector res = cumsum( xx ) ; return res ; } +// [[Rcpp::export]] +IntegerVector runit_cumsum_iv( IntegerVector xx ){ + IntegerVector res = cumsum( xx ) ; + return res ; +} + // [[Rcpp::export]] List runit_asvector( NumericMatrix z, NumericVector x, NumericVector y){ return List::create( diff --git a/inst/tinytest/cpp/sugar_safe_math.cpp b/inst/tinytest/cpp/sugar_safe_math.cpp new file mode 100644 index 000000000..9677184b9 --- /dev/null +++ b/inst/tinytest/cpp/sugar_safe_math.cpp @@ -0,0 +1,36 @@ + +// sugar_safe_math.cpp: Rcpp R/C++ interface class library -- safe math unit tests +// +// Copyright (C) 2026 Iñaki Ucar +// +// This file is part of Rcpp. +// +// Rcpp is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// Rcpp is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Rcpp. If not, see . + +#include + +// [[Rcpp::export]] +int safe_add(int a, int b){ + return RCPP_SAFE_ADD(a, b); +} + +// [[Rcpp::export]] +int safe_sub(int a, int b){ + return RCPP_SAFE_SUB(a, b); +} + +// [[Rcpp::export]] +int safe_mul(int a, int b){ + return RCPP_SAFE_MUL(a, b); +} diff --git a/inst/tinytest/cpp/sugar_safe_math_fallback.cpp b/inst/tinytest/cpp/sugar_safe_math_fallback.cpp new file mode 100644 index 000000000..15fdcc2e9 --- /dev/null +++ b/inst/tinytest/cpp/sugar_safe_math_fallback.cpp @@ -0,0 +1,37 @@ + +// sugar_safe_math.cpp: Rcpp R/C++ interface class library -- safe math unit tests +// +// Copyright (C) 2026 Iñaki Ucar +// +// This file is part of Rcpp. +// +// Rcpp is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// Rcpp is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Rcpp. If not, see . + +#define safe_math__has_builtin(x) 0 +#include + +// [[Rcpp::export]] +int safe_add_fallback(int a, int b){ + return RCPP_SAFE_ADD(a, b); +} + +// [[Rcpp::export]] +int safe_sub_fallback(int a, int b){ + return RCPP_SAFE_SUB(a, b); +} + +// [[Rcpp::export]] +int safe_mul_fallback(int a, int b){ + return RCPP_SAFE_MUL(a, b); +} diff --git a/inst/tinytest/test_sugar.R b/inst/tinytest/test_sugar.R index 4465cedf7..c623aefa1 100644 --- a/inst/tinytest/test_sugar.R +++ b/inst/tinytest/test_sugar.R @@ -1,6 +1,6 @@ -## Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois -## Copyright (C) 2025 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar +## Copyright (C) 2010 - 2024 Dirk Eddelbuettel and Romain Francois +## Copyright (C) 2025 - 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar ## ## This file is part of Rcpp. ## @@ -20,6 +20,8 @@ if (Sys.getenv("RunAllRcppTests") != "yes") exit_file("Set 'RunAllRcppTests' to 'yes' to run.") Rcpp::sourceCpp("cpp/sugar.cpp") +Rcpp::sourceCpp("cpp/sugar_safe_math.cpp") +Rcpp::sourceCpp("cpp/sugar_safe_math_fallback.cpp") ## There are some (documented, see https://blog.r-project.org/2020/11/02/will-r-work-on-apple-silicon/index.html) ## issues with NA propagation on arm64 / macOS. We not (yet ?) do anything special so we just skip some tests @@ -29,6 +31,22 @@ isArm <- Sys.info()[["machine"]] == "arm64" || Sys.info()[["machine"]] == "aarch ## Needed for a change in R 3.6.0 reducing a bias in very large samples suppressWarnings(RNGversion("3.5.0")) +# test.sugar.safe_math +expect_equal(safe_add(3, 2), 5) +expect_error(safe_add(.Machine$integer.max, 2), "overflow") +expect_equal(safe_sub(3, 2), 1) +expect_error(safe_sub(-.Machine$integer.max, 2), "overflow") +expect_equal(safe_mul(3, 2), 6) +expect_error(safe_mul(.Machine$integer.max, 2), "overflow") + +expect_equal(safe_add_fallback(3, 2), 5) +expect_error(safe_add_fallback(.Machine$integer.max, 2), "overflow") +expect_equal(safe_sub_fallback(3, 2), 1) +expect_error(safe_sub_fallback(-.Machine$integer.max, 2), "overflow") +expect_equal(safe_mul_fallback(3, 2), 6) +expect_error(safe_mul_fallback(.Machine$integer.max, 2), "overflow") + + # test.sugar.abs <- function( ){ x <- rnorm(10) y <- -10:10 @@ -210,6 +228,7 @@ expect_true( identical( fx( NA, 1 ), NA ) ) # test.sugar.diff <- function( ){ +expect_error(runit_diff_int(c(1, 2, -.Machine$integer.max)), "overflow") x <- as.integer(round(rnorm(100,1,100))) expect_equal( runit_diff_int(x) , diff(x) ) x <- rnorm( 100 ) @@ -348,11 +367,19 @@ expect_equal( fx(1:10, 1:10*2) , mapply(seq, 1:10, 1:10*2) ) # test.sugar.minus <- function( ){ +expect_error(runit_minus_ivv(-.Machine$integer.max, 2), "overflow") +expect_error(runit_minus_ivp(-.Machine$integer.max, 2), "overflow") +expect_error(runit_minus_ipv(-.Machine$integer.max, 2), "overflow") fx <- runit_minus expect_equal(fx(1:10) , list( (1:10)-10L, 10L-(1:10), rep(0L,10), (1:10)-10L, 10L-(1:10) )) +# test.sugar.minus.seqlen <- function( ){ +fx <- runit_minus_seqlen +expect_equal( fx() , list( -9:0, 9:0, rep(0, 10)) ) + + # test.sugar.any.equal.not <- function( ){ fx <- runit_any_equal_not expect_true( ! fx( 1, 1 ) ) @@ -363,6 +390,9 @@ expect_true( is.na( fx( NA, 1 ) ) ) # test.sugar.plus <- function( ){ +expect_error(runit_plus_ivv(.Machine$integer.max, 2), "overflow") +expect_error(runit_plus_ivp(.Machine$integer.max, 2), "overflow") +expect_error(runit_plus_ipv(.Machine$integer.max, 2), "overflow") fx <- runit_plus expect_equal( fx(1:10) , list( 11:20,11:20,1:10+1:10, 3*(1:10)) ) @@ -455,12 +485,20 @@ expect_equal(fx( seq(-10, 10, length.out = 51), -25:25 ), # test.sugar.times <- function( ){ +expect_error(runit_times_ivv(.Machine$integer.max, 2), "overflow") +expect_error(runit_times_ivp(.Machine$integer.max, 2), "overflow") +expect_error(runit_times_ipv(.Machine$integer.max, 2), "overflow") fx <- runit_times expect_equal(fx(1:10) , list(10L*(1:10), 10L*(1:10), (1:10)*(1:10), (1:10)*(1:10)*(1:10), c(NA,(2:10)*(2:10)), c(NA,10L*(2:10)), c(NA,10L*(2:10)), rep( NA_integer_, 10L ))) +# test.sugar.times.seqlen <- function( ){ +fx <- runit_times_seqlen +expect_equal( fx() , list( seq(10, 100, 10), seq(10, 100, 10), 1:10*1:10) ) + + # test.sugar.divides <- function( ){ fx <- runit_divides expect_equal(fx(1:10) , @@ -626,20 +664,38 @@ expect_equal(fx(10:6,5:1), VP = psigamma( 10:6, 5 ))) -# test.sugar.sum <- function(){ -fx <- runit_sum +# test.sugar.sum_nv <- function(){ +fx <- runit_sum_nv x <- rnorm( 10 ) expect_equal( fx(x), sum(x) ) x[4] <- NA expect_equal( fx(x), sum(x) ) -# test.sugar.cumsum <- function(){ -fx <- runit_cumsum -x <- rnorm( 10 ) -expect_equal( fx(x), cumsum(x) ) +# test.sugar.sum_iv <- function() { +expect_error(runit_sum_iv(c(2, .Machine$integer.max)), "overflow") +fx <- runit_sum_iv +x <- as.integer(rpois(10, 5)) +expect_equal(fx(x), sum(x)) x[4] <- NA -expect_equal( fx(x), cumsum(x) ) +expect_equal(fx(x), sum(x)) + + +# test.sugar.cumsum_nv <- function(){ +fx <- runit_cumsum_nv +x <- rnorm(10) +expect_equal(fx(x), cumsum(x)) +x[4] <- NA +expect_equal(fx(x), cumsum(x)) + + +# test.sugar.cumsum_iv <- function() { +expect_error(runit_cumsum_iv(c(2, .Machine$integer.max)), "overflow") +fx <- runit_cumsum_iv +x <- as.integer(rpois(10, 5)) +expect_equal(fx(x), cumsum(x)) +x[4] <- NA +expect_equal(fx(x), cumsum(x)) # test.sugar.asvector <- function(){ @@ -826,6 +882,7 @@ expect_equal(fx(x), cumprod(x)) # test.sugar.cumprod_iv <- function() { +expect_error(runit_cumprod_iv(c(2, .Machine$integer.max)), "overflow") fx <- runit_cumprod_iv x <- as.integer(rpois(10, 5)) expect_equal(fx(x), cumprod(x)) @@ -1160,6 +1217,11 @@ expect_equal(dbl_col_means(x, TRUE), colMeans(x, TRUE), info = "numeric / colMea ## {row,col}{Sums,Means} integer tests # test.sugar.rowMeans_integer <- function() { +x <- matrix(rep(.Machine$integer.max, 4), 2) + +expect_error(int_row_sums(x), "overflow") +expect_error(int_col_sums(x), "overflow") + x <- matrix(as.integer(rnorm(9) * 1e4), 3) expect_equal(int_row_sums(x), rowSums(x), info = "integer / rowSums / keep NA / clean input")