Skip to content

Commit 933514d

Browse files
committed
FEAT: reimplemented mod and modulo in C, // is now op! for modulo and % is now op! for remainder
resolves: Oldes/Rebol-issues#1332 related to: Oldes/Rebol-issues#2311
1 parent 72a7b98 commit 933514d

File tree

5 files changed

+174
-36
lines changed

5 files changed

+174
-36
lines changed

src/boot/ops.reb

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ REBOL [
1818
- subtract
1919
* multiply
2020
/ divide
21-
// remainder
21+
// modulo
22+
% remainder
2223
** power
2324
= equal?
2425
=? same?

src/core/n-math.c

+71
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,77 @@ enum {SINE, COSINE, TANGENT};
359359
}
360360

361361

362+
/***********************************************************************
363+
**
364+
*/ void modulus(REBVAL *ret, REBVAL *val1, REBVAL *val2, REBOOL round)
365+
/*
366+
** Based on: https://stackoverflow.com/a/66777048/494472
367+
**
368+
***********************************************************************/
369+
{
370+
double a, aa, b, m;
371+
if (IS_INTEGER(val1) && IS_INTEGER(val2)) {
372+
REBI64 ia = VAL_INT64(val1);
373+
REBI64 ib = VAL_INT64(val2);
374+
if (ib == 0) Trap0(RE_ZERO_DIVIDE);
375+
SET_INTEGER(ret, ((ia % ib) + ib) % ib);
376+
return;
377+
}
378+
379+
a = Number_To_Dec(val1);
380+
b = Number_To_Dec(val2);
381+
382+
if (b == 0.0) Trap0(RE_ZERO_DIVIDE);
383+
384+
if (round && b < 0.0) b = fabs(b);
385+
386+
m = fmod(fmod(a, b) + b, b);
387+
388+
if (round && (almost_equal(a, a - m, 10) || almost_equal(b, b + m, 10))) {
389+
m = 0.0;
390+
}
391+
switch (VAL_TYPE(val1)) {
392+
case REB_DECIMAL:
393+
case REB_PERCENT: SET_DECIMAL(ret, m); break;
394+
case REB_INTEGER: SET_INTEGER(ret, (REBI64)m); break;
395+
case REB_TIME: VAL_TIME(ret) = DEC_TO_SECS(m); break;
396+
case REB_MONEY: VAL_DECI(ret) = decimal_to_deci(m); break;
397+
case REB_CHAR: SET_CHAR(ret, (REBINT)m); break;
398+
}
399+
SET_TYPE(ret, VAL_TYPE(val1));
400+
}
401+
402+
/***********************************************************************
403+
**
404+
*/ REBNATIVE(mod)
405+
/*
406+
// mod: native [
407+
// {Compute a nonnegative remainder of A divided by B.}
408+
// a [number! money! char! time!]
409+
// b [number! money! char! time!] "Must be nonzero."
410+
// ]
411+
***********************************************************************/
412+
{
413+
modulus(D_RET, D_ARG(1), D_ARG(2), FALSE);
414+
return R_RET;
415+
}
416+
417+
/***********************************************************************
418+
**
419+
*/ REBNATIVE(modulo)
420+
/*
421+
// modulo: native [
422+
// {Wrapper for MOD that handles errors like REMAINDER. Negligible values (compared to A and B) are rounded to zero.}
423+
// a [number! money! char! time!]
424+
// b [number! money! char! time!] "Absolute value will be used."
425+
// ]
426+
***********************************************************************/
427+
{
428+
modulus(D_RET, D_ARG(1), D_ARG(2), TRUE);
429+
return R_RET;
430+
}
431+
432+
362433
/***********************************************************************
363434
**
364435
*/ REBNATIVE(log_10)

src/core/t-decimal.c

+18
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,24 @@ REBOOL almost_equal(REBDEC a, REBDEC b, REBCNT max_diff) {
214214
VAL_INT64(dec) = n; // aliasing the bits!
215215
}
216216

217+
/***********************************************************************
218+
**
219+
*/ REBDEC Number_To_Dec(REBVAL* val)
220+
/*
221+
***********************************************************************/
222+
{
223+
REBDEC d = NAN;
224+
switch (VAL_TYPE(val)) {
225+
case REB_DECIMAL:
226+
case REB_PERCENT: d = VAL_DECIMAL(val); break;
227+
case REB_INTEGER: d = (REBDEC)VAL_INT64(val); break;
228+
case REB_MONEY: d = deci_to_decimal(VAL_DECI(val)); break;
229+
case REB_CHAR: d = (REBDEC)VAL_CHAR(val); break;
230+
case REB_TIME: d = VAL_TIME(val) * NANO;
231+
}
232+
return d;
233+
}
234+
217235

218236
/***********************************************************************
219237
**

src/mezz/mezz-math.reb

+38-35
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,45 @@ REBOL [
1111
}
1212
]
1313

14-
mod: func [
15-
"Compute a nonnegative remainder of A divided by B."
16-
; In fact the function tries to find the remainder,
17-
; that is "almost non-negative"
18-
; Example: 0.15 - 0.05 - 0.1 // 0.1 is negative,
19-
; but it is "almost" zero, i.e. "almost non-negative"
20-
[catch]
21-
a [number! money! time!]
22-
b [number! money! time!] "Must be nonzero."
23-
/local r
24-
] [
25-
; Compute the smallest non-negative remainder
26-
all [negative? r: a // b r: r + b]
27-
; Use abs a for comparisons
28-
a: abs a
29-
; If r is "almost" b (i.e. negligible compared to b), the
30-
; result will be r - b. Otherwise the result will be r
31-
either all [a + r = (a + b) positive? r + r - b] [r - b] [r]
32-
]
14+
;- mod and modulo are now implemented as natives
15+
;
16+
;mod: func [
17+
; "Compute a nonnegative remainder of A divided by B."
18+
; ; In fact the function tries to find the remainder,
19+
; ; that is "almost non-negative"
20+
; ; Example: 0.15 - 0.05 - 0.1 % 0.1 is negative,
21+
; ; but it is "almost" zero, i.e. "almost non-negative"
22+
; [catch]
23+
; a [number! money! time!]
24+
; b [number! money! time!] "Must be nonzero."
25+
; /local r
26+
;] [
27+
; ; Compute the smallest non-negative remainder
28+
; all [negative? r: a % b r: r + b]
29+
; ; Use abs a for comparisons
30+
; a: abs a
31+
; ; If r is "almost" b (i.e. negligible compared to b), the
32+
; ; result will be r - b. Otherwise the result will be r
33+
; either all [a + r = (a + b) positive? r + r - b] [r - b] [r]
34+
;]
35+
;
36+
;modulo: func [
37+
; {Wrapper for MOD that handles errors like REMAINDER. Negligible values (compared to A and B) are rounded to zero.}
38+
; ;[catch]
39+
; a [number! money! time!]
40+
; b [number! money! time!] "Absolute value will be used"
41+
; /local r
42+
;] [
43+
; ; Coerce B to a type compatible with A
44+
; any [number? a b: make a b]
45+
; ; Get the "accurate" MOD value
46+
; r: mod a abs b
47+
; ; If the MOD result is "near zero", w.r.t. A and B,
48+
; ; return 0--the "expected" result, in human terms.
49+
; ; Otherwise, return the result we got from MOD.
50+
; either any [a - r = a r + b = b] [make r 0] [r]
51+
;]
3352

34-
modulo: func [
35-
{Wrapper for MOD that handles errors like REMAINDER. Negligible values (compared to A and B) are rounded to zero.}
36-
;[catch]
37-
a [number! money! time!]
38-
b [number! money! time!] "Absolute value will be used"
39-
/local r
40-
] [
41-
; Coerce B to a type compatible with A
42-
any [number? a b: make a b]
43-
; Get the "accurate" MOD value
44-
r: mod a abs b
45-
; If the MOD result is "near zero", w.r.t. A and B,
46-
; return 0--the "expected" result, in human terms.
47-
; Otherwise, return the result we got from MOD.
48-
either any [a - r = a r + b = b] [make r 0] [r]
49-
]
5053

5154
sign?: func [
5255
"Returns sign of number as 1, 0, or -1 (to use as multiplier)."

src/tests/units/decimal-test.r3

+45
Original file line numberDiff line numberDiff line change
@@ -275,5 +275,50 @@ Rebol [
275275

276276
===end-group===
277277

278+
===start-group=== "modulo / remainder"
279+
;@@ https://github.com/Oldes/Rebol-issues/issues/1332
280+
;@@ https://github.com/Oldes/Rebol-issues/issues/2311
281+
;@@ https://github.com/metaeducation/ren-c/issues/843
282+
;@@ https://github.com/red/red/issues/1515
283+
--test-- "remainder"
284+
b: copy [] for i -7 7 1 [append b i % 3] b
285+
--assert b = [-1 0 -2 -1 0 -2 -1 0 1 2 0 1 2 0 1]
286+
b: copy [] for i -7 7 1 [append b i % -3] b
287+
--assert b = [-1 0 -2 -1 0 -2 -1 0 1 2 0 1 2 0 1]
288+
--assert all [error? e: try [7 % 0] e/id = 'zero-divide]
289+
--assert 1.222090944E+33 % -2147483648.0 = 0.0
290+
--test-- "mod"
291+
b: copy [] for i -7 7 1 [append b mod i 3] b
292+
--assert b = [2 0 1 2 0 1 2 0 1 2 0 1 2 0 1]
293+
b: copy [] for i -7 7 1 [append b mod i -3] b
294+
--assert b = [-1 0 -2 -1 0 -2 -1 0 -2 -1 0 -2 -1 0 -2]
295+
--assert all [error? e: try [mod 7 0] e/id = 'zero-divide]
296+
--assert 0.25 = mod 562949953421311.25 1
297+
--assert 5.55111512312578e-17 = mod 0.1 + 0.1 + 0.1 0.3
298+
--assert -3 == mod -8 -5
299+
--assert -3.0 == mod -8.0 -5
300+
301+
--test-- "modulo"
302+
b: copy [] for i -7 7 1 [append b i // 3] b
303+
--assert b = [2 0 1 2 0 1 2 0 1 2 0 1 2 0 1]
304+
b: copy [] for i -7 7 1 [append b i // -3] b
305+
--assert b = [-1 0 -2 -1 0 -2 -1 0 -2 -1 0 -2 -1 0 -2]
306+
--assert 0.0 = (1.222090944E+33 // -2147483648.0)
307+
--assert 0.0 = modulo 562949953421311.25 1
308+
--assert 0.0 = modulo 0.1 + 0.1 + 0.1 0.3
309+
--assert $0 == modulo $0.1 + $0.1 + $0.1 $0.3
310+
--assert $0 == modulo $0.3 $0.1 + $0.1 + $0.1
311+
--assert 0 == modulo 1 0.1
312+
--assert -3 // 2 == 1
313+
--assert 3 // -2 == -1
314+
--assert 1000 // #"a" == 30
315+
--assert #"a" // 3 == #"^A"
316+
--assert 10:0 // 3:0 == 1:0
317+
--assert 10% // 3% == 1%
318+
--assert 10 // 3% == 0 ; because result A was integer, result is also integer!
319+
--assert 0.01 = round/to (10.0 // 3%) 0.00001
320+
321+
===end-group===
322+
278323

279324
~~~end-file~~~

0 commit comments

Comments
 (0)