Search code examples
javascriptschememodulor5rslanguage-implementation

How modulo and remainder should work in Scheme?


I'm reading R5RS spec and it shows this:

(modulo 13 4)                   ===>  1
(remainder 13 4)                ===>  1

(modulo -13 4)                  ===>  3
(remainder -13 4)               ===>  -1

(modulo 13 -4)                  ===>  -3
(remainder 13 -4)               ===>  1

(modulo -13 -4)                 ===>  -1
(remainder -13 -4)              ===>  -1

(remainder -13 -4.0)            ===>  -1.0  ; inexact

Is this correct? I thought that modulo and remainder differ only in minus sign. And here it shows that (modulo -13 4) should return 3, in JavaScript it returns 1.

What are proper algorithms to calculate modulo and remainder? I need this for my Scheme in JavaScript implementation.

I've found this code at quora.

function modulo(num1, num2) {
  if (num2 === 0 || isNaN(num1) || isNaN(num2)) {
    return NaN;
  }

  var isPositive = num1 >= 0;

  num1 = Math.abs(num1);
  num2 = Math.abs(num2);

  while (num1 >= num2) {
    num1 = num1 - num2;
  }

  return isPositive ? num1 : -num1;
}

but it don't work like in R5RS spec, it returns -1 for modulo(-13, 4). Also I thought that JavaScript's % is the same as remainder. How to implement both functions in JavaScript or in Scheme?

My exact question is: how the algorithm for both functions should look like or how JavaScript code to calculate them both should look like?


Solution

  • If someone is interested I've asked the same question on Reddit (with link to this question) and got the answer with exact scheme code:

    https://www.reddit.com/r/scheme/comments/fpt1b8/help_with_modulo_and_reminder_in_r5rs/

    (define (modulo a b)
      (- a (* b (floor (/ a b)))))
    
    (define (remainder a b)
      (- a (* b (truncate (/ a b)))))
    
    ;; as @soegaard show reminder is just JavaScript % so this can be
    ;; if % is proper function
    (define (remainder a b)
      (% a b))
    

    it works the same with examples from R5RS:

    (list
      (= (modulo 13 4) 1)
      (= (remainder 13 4) 1)      ;; ===>  1
    
      (= (modulo -13 4) 3)        ;; ===>  3
      (= (remainder -13 4) -1)    ;; ===>  -1
    
      (= (modulo 13 -4) -3)       ;; ===>  -3
      (= (remainder 13 -4) 1)     ;; ===>  1
    
      (= (modulo -13 -4) -1)      ;; ===>  -1
      (= (remainder -13 -4) -1)   ;; ===>  -1
    
      (= (remainder -13 -4.0) -1.0)) ;; ===>  -1.0  ; inexact
    

    floor is Math.floor and truncate is:

    var truncate = (function() {
        if (Math.trunc) {
            return Math.trunc;
        } else {
            return function(x) {
                if (x === 0) {
                    return 0;
                } else if (x < 0) {
                    return Math.ceil(x);
                } else {
                    return Math.floor(x);
                }
            };
        }
    })();