JavaScript% (modulo) daje wynik ujemny dla liczb ujemnych

252

Według kalkulatora Google (-13) % 64 jest 51.

Według Javascript (zobacz JSBin ) jest -13.

Jak to naprawić?

Wąwóz Alec
źródło
To może być tylko kwestia pierwszeństwa. Masz na myśli (-13) % 64czy -(13 % 64)? Osobiście wstawiłbym pareny w obu kierunkach, dla większej przejrzystości.
MatrixFrog,
2
w zasadzie duplikat: W jaki sposób Java wykonuje obliczenia modułu z liczbami ujemnymi? nawet jeśli jest to pytanie javascript.
Prezydent James K. Polk,
85
JavaScript czasami wydaje się bardzo okrutnym żartem
dukeofgaming
6
Google nie może się mylić
Caub
10
Podstawowym problemem w JS %nie jest operator modulo. To pozostały operator. W JavaScript nie ma operatora modulo. Tak więc zaakceptowana odpowiedź jest właściwą drogą.
Redu

Odpowiedzi:

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

Zaczerpnięte z tego artykułu: Błąd JavaScript Modulo

Enrique
źródło
23
Nie wiem, czy nazwałbym to „błędem”. Operacja modulo nie jest zbyt dobrze zdefiniowana w porównaniu z liczbami ujemnymi, a różne środowiska komputerowe traktują ją inaczej. Artykuł Wikipedii na temat działania modulo całkiem dobrze to opisuje.
Daniel Pryden,
22
Może wydawać się głupi, ponieważ często nazywa się go „modulo”, co sugeruje, że zachowałby się tak samo, jak jego definicja matematyczna (zob. Algebra ℤ / nℤ), co nie.
etienne
7
Dlaczego warto wziąć modulo przed dodaniem n? Dlaczego po prostu nie dodać n, a następnie wziąć moduł?
głodował
12
@starwed, jeśli nie użyjesz tego% n, to się nie powiedzie x < -n- np . (-7 + 5) % 5 === -2ale ((-7 % 5) + 5) % 5 == 3.
fadedbee
7
Polecam dodać do odpowiedzi, że aby uzyskać dostęp do tej funkcji, należy użyć formatu (-13) .mod (10) zamiast -13% 10. Byłoby to bardziej jasne.
Jp_
161

Używanie Number.prototypejest POWOLNE, ponieważ za każdym razem, gdy używasz metody prototypowej, twój numer jest zawijany w Object. Zamiast tego:

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

Posługiwać się:

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

Zobacz: http://jsperf.com/negative-modulo/2

~ 97% szybciej niż przy użyciu prototypu. Jeśli wydajność jest dla Ciebie ważna, oczywiście ...

StuR
źródło
1
Świetna wskazówka. Wziąłem twój jsperf i porównałem z pozostałymi rozwiązaniami w tym pytaniu (ale wydaje się, że i tak jest najlepsze): jsperf.com/negative-modulo/3
Mariano Desanze
11
Mikrooptymalizacja. Aby to zmienić, musisz wykonać ogromną liczbę modyfikacji modów. Koduj, co jest najczystsze i najbardziej łatwe w utrzymaniu, a następnie zoptymalizuj następującą analizę wydajności.
ChrisV
Myślę, że masz swoje nS i mS dookoła niewłaściwy sposób w swoim drugim przykładzie @StuR. Powinno być return ((n % m) + m) % m;.
vimist
Powinien to być komentarz do zaakceptowanej odpowiedzi, a nie odpowiedź sama w sobie.
xehpuk
5
Motywacja podana w tej odpowiedzi to mikrooptymalizacja, tak, ale modyfikacja prototypu jest problematyczna. Preferuj podejście z najmniejszymi skutkami ubocznymi, którym jest ten.
Keen
30

%Operator JavaScript jest operatorem pozostała nie operator modulo (główna różnica polega na tym, w jaki sposób ujemne numery obróbce):

-1 % 8 // -1, not 7

Rob Sobers
źródło
8
To powinien być nazywany operatorem pozostała jednak jest nazywany operatorem modulo: developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/...
Big McLargeHuge
15
@DaveKennedy: MDN nie jest oficjalnym odniesieniem do języka, jest to strona edytowana przez społeczność, która czasem się myli. Specyfikacja nie nazywa go operatorem modulo i, o ile mogę stwierdzić, nigdy nie miała (wróciłem do ES3). Wyraźnie mówi, że operator podaje pozostałą część domniemanego podziału, i po prostu nazywa to „operatorem%”.
TJ Crowder
2
Jeśli jest wywoływany remainder, z definicji musi być większy niż 0. Nie pamiętasz twierdzenia o podziale z liceum ?! Więc może zajrzyj tutaj: en.wikipedia.org/wiki/Euclidean_division
Ahmad
19

Funkcja „mod”, która zwraca wynik dodatni.

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

I oczywiście

mod(-13,64) // 51
Shanimal
źródło
1
MDN nie jest oficjalnym odniesieniem do języka, jest to strona edytowana przez społeczność, która czasem się myli. Specyfikacja nie nazywa go operatorem modulo i, o ile mogę stwierdzić, nigdy nie miała (wróciłem do ES3). Wyraźnie mówi, że operator podaje pozostałą część dorozumianego podziału i po prostu nazywa to „operatorem%”.
TJ Crowder
1
Ups, podany link faktycznie odwołuje się #sec-applying-the-mod-operatordo adresu URL :) W każdym razie, dzięki za notatkę, usunąłem puch z mojej odpowiedzi, to i tak nie jest tak naprawdę ważne.
Shanimal,
3
@ Shanimal: LOL! To robi. Błąd edytora HTML. Tekst specyfikacji nie.
TJ Crowder,
10

Przyjęta odpowiedź trochę mnie denerwuje, ponieważ ponownie używa operatora%. Co się stanie, jeśli JavaScript zmieni zachowanie w przyszłości?

Oto obejście, które nie wykorzystuje ponownie%:

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
źródło
8
Jeśli javascript zmieni operator modulo, aby pasował do definicji matematycznej, zaakceptowana odpowiedź nadal będzie działać.
głodował
20
„Co, jeśli JavaScript zmieni zachowanie w przyszłości?” - Dlaczego miałby to zrobić? Zmiana zachowania takiego podstawowego operatora jest mało prawdopodobna.
nnnnnn
1
+1 za podzielenie się tą troską i alternatywą wobec przedstawionej odpowiedzi # answer-4467559 i z 4 powodów: (1) Dlaczego stwierdza, i tak „Zmiana zachowania tak fundamentalnej operacji jest mało prawdopodobna”, ale nadal rozważnie rozważa nawet aby znaleźć to nie jest potrzebne. (2) zdefiniowanie działającego op w kategoriach zepsutego, choć imponujące, jest niepokojące przynajmniej na pierwszy rzut oka, nie powinno być pokazane, dopóki (3) nie dobrze sprawdziłem tę alternatywę, uważam, że łatwiej jest naśladować Szybkie spojrzenie. (4) malutki: używa 1 div + 1 mul zamiast 2 (mod) div i słyszałem na DUŻO wcześniejszym sprzęcie bez dobrego FPU, mnożenie było szybsze.
Architekt Destiny
2
@DestinyArchitect nie jest ostrożny, nie ma sensu. Jeśli mieliby zmienić zachowanie pozostałego operatora, zepsułoby to dobry zakres programów, które go wykorzystują. To się nigdy nie wydarzy.
Aegis
9
Co zrobić, jeśli zachowanie -, *, /, ;, ., (, ), ,, Math.floor, functionlub returnzmiany? Twój kod jest strasznie zepsuty.
xehpuk
5

Chociaż nie zachowuje się tak, jak się spodziewałeś, nie oznacza to, że JavaScript nie zachowuje się. Jest to JavaScript wybierany do obliczeń modulo. Ponieważ z definicji każda odpowiedź ma sens.

Zobacz to z Wikipedii. Po prawej stronie widać, jak różne języki wybrały znak wyniku.

dheerozaur
źródło
3

Jeśli xjest liczbą całkowitą i npotęgą 2, możesz użyć x & (n - 1)zamiast x % n.

> -13 & (64 - 1)
51 
quasimodo
źródło
2

Wygląda więc na to, że jeśli próbujesz modować wokół stopni (tak, że jeśli masz -50 stopni - 200 stopni), powinieneś użyć czegoś takiego:

function modrad(m) {
    return ((((180+m) % 360) + 360) % 360)-180;
}
JayCrossler
źródło
1

Mam też do czynienia z negatywnym i ujemnym n

 //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
źródło
1

To nie jest błąd, istnieją 3 funkcje do obliczania modulo, możesz użyć tej, która odpowiada Twoim potrzebom (polecam użyć funkcji euklidesowej)

Obcinanie funkcji części dziesiętnej

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

Funkcja części całkowitych

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

Funkcja euklidesowa

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
źródło
1
W funkcji euklidesowej sprawdzanie m <0 jest bezużyteczne, ponieważ ((ten% n) + n)% n jest zawsze dodatnie
bormat
@ormat Tak, tak, ale w JavaScript %może zwracać negatywne wyniki (jest to celem tych funkcji, aby to naprawić)
zessx
napisałeś ten [kod] Number.prototype.mod = funkcja (n) {var m = ((ten% n) + n)% n; zwrócić m <0? m + Math.abs (n): m; }; [/ code] daj mi jedną wartość n, gdzie m jest ujemne. nie mają wartości n, gdzie m jest ujemne, ponieważ dodajesz n po pierwszym%.
bormat
Bez tej kontroli parseInt(-41).mod(-7)wróciłbym -6zamiast 1(i to jest dokładnie cel funkcji liczb całkowitych, którą napisałem)
zessx,
ha ok, masz rację, wszystkie moje przeprosiny, zapomniałem negatywnego modulo, myślałem tylko o „tym” negatywnym. Czy mogę zasugerować przeniesienie Math.abs Number.prototype.mod = function (n) {return ((this% n) + Math.abs (n))% n; }; (-41) .mod (-7) == 1 // nie trzeba
analizować parsetu
0

Istnieje pakiet NPM, który wykona pracę za Ciebie. Możesz zainstalować go za pomocą następującego polecenia.

npm install just-modulo --save

Użycie skopiowane z 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

Repozytorium GitHub można znaleźć pod następującym linkiem:

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

maartenpaauw
źródło