Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion inst/include/Rcpp/sugar/sugar.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand All @@ -23,6 +24,7 @@
#define RCPP_SUGAR_H

#include <Rcpp/sugar/tools/iterator.h>
#include <Rcpp/sugar/tools/safe_math.h>
#include <Rcpp/sugar/block/block.h>

#include <Rcpp/hash/hash.h>
Expand Down
132 changes: 132 additions & 0 deletions inst/include/Rcpp/sugar/tools/safe_math.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// -*- 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 <http://www.gnu.org/licenses/>.

#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::safe_add(a, b, __func__)
#define RCPP_SAFE_SUB(a, b) Rcpp::sugar::safe_sub(a, b, __func__)
#define RCPP_SAFE_MUL(a, b) Rcpp::sugar::safe_mul(a, b, __func__)

namespace Rcpp {
namespace sugar {

inline void stop_overflow(const char* caller) {
if (caller)
Rcpp::stop("[%s] Integer overflow!", caller);
Rcpp::stop("Integer overflow!");
}

// Addition
template <typename T>
inline typename std::enable_if<std::is_integral<T>::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<T>::value) {
if ((b > 0 && a > std::numeric_limits<T>::max() - b) ||
(b < 0 && a < std::numeric_limits<T>::min() - b))
stop_overflow(caller);
} else {
if (a > std::numeric_limits<T>::max() - b)
stop_overflow(caller);
}
return a + b;
#endif
}

template <typename T>
inline typename std::enable_if<!std::is_integral<T>::value, T>::type
safe_add(T a, T b, const char* caller = nullptr) { return a + b; }

// Subtraction
template <typename T>
inline typename std::enable_if<std::is_integral<T>::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<T>::value) {
if ((b < 0 && a > std::numeric_limits<T>::max() + b) ||
(b > 0 && a < std::numeric_limits<T>::min() + b))
stop_overflow(caller);
} else {
if (a < b)
stop_overflow(caller);
}
return a - b;
#endif
}

template <typename T>
inline typename std::enable_if<!std::is_integral<T>::value, T>::type
safe_sub(T a, T b, const char* caller = nullptr) { return a - b; }

// Multiplication
template <typename T>
inline typename std::enable_if<std::is_integral<T>::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<T>::value) {
if ((a > 0 && b > 0 && a > std::numeric_limits<T>::max() / b) ||
(a > 0 && b < 0 && b < std::numeric_limits<T>::min() / a) ||
(a < 0 && b > 0 && a < std::numeric_limits<T>::min() / b) ||
(a < 0 && b < 0 && a < std::numeric_limits<T>::max() / b))
stop_overflow(caller);
} else {
if (b > 0 && a > std::numeric_limits<T>::max() / b)
stop_overflow(caller);
}
return a * b;
#endif
}

template <typename T>
inline typename std::enable_if<!std::is_integral<T>::value, T>::type
safe_mul(T a, T b, const char* caller = nullptr) { return a * b; }

} // namespace sugar
} // namespace Rcpp

#undef safe_math__has_builtin

#endif
36 changes: 36 additions & 0 deletions inst/tinytest/cpp/sugar_safe_math.cpp
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

#include <Rcpp.h>

// [[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);
}
37 changes: 37 additions & 0 deletions inst/tinytest/cpp/sugar_safe_math_fallback.cpp
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

#define define safe_math__has_builtin(x) 0
#include <Rcpp.h>

// [[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);
}
22 changes: 20 additions & 2 deletions inst/tinytest/test_sugar.R
Original file line number Diff line number Diff line change
@@ -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.
##
Expand All @@ -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
Expand All @@ -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))
expect_equal(safe_sub(3, 2), 1)
expect_error(safe_sub(.Machine$integer.min, 2))
expect_equal(safe_mul(3, 2), 6)
expect_error(safe_mul(.Machine$integer.max, 2))

expect_equal(safe_add_fallback(3, 2), 5)
expect_error(safe_add_fallback(.Machine$integer.max, 2))
expect_equal(safe_sub_fallback(3, 2), 1)
expect_error(safe_sub_fallback(.Machine$integer.min, 2))
expect_equal(safe_mul_fallback(3, 2), 6)
expect_error(safe_mul_fallback(.Machine$integer.max, 2))


# test.sugar.abs <- function( ){
x <- rnorm(10)
y <- -10:10
Expand Down