JavaScript% (по модулю) дает отрицательный результат для отрицательных чисел

253

По гугл калькулятору (-13) % 64 есть 51.

Согласно Javascript (см. Этот JSBin ) это так -13.

Как это исправить?

Алек ущелье
источник
Это может быть просто проблемой приоритета. Вы имеете в виду (-13) % 64или -(13 % 64)? Лично я бы поставил паренсы в любом случае, просто для большей ясности.
MatrixFrog
2
по сути, дубликат Как Java выполняет вычисления модуля с отрицательными числами? хотя это вопрос javascript.
Президент Джеймс К. Полк
85
Javascript иногда кажется очень жестокой шуткой
dukeofgaming
6
Гугл не может ошибаться
совещание
10
Основная проблема в JS %не является оператором по модулю. Это оператор остатка. В JavaScript нет оператора по модулю. Таким образом, принятый ответ - путь.
Redu

Ответы:

263
Number.prototype.mod = function(n) {
    return ((this%n)+n)%n;
};

Взято из этой статьи: JavaScript Modulo Bug

Энрике
источник
23
Я не знаю, что я бы назвал это "ошибка". Операция по модулю не очень хорошо определена для отрицательных чисел, и разные вычислительные среды обрабатывают ее по-разному. Статья в Википедии, посвященная операции по модулю, достаточно хорошо это описывает.
Даниэль Приден
22
Это может показаться глупым, поскольку его часто называют «по модулю», предполагая, что он будет вести себя так же, как и его математическое определение (см. Алгебру ℤ / nℤ), а это не так.
Этьен
7
Зачем брать по модулю перед добавлением? Почему бы просто не добавить, а затем взять по модулю?
Звездный день
12
@starwed, если вы не используете этот% n, он потерпит неудачу x < -n- например, (-7 + 5) % 5 === -2но ((-7 % 5) + 5) % 5 == 3.
Fadedbee
7
Я рекомендую добавить к ответу, что для доступа к этой функции следует использовать формат (-13) .mod (10) вместо -13% 10. Это было бы более понятно.
января
161

Использование Number.prototypeявляется МЕДЛЕННЫМ, потому что каждый раз, когда вы используете метод-прототип, ваш номер заключен в Object. Вместо этого:

Number.prototype.mod = function(n) {
  return ((this % n) + n) % n;
}

Использование:

function mod(n, m) {
  return ((n % m) + m) % m;
}

Смотрите: http://jsperf.com/negative-modulo/2

~ 97% быстрее, чем при использовании прототипа. Если производительность важна для вас, конечно ..

Stur
источник
1
Отличный совет. Я взял ваш jsperf и сравнил с остальными решениями в этом вопросе (но в любом случае это кажется лучшим): jsperf.com/negative-modulo/3
Mariano Desanze
11
Micro-оптимизации. Вы должны были бы сделать огромное количество модовых расчетов для этого, чтобы иметь какое-либо значение. Запишите, что является наиболее понятным и наиболее обслуживаемым, а затем оптимизируйте его после анализа производительности.
ChrisV
Я думаю , что у вас есть ваши nс и mS вокруг неправильно в вашем втором примере @StuR. Так и должно быть return ((n % m) + m) % m;.
vimist
Это должен быть комментарий к принятому ответу, а не ответ сам по себе.
xehpuk
5
Мотивация, изложенная в этом ответе, - это микрооптимизация, да, но изменение прототипа проблематично. Предпочитаю подход с наименьшим количеством побочных эффектов, вот этот.
Кин
31

%Оператор в JavaScript оператор остальное, а не оператор по модулю (главное отличие заключается в том , как отрицательные числа обрабатываются):

-1 % 8 // -1, not 7

Роб Соберс
источник
8
Он должен быть назван оператор остаток , но это называется модуль оператора: developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/...
Big McLargeHuge
16
@DaveKennedy: MDN не является официальным языком, это сайт, отредактированный сообществом, который иногда ошибается. Спецификация не называет это оператором по модулю, и, насколько я могу судить, этого никогда не было (я вернулся к ES3). Он явно говорит, что оператор возвращает остаток от подразумеваемого деления, и просто называет его «оператором%».
TJ Crowder
2
Если он вызывается remainder, он должен быть больше 0 по определению. Разве вы не помните теорему о делении из средней школы ?! Поэтому, может быть, вы можете посмотреть здесь: en.wikipedia.org/wiki/Euclidean_division
Ахмад
19

Функция «мод» для возврата положительного результата.

var mod = function (n, m) {
    var remain = n % m;
    return Math.floor(remain >= 0 ? remain : remain + m);
};
mod(5,22)   // 5
mod(25,22)  // 3
mod(-1,22)  // 21
mod(-2,22)  // 20
mod(0,22)   // 0
mod(-1,22)  // 21
mod(-21,22) // 1

И конечно

mod(-13,64) // 51
Shanimal
источник
1
MDN не является официальным языком, это сайт, отредактированный сообществом, который иногда ошибается. Спецификация не называет это оператором по модулю, и, насколько я могу судить, этого никогда не было (я вернулся к ES3). Он явно говорит, что оператор возвращает остаток от подразумеваемого деления, и просто называет его «оператором%».
TJ Crowder
1
Упс, ссылка, которую вы указали, на самом деле ссылается #sec-applying-the-mod-operatorпрямо в URL :) В любом случае, спасибо за примечание, я убрал пух из своего ответа, это не очень важно в любом случае.
Shanimal
3
@ Shanimal: LOL! Оно делает. Ошибка редактора HTML. Текст спецификации не имеет.
TJ Crowder
10

Принятый ответ заставляет меня немного нервничать, потому что он снова использует оператор%. Что если Javascript изменит поведение в будущем?

Вот обходной путь, который не использует%:

function mod(a, n) {
    return a - (n * Math.floor(a/n));
}

mod(1,64); // 1
mod(63,64); // 63
mod(64,64); // 0
mod(65,64); // 1
mod(0,64); // 0
mod(-1,64); // 63
mod(-13,64); // 51
mod(-63,64); // 1
mod(-64,64); // 0
mod(-65,64); // 63
wisbucky
источник
8
Если javascript изменит оператор по модулю, чтобы он соответствовал математическому определению, принятый ответ все равно будет работать.
умер
20
«Что если Javascript изменит поведение в будущем?» - С чего бы это? Изменение поведения такого фундаментального оператора маловероятно.
nnnnnn
1
+1 за то, что поделился этой озабоченностью и альтернативой избранному ответу # answer-4467559 & по 4 причинам: (1) почему он говорит, и да «изменение поведения такой фундаментальной операции маловероятно», но все же целесообразно учитывать даже найти это не нужно. (2) определение работоспособности в терминах сломанной, хотя и впечатляющее, вызывает беспокойство, по крайней мере, на 1-м взгляде, но это должно быть пока не показано (3) хотя я не проверил эту альтернативу, я считаю более легким следовать Беглый взгляд. (4) крошечный: он использует 1 div + 1 mul вместо 2 (mod) div, и я слышал о НАМНОГО более раннем оборудовании без хорошего FPU, умножение было быстрее.
Судьба Архитектор
2
@DestinyArchitect это не разумно, это бессмысленно. Если бы они изменили поведение оператора остатка, это нарушило бы хороший диапазон программ, использующих его. Этого никогда не случится.
Aegis
10
Что делать , если поведение -, *, /, ;, ., (, ), ,, Math.floor, functionили returnизменения? Тогда ваш код ужасно сломан.
xehpuk
5

Хотя он не ведет себя так, как вы ожидали, это не значит, что JavaScript не «ведет себя». Это выбор JavaScript, сделанный для его вычисления по модулю. Потому что по определению любой ответ имеет смысл.

Смотрите это из Википедии. Справа видно, как разные языки выбрали знак результата.

dheerosaur
источник
4

Если xэто целое число и nстепень 2, вы можете использовать x & (n - 1)вместо x % n.

> -13 & (64 - 1)
51 
Фомино воскресенье
источник
2

Таким образом, кажется, что если вы пытаетесь модовать около градусов (то есть, если у вас -50 градусов - 200 градусов), вы захотите использовать что-то вроде:

function modrad(m) {
    return ((((180+m) % 360) + 360) % 360)-180;
}
JayCrossler
источник
1

Я имею дело с отрицательным а и отрицательным п тоже

 //best perf, hard to read
   function modul3(a,n){
        r = a/n | 0 ;
        if(a < 0){ 
            r += n < 0 ? 1 : -1
        }
        return a - n * r 
    }
    // shorter code
    function modul(a,n){
        return  a%n + (a < 0 && Math.abs(n)); 
    }

    //beetween perf and small code
    function modul(a,n){
        return a - n * Math[n > 0 ? 'floor' : 'ceil'](a/n); 
    }
bormat
источник
1

Это не ошибка, есть 3 функции для вычисления по модулю, вы можете использовать ту, которая соответствует вашим потребностям (я бы рекомендовал использовать евклидову функцию)

Усечение функции десятичной части

console.log(  41 %  7 ); //  6
console.log( -41 %  7 ); // -6
console.log( -41 % -7 ); // -6
console.log(  41 % -7 ); //  6

Целочисленная часть функции

Number.prototype.mod = function(n) {
    return ((this%n)+n)%n;
};

console.log( parseInt( 41).mod( 7) ); //  6
console.log( parseInt(-41).mod( 7) ); //  1
console.log( parseInt(-41).mod(-7) ); // -6
console.log( parseInt( 41).mod(-7) ); // -1

Евклидова функция

Number.prototype.mod = function(n) {
    var m = ((this%n)+n)%n;
    return m < 0 ? m + Math.abs(n) : m;
};

console.log( parseInt( 41).mod( 7) ); // 6
console.log( parseInt(-41).mod( 7) ); // 1
console.log( parseInt(-41).mod(-7) ); // 1
console.log( parseInt( 41).mod(-7) ); // 6
zessx
источник
1
В евклидовой функции проверка m <0 бесполезна, потому что ((это% n) + n)% n всегда положительно
bormat
1
@bormat Да, это так, но в Javascript %могут возвращаться отрицательные результаты (и это цель этих функций, чтобы исправить это)
zessx
Вы написали этот [код] Number.prototype.mod = function (n) {var m = ((это% n) + n)% n; вернуть m <0? m + Math.abs (n): m; }; [/ code] дайте мне одно значение n, где m отрицательно. они не имеют значения n, где m отрицательно, потому что вы добавляете n после первого%.
бормат
Без этой проверки parseInt(-41).mod(-7)вернулось бы -6вместо 1(и это точно цель функции части Integer, которую я написал)
zessx
1
Вы можете упростить свою функцию, удалив второй модуль Number.prototype.mod = function (n) {var m = this% n; возврат (m <0)? m + Math.abs (n): m; };
бормат
0

Существует пакет NPM, который сделает всю работу за вас. Вы можете установить его с помощью следующей команды.

npm install just-modulo --save

Использование скопировано из README

import modulo from 'just-modulo';

modulo(7, 5); // 2
modulo(17, 23); // 17
modulo(16.2, 3.8); // 17
modulo(5.8, 3.4); //2.4
modulo(4, 0); // 4
modulo(-7, 5); // 3
modulo(-2, 15); // 13
modulo(-5.8, 3.4); // 1
modulo(12, -1); // NaN
modulo(-3, -8); // NaN
modulo(12, 'apple'); // NaN
modulo('bee', 9); // NaN
modulo(null, undefined); // NaN

GitHub репозиторий можно найти по следующей ссылке:

https://github.com/angus-c/just/tree/master/packages/number-modulo

maartenpaauw
источник