Skip to content

Commit

Permalink
FEAT: reimplemented mod and modulo in C, // is now op! for `m…
Browse files Browse the repository at this point in the history
…odulo` and `%` is now `op!` for `remainder`

resolves: Oldes/Rebol-issues#1332
related to: Oldes/Rebol-issues#2311
  • Loading branch information
Oldes committed May 4, 2021
1 parent 72a7b98 commit 933514d
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 36 deletions.
3 changes: 2 additions & 1 deletion src/boot/ops.reb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ REBOL [
- subtract
* multiply
/ divide
// remainder
// modulo
% remainder
** power
= equal?
=? same?
Expand Down
71 changes: 71 additions & 0 deletions src/core/n-math.c
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,77 @@ enum {SINE, COSINE, TANGENT};
}


/***********************************************************************
**
*/ void modulus(REBVAL *ret, REBVAL *val1, REBVAL *val2, REBOOL round)
/*
** Based on: https://stackoverflow.com/a/66777048/494472
**
***********************************************************************/
{
double a, aa, b, m;
if (IS_INTEGER(val1) && IS_INTEGER(val2)) {
REBI64 ia = VAL_INT64(val1);
REBI64 ib = VAL_INT64(val2);
if (ib == 0) Trap0(RE_ZERO_DIVIDE);
SET_INTEGER(ret, ((ia % ib) + ib) % ib);
return;
}

a = Number_To_Dec(val1);
b = Number_To_Dec(val2);

if (b == 0.0) Trap0(RE_ZERO_DIVIDE);

if (round && b < 0.0) b = fabs(b);

m = fmod(fmod(a, b) + b, b);

if (round && (almost_equal(a, a - m, 10) || almost_equal(b, b + m, 10))) {
m = 0.0;
}
switch (VAL_TYPE(val1)) {
case REB_DECIMAL:
case REB_PERCENT: SET_DECIMAL(ret, m); break;
case REB_INTEGER: SET_INTEGER(ret, (REBI64)m); break;
case REB_TIME: VAL_TIME(ret) = DEC_TO_SECS(m); break;
case REB_MONEY: VAL_DECI(ret) = decimal_to_deci(m); break;
case REB_CHAR: SET_CHAR(ret, (REBINT)m); break;
}
SET_TYPE(ret, VAL_TYPE(val1));
}

/***********************************************************************
**
*/ REBNATIVE(mod)
/*
// mod: native [
// {Compute a nonnegative remainder of A divided by B.}
// a [number! money! char! time!]
// b [number! money! char! time!] "Must be nonzero."
// ]
***********************************************************************/
{
modulus(D_RET, D_ARG(1), D_ARG(2), FALSE);
return R_RET;
}

/***********************************************************************
**
*/ REBNATIVE(modulo)
/*
// modulo: native [
// {Wrapper for MOD that handles errors like REMAINDER. Negligible values (compared to A and B) are rounded to zero.}
// a [number! money! char! time!]
// b [number! money! char! time!] "Absolute value will be used."
// ]
***********************************************************************/
{
modulus(D_RET, D_ARG(1), D_ARG(2), TRUE);
return R_RET;
}


/***********************************************************************
**
*/ REBNATIVE(log_10)
Expand Down
18 changes: 18 additions & 0 deletions src/core/t-decimal.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,24 @@ REBOOL almost_equal(REBDEC a, REBDEC b, REBCNT max_diff) {
VAL_INT64(dec) = n; // aliasing the bits!
}

/***********************************************************************
**
*/ REBDEC Number_To_Dec(REBVAL* val)
/*
***********************************************************************/
{
REBDEC d = NAN;
switch (VAL_TYPE(val)) {
case REB_DECIMAL:
case REB_PERCENT: d = VAL_DECIMAL(val); break;
case REB_INTEGER: d = (REBDEC)VAL_INT64(val); break;
case REB_MONEY: d = deci_to_decimal(VAL_DECI(val)); break;
case REB_CHAR: d = (REBDEC)VAL_CHAR(val); break;
case REB_TIME: d = VAL_TIME(val) * NANO;
}
return d;
}


/***********************************************************************
**
Expand Down
73 changes: 38 additions & 35 deletions src/mezz/mezz-math.reb
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,45 @@ REBOL [
}
]

mod: func [
"Compute a nonnegative remainder of A divided by B."
; In fact the function tries to find the remainder,
; that is "almost non-negative"
; Example: 0.15 - 0.05 - 0.1 // 0.1 is negative,
; but it is "almost" zero, i.e. "almost non-negative"
[catch]
a [number! money! time!]
b [number! money! time!] "Must be nonzero."
/local r
] [
; Compute the smallest non-negative remainder
all [negative? r: a // b r: r + b]
; Use abs a for comparisons
a: abs a
; If r is "almost" b (i.e. negligible compared to b), the
; result will be r - b. Otherwise the result will be r
either all [a + r = (a + b) positive? r + r - b] [r - b] [r]
]
;- mod and modulo are now implemented as natives
;
;mod: func [
; "Compute a nonnegative remainder of A divided by B."
; ; In fact the function tries to find the remainder,
; ; that is "almost non-negative"
; ; Example: 0.15 - 0.05 - 0.1 % 0.1 is negative,
; ; but it is "almost" zero, i.e. "almost non-negative"
; [catch]
; a [number! money! time!]
; b [number! money! time!] "Must be nonzero."
; /local r
;] [
; ; Compute the smallest non-negative remainder
; all [negative? r: a % b r: r + b]
; ; Use abs a for comparisons
; a: abs a
; ; If r is "almost" b (i.e. negligible compared to b), the
; ; result will be r - b. Otherwise the result will be r
; either all [a + r = (a + b) positive? r + r - b] [r - b] [r]
;]
;
;modulo: func [
; {Wrapper for MOD that handles errors like REMAINDER. Negligible values (compared to A and B) are rounded to zero.}
; ;[catch]
; a [number! money! time!]
; b [number! money! time!] "Absolute value will be used"
; /local r
;] [
; ; Coerce B to a type compatible with A
; any [number? a b: make a b]
; ; Get the "accurate" MOD value
; r: mod a abs b
; ; If the MOD result is "near zero", w.r.t. A and B,
; ; return 0--the "expected" result, in human terms.
; ; Otherwise, return the result we got from MOD.
; either any [a - r = a r + b = b] [make r 0] [r]
;]

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

sign?: func [
"Returns sign of number as 1, 0, or -1 (to use as multiplier)."
Expand Down
45 changes: 45 additions & 0 deletions src/tests/units/decimal-test.r3
Original file line number Diff line number Diff line change
Expand Up @@ -275,5 +275,50 @@ Rebol [

===end-group===

===start-group=== "modulo / remainder"
;@@ https://github.com/Oldes/Rebol-issues/issues/1332
;@@ https://github.com/Oldes/Rebol-issues/issues/2311
;@@ https://github.com/metaeducation/ren-c/issues/843
;@@ https://github.com/red/red/issues/1515
--test-- "remainder"
b: copy [] for i -7 7 1 [append b i % 3] b
--assert b = [-1 0 -2 -1 0 -2 -1 0 1 2 0 1 2 0 1]
b: copy [] for i -7 7 1 [append b i % -3] b
--assert b = [-1 0 -2 -1 0 -2 -1 0 1 2 0 1 2 0 1]
--assert all [error? e: try [7 % 0] e/id = 'zero-divide]
--assert 1.222090944E+33 % -2147483648.0 = 0.0
--test-- "mod"
b: copy [] for i -7 7 1 [append b mod i 3] b
--assert b = [2 0 1 2 0 1 2 0 1 2 0 1 2 0 1]
b: copy [] for i -7 7 1 [append b mod i -3] b
--assert b = [-1 0 -2 -1 0 -2 -1 0 -2 -1 0 -2 -1 0 -2]
--assert all [error? e: try [mod 7 0] e/id = 'zero-divide]
--assert 0.25 = mod 562949953421311.25 1
--assert 5.55111512312578e-17 = mod 0.1 + 0.1 + 0.1 0.3
--assert -3 == mod -8 -5
--assert -3.0 == mod -8.0 -5

--test-- "modulo"
b: copy [] for i -7 7 1 [append b i // 3] b
--assert b = [2 0 1 2 0 1 2 0 1 2 0 1 2 0 1]
b: copy [] for i -7 7 1 [append b i // -3] b
--assert b = [-1 0 -2 -1 0 -2 -1 0 -2 -1 0 -2 -1 0 -2]
--assert 0.0 = (1.222090944E+33 // -2147483648.0)
--assert 0.0 = modulo 562949953421311.25 1
--assert 0.0 = modulo 0.1 + 0.1 + 0.1 0.3
--assert $0 == modulo $0.1 + $0.1 + $0.1 $0.3
--assert $0 == modulo $0.3 $0.1 + $0.1 + $0.1
--assert 0 == modulo 1 0.1
--assert -3 // 2 == 1
--assert 3 // -2 == -1
--assert 1000 // #"a" == 30
--assert #"a" // 3 == #"^A"
--assert 10:0 // 3:0 == 1:0
--assert 10% // 3% == 1%
--assert 10 // 3% == 0 ; because result A was integer, result is also integer!
--assert 0.01 = round/to (10.0 // 3%) 0.00001

===end-group===


~~~end-file~~~

0 comments on commit 933514d

Please sign in to comment.