Skip to content

Commit 69505bb

Browse files
mns-nordicalst-kalinowski
authored andcommitted
Compute integer %/% in double precision
The integer branch of %/% emitted int(floor(real(a) / real(b))); kind-less real() of an integer is default (single-precision) real, which cannot represent odd integers above 2^24, so e.g. 16777219L %/% 2L returned 8388610 instead of R's 8388609 — a silent wrong answer. Cast both operands with kind=c_double and the result with kind=c_int.
1 parent 47961f6 commit 69505bb

3 files changed

Lines changed: 97 additions & 1 deletion

File tree

R/r2f-arithmetic.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ r2f_handlers[["%/%"]] <- function(args, scope, ...) {
8383

8484
expr <- switch(
8585
out_val@mode,
86-
integer = glue("int(floor(real({left}) / real({right})))"),
86+
integer = glue(
87+
"int(floor(real({left}, kind=c_double) / real({right}, kind=c_double)), kind=c_int)"
88+
),
8789
double = glue("floor({left} / {right})"),
8890
stop("%/% only implemented for numeric types")
8991
)

tests/testthat/_snaps/div-mod.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,78 @@
160160
return out_;
161161
}
162162

163+
# integer %/% is exact beyond 2^24
164+
165+
Code
166+
fn
167+
Output
168+
function(a, b) {
169+
declare(type(a = integer(1)), type(b = integer(1)))
170+
a %/% b
171+
}
172+
<environment: 0x0>
173+
Code
174+
cat(fsub)
175+
Output
176+
subroutine fn(a, b, out_) bind(c)
177+
use iso_c_binding, only: c_double, c_int
178+
implicit none
179+
180+
! manifest start
181+
! args
182+
integer(c_int), intent(in) :: a
183+
integer(c_int), intent(in) :: b
184+
integer(c_int), intent(out) :: out_
185+
! manifest end
186+
187+
188+
out_ = int(floor(real(a, kind=c_double) / real(b, kind=c_double)), kind=c_int)
189+
end subroutine
190+
Code
191+
cat(cwrapper)
192+
Output
193+
#define R_NO_REMAP
194+
#include <R.h>
195+
#include <Rinternals.h>
196+
197+
198+
extern void fn(
199+
const int* const a__,
200+
const int* const b__,
201+
int* const out___);
202+
203+
SEXP fn_(SEXP _args) {
204+
// a
205+
_args = CDR(_args);
206+
SEXP a = CAR(_args);
207+
if (TYPEOF(a) != INTSXP) {
208+
Rf_error("typeof(a) must be 'integer', not '%s'", Rf_type2char(TYPEOF(a)));
209+
}
210+
const int* const a__ = INTEGER(a);
211+
const R_xlen_t a__len_ = Rf_xlength(a);
212+
213+
// b
214+
_args = CDR(_args);
215+
SEXP b = CAR(_args);
216+
if (TYPEOF(b) != INTSXP) {
217+
Rf_error("typeof(b) must be 'integer', not '%s'", Rf_type2char(TYPEOF(b)));
218+
}
219+
const int* const b__ = INTEGER(b);
220+
const R_xlen_t b__len_ = Rf_xlength(b);
221+
222+
if (a__len_ != 1)
223+
Rf_error("length(a) must be 1, not %0.f",
224+
(double)a__len_);
225+
if (b__len_ != 1)
226+
Rf_error("length(b) must be 1, not %0.f",
227+
(double)b__len_);
228+
const R_xlen_t out___len_ = (1);
229+
SEXP out_ = PROTECT(Rf_allocVector(INTSXP, out___len_));
230+
int* out___ = INTEGER(out_);
231+
232+
fn(a__, b__, out___);
233+
234+
UNPROTECT(1);
235+
return out_;
236+
}
237+

tests/testthat/test-div-mod.R

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,22 @@ test_that("%% and %/%", {
3636
}
3737
expect_quick_identical(div_int, x)
3838
})
39+
40+
test_that("integer %/% is exact beyond 2^24", {
41+
div_int_big <- function(a, b) {
42+
declare(type(a = integer(1)), type(b = integer(1)))
43+
a %/% b
44+
}
45+
46+
# locks the kind=c_double casts: kind-less real() is single precision,
47+
# which cannot represent odd integers above 2^24, so e.g.
48+
# 16777219L %/% 2L came back as 8388610 instead of 8388609
49+
expect_translation_snapshots(div_int_big)
50+
51+
expect_quick_identical(
52+
div_int_big,
53+
list(16777219L, 2L),
54+
list(-16777219L, 2L),
55+
list(.Machine$integer.max, 7L)
56+
)
57+
})

0 commit comments

Comments
 (0)