Skip to content

Commit 38178a8

Browse files
committed
implementation of safe add/sub/mul for integers
1 parent 7ff6ba2 commit 38178a8

File tree

5 files changed

+228
-3
lines changed

5 files changed

+228
-3
lines changed

inst/include/Rcpp/sugar/sugar.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
//
33
// sugar.h: Rcpp R/C++ interface class library -- main file for Rcpp::sugar
44
//
5-
// Copyright (C) 2010 - 2012 Dirk Eddelbuettel and Romain Francois
5+
// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois
6+
// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar
67
//
78
// This file is part of Rcpp.
89
//
@@ -23,6 +24,7 @@
2324
#define RCPP_SUGAR_H
2425

2526
#include <Rcpp/sugar/tools/iterator.h>
27+
#include <Rcpp/sugar/tools/safe_math.h>
2628
#include <Rcpp/sugar/block/block.h>
2729

2830
#include <Rcpp/hash/hash.h>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; tab-width: 8 -*-
2+
//
3+
// safe_math.h: Rcpp R/C++ interface class library --
4+
//
5+
// Copyright (C) 2026 Iñaki Ucar
6+
//
7+
// This file is part of Rcpp.
8+
//
9+
// Rcpp is free software: you can redistribute it and/or modify it
10+
// under the terms of the GNU General Public License as published by
11+
// the Free Software Foundation, either version 2 of the License, or
12+
// (at your option) any later version.
13+
//
14+
// Rcpp is distributed in the hope that it will be useful, but
15+
// WITHOUT ANY WARRANTY; without even the implied warranty of
16+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
// GNU General Public License for more details.
18+
//
19+
// You should have received a copy of the GNU General Public License
20+
// along with Rcpp. If not, see <http://www.gnu.org/licenses/>.
21+
22+
#ifndef Rcpp__sugar__tools_safe_math_h
23+
#define Rcpp__sugar__tools_safe_math_h
24+
25+
#ifndef safe_math__has_builtin
26+
# ifdef __has_builtin
27+
# define safe_math__has_builtin(x) __has_builtin(x)
28+
# else
29+
# define safe_math__has_builtin(x) 0
30+
# endif
31+
#endif
32+
33+
#define RCPP_SAFE_ADD(a, b) Rcpp::sugar::safe_add(a, b, __func__)
34+
#define RCPP_SAFE_SUB(a, b) Rcpp::sugar::safe_sub(a, b, __func__)
35+
#define RCPP_SAFE_MUL(a, b) Rcpp::sugar::safe_mul(a, b, __func__)
36+
37+
namespace Rcpp {
38+
namespace sugar {
39+
40+
inline void stop_overflow(const char* caller) {
41+
if (caller)
42+
Rcpp::stop("[%s] Integer overflow!", caller);
43+
Rcpp::stop("Integer overflow!");
44+
}
45+
46+
// Addition
47+
template <typename T>
48+
inline typename std::enable_if<std::is_integral<T>::value, T>::type
49+
safe_add(T a, T b, const char* caller = nullptr) {
50+
#if safe_math__has_builtin(__builtin_add_overflow)
51+
T result;
52+
if (__builtin_add_overflow(a, b, &result))
53+
stop_overflow(caller);
54+
return result;
55+
#else // fallback
56+
if (std::is_signed<T>::value) {
57+
if ((b > 0 && a > std::numeric_limits<T>::max() - b) ||
58+
(b < 0 && a < std::numeric_limits<T>::min() - b))
59+
stop_overflow(caller);
60+
} else {
61+
if (a > std::numeric_limits<T>::max() - b)
62+
stop_overflow(caller);
63+
}
64+
return a + b;
65+
#endif
66+
}
67+
68+
template <typename T>
69+
inline typename std::enable_if<!std::is_integral<T>::value, T>::type
70+
safe_add(T a, T b, const char* caller = nullptr) { return a + b; }
71+
72+
// Subtraction
73+
template <typename T>
74+
inline typename std::enable_if<std::is_integral<T>::value, T>::type
75+
safe_sub(T a, T b, const char* caller = nullptr) {
76+
#if safe_math__has_builtin(__builtin_sub_overflow)
77+
T result;
78+
if (__builtin_sub_overflow(a, b, &result))
79+
stop_overflow(caller);
80+
return result;
81+
#else // fallback
82+
if (std::is_signed<T>::value) {
83+
if ((b < 0 && a > std::numeric_limits<T>::max() + b) ||
84+
(b > 0 && a < std::numeric_limits<T>::min() + b))
85+
stop_overflow(caller);
86+
} else {
87+
if (a < b)
88+
stop_overflow(caller);
89+
}
90+
return a - b;
91+
#endif
92+
}
93+
94+
template <typename T>
95+
inline typename std::enable_if<!std::is_integral<T>::value, T>::type
96+
safe_sub(T a, T b, const char* caller = nullptr) { return a - b; }
97+
98+
// Multiplication
99+
template <typename T>
100+
inline typename std::enable_if<std::is_integral<T>::value, T>::type
101+
safe_mul(T a, T b, const char* caller = nullptr) {
102+
#if safe_math__has_builtin(__builtin_mul_overflow)
103+
T result;
104+
if (__builtin_mul_overflow(a, b, &result))
105+
stop_overflow(caller);
106+
return result;
107+
#else // fallback
108+
if (a == 0 || b == 0) return 0;
109+
if (std::is_signed<T>::value) {
110+
if ((a > 0 && b > 0 && a > std::numeric_limits<T>::max() / b) ||
111+
(a > 0 && b < 0 && b < std::numeric_limits<T>::min() / a) ||
112+
(a < 0 && b > 0 && a < std::numeric_limits<T>::min() / b) ||
113+
(a < 0 && b < 0 && a < std::numeric_limits<T>::max() / b))
114+
stop_overflow(caller);
115+
} else {
116+
if (b > 0 && a > std::numeric_limits<T>::max() / b)
117+
stop_overflow(caller);
118+
}
119+
return a * b;
120+
#endif
121+
}
122+
123+
template <typename T>
124+
inline typename std::enable_if<!std::is_integral<T>::value, T>::type
125+
safe_mul(T a, T b, const char* caller = nullptr) { return a * b; }
126+
127+
} // namespace sugar
128+
} // namespace Rcpp
129+
130+
#undef safe_math__has_builtin
131+
132+
#endif
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
// sugar_safe_math.cpp: Rcpp R/C++ interface class library -- safe math unit tests
3+
//
4+
// Copyright (C) 2026 Iñaki Ucar
5+
//
6+
// This file is part of Rcpp.
7+
//
8+
// Rcpp is free software: you can redistribute it and/or modify it
9+
// under the terms of the GNU General Public License as published by
10+
// the Free Software Foundation, either version 2 of the License, or
11+
// (at your option) any later version.
12+
//
13+
// Rcpp is distributed in the hope that it will be useful, but
14+
// WITHOUT ANY WARRANTY; without even the implied warranty of
15+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
// GNU General Public License for more details.
17+
//
18+
// You should have received a copy of the GNU General Public License
19+
// along with Rcpp. If not, see <http://www.gnu.org/licenses/>.
20+
21+
#include <Rcpp.h>
22+
23+
// [[Rcpp::export]]
24+
int safe_add(int a, int b){
25+
return RCPP_SAFE_ADD(a, b);
26+
}
27+
28+
// [[Rcpp::export]]
29+
int safe_sub(int a, int b){
30+
return RCPP_SAFE_SUB(a, b);
31+
}
32+
33+
// [[Rcpp::export]]
34+
int safe_mul(int a, int b){
35+
return RCPP_SAFE_MUL(a, b);
36+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
// sugar_safe_math.cpp: Rcpp R/C++ interface class library -- safe math unit tests
3+
//
4+
// Copyright (C) 2026 Iñaki Ucar
5+
//
6+
// This file is part of Rcpp.
7+
//
8+
// Rcpp is free software: you can redistribute it and/or modify it
9+
// under the terms of the GNU General Public License as published by
10+
// the Free Software Foundation, either version 2 of the License, or
11+
// (at your option) any later version.
12+
//
13+
// Rcpp is distributed in the hope that it will be useful, but
14+
// WITHOUT ANY WARRANTY; without even the implied warranty of
15+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
// GNU General Public License for more details.
17+
//
18+
// You should have received a copy of the GNU General Public License
19+
// along with Rcpp. If not, see <http://www.gnu.org/licenses/>.
20+
21+
#define define safe_math__has_builtin(x) 0
22+
#include <Rcpp.h>
23+
24+
// [[Rcpp::export]]
25+
int safe_add_fallback(int a, int b){
26+
return RCPP_SAFE_ADD(a, b);
27+
}
28+
29+
// [[Rcpp::export]]
30+
int safe_sub_fallback(int a, int b){
31+
return RCPP_SAFE_SUB(a, b);
32+
}
33+
34+
// [[Rcpp::export]]
35+
int safe_mul_fallback(int a, int b){
36+
return RCPP_SAFE_MUL(a, b);
37+
}

inst/tinytest/test_sugar.R

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

2-
## Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois
3-
## Copyright (C) 2025 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar
2+
## Copyright (C) 2010 - 2024 Dirk Eddelbuettel and Romain Francois
3+
## Copyright (C) 2025 - 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar
44
##
55
## This file is part of Rcpp.
66
##
@@ -20,6 +20,8 @@
2020
if (Sys.getenv("RunAllRcppTests") != "yes") exit_file("Set 'RunAllRcppTests' to 'yes' to run.")
2121

2222
Rcpp::sourceCpp("cpp/sugar.cpp")
23+
Rcpp::sourceCpp("cpp/sugar_safe_math.cpp")
24+
Rcpp::sourceCpp("cpp/sugar_safe_math_fallback.cpp")
2325

2426
## There are some (documented, see https://blog.r-project.org/2020/11/02/will-r-work-on-apple-silicon/index.html)
2527
## 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
2931
## Needed for a change in R 3.6.0 reducing a bias in very large samples
3032
suppressWarnings(RNGversion("3.5.0"))
3133

34+
# test.sugar.safe_math
35+
expect_equal(safe_add(3, 2), 5)
36+
expect_error(safe_add(.Machine$integer.max, 2))
37+
expect_equal(safe_sub(3, 2), 1)
38+
expect_error(safe_sub(.Machine$integer.min, 2))
39+
expect_equal(safe_mul(3, 2), 6)
40+
expect_error(safe_mul(.Machine$integer.max, 2))
41+
42+
expect_equal(safe_add_fallback(3, 2), 5)
43+
expect_error(safe_add_fallback(.Machine$integer.max, 2))
44+
expect_equal(safe_sub_fallback(3, 2), 1)
45+
expect_error(safe_sub_fallback(.Machine$integer.min, 2))
46+
expect_equal(safe_mul_fallback(3, 2), 6)
47+
expect_error(safe_mul_fallback(.Machine$integer.max, 2))
48+
49+
3250
# test.sugar.abs <- function( ){
3351
x <- rnorm(10)
3452
y <- -10:10

0 commit comments

Comments
 (0)