Podział liczb całkowitych z resztą w JavaScript?

Odpowiedzi:

1239

Dla pewnej liczby yi dzielnika xoblicz iloraz ( quotient) i resztę ( remainder) jako:

var quotient = Math.floor(y/x);
var remainder = y % x;
Mark Elliot
źródło
84
% działa na zmiennoprzecinkowych w JavaScript (różni się od wielu innych języków), co być może nie jest pożądane: 3.5 % 2ocenia na 1.5. Upewnij się, że
16
Całkowitą częścią -4,5 w matematyce jest -5, ponieważ -5 jest „najwyższą możliwą liczbą całkowitą, która jest wciąż mniejsza niż -4,5”.
Toughy
9
Jednak cokolwiek zdecydujesz się zrobić z liczbami ujemnymi, powinno być spójne dla ilorazu i reszty. Używanie floori %razem nie jest spójne w ten sposób. Albo użyj trunczamiast floor(umożliwiając w ten sposób ujemne resztki), albo użyj odejmowania, aby otrzymać resztę ( rem = y - div * x).
Mark Reed,
7
1. Jeśli masz zamiar obliczyć resztę remtak, można dostać iloraz divszybciej bez podłogi: (y - rem) / x. 2. Nawiasem mówiąc, operacja modulo według zalecanej definicji Donalda Knutha (dzielnik znaków dopasowania, a nie reszta, tj. Moduł euklidesowy, ani znak dywidendy znaków JavaScript) jest tym, co możemy zakodować w JavaScript jako function mod (a, n) { return a % n + (Math.sign(a) !== Math.sign(n) ? n : 0); }.
Aaron Mansheim,
1
-9 / 2 = -4,5. Następnie zabierasz głos na poziomie -4,5, czyli -5. Pamiętaj, że -5 jest mniejsze niż -4,5, a działanie podłogi jest zdefiniowane jako największa liczba całkowita mniejsza niż podana wartość.
Mark Reed
370

Nie jestem ekspertem od operatorów bitowych, ale oto inny sposób na uzyskanie całej liczby:

var num = ~~(a / b);

Działa to również poprawnie dla liczb ujemnych, a Math.floor()zaokrągla w niewłaściwym kierunku.

To również wydaje się poprawne:

var num = (a / b) >> 0;
użytkownik113716
źródło
84
Kolejnym, którego celem, który właśnie spędziłem ostatnie 20 minut, próbując to a/b | 0
rozgryźć
18
@ user113716 @BlueRaja Operacje bitowe mają sens tylko w przypadku liczb całkowitych i JS (oczywiście) o tym wie. ~~int, int | 0I int >> 0nie zmienia Pierwszym argumentem, ale sprawiają, że integralną część interpretera przechodzi do operatora.
Aleksei Zabrodskii
15
floorprawie nie krąży w złym kierunku, biorąc pod uwagę jego nazwę - po prostu nie w kierunku, który ludzie na ogół chcą!
Mark K Cowan,
19
To jest buu buu. a = 12447132275286670000; b = 128 Math.floor(a/b)-> 97243220900677100i ~~(a/b)-> -1231452688.
Mirek Rusin
7
Uważaj z pierwszeństwem. ~~(5/2) --> 2jak to robi (5/2)>>0 --> 2, ale ~~(5/2) + 1 --> 3jednocześnie ~~(5/2)>>0 + 1 --> 1. ~~jest dobrym wyborem, ponieważ pierwszeństwo jest bardziej odpowiednie.
timkay
217

Zrobiłem kilka testów prędkości w Firefoksie.

-100/3             // -33.33..., 0.3663 millisec
Math.floor(-100/3) // -34,       0.5016 millisec
~~(-100/3)         // -33,       0.3619 millisec
(-100/3>>0)        // -33,       0.3632 millisec
(-100/3|0)         // -33,       0.3856 millisec
(-100-(-100%3))/3  // -33,       0.3591 millisec

/* a=-100, b=3 */
a/b                // -33.33..., 0.4863 millisec
Math.floor(a/b)    // -34,       0.6019 millisec
~~(a/b)            // -33,       0.5148 millisec
(a/b>>0)           // -33,       0.5048 millisec
(a/b|0)            // -33,       0.5078 millisec
(a-(a%b))/b        // -33,       0.6649 millisec

Powyższe opiera się na 10 milionach prób dla każdego.

Wniosek: Użyj (a/b>>0)(lub (~~(a/b))lub (a/b|0)), aby osiągnąć około 20% przyrost wydajności. Należy także pamiętać, że są one niezgodne z Math.floorkiedy a/b<0 && a%b!=0.

KalEl
źródło
54
Pamiętaj, że optymalizacja podziału liczb całkowitych pod kątem prędkości ma sens tylko wtedy, gdy robisz to dużo . W każdym innym przypadku poleciłbym wybór najprostszego (którykolwiek wydaje się najprostszy dla ciebie i twoich współpracowników).
mik01aj
7
@ m01 całkowicie się zgadzam - zbyt duży nacisk kładziony jest na takie rzeczy online
JonnyRaa
2
@ m01 Ale co jest trudniejsze: poznanie Math.floorwielu innych funkcji API i kto wie, ile funkcji API, lub poznanie ~operatora (bitowego-nie) i tego, jak działają operacje bitowe w JS, a następnie zrozumienie efektu podwójnej tyldy?
Stijn de Witt
11
Cóż, jeśli twoi współpracownicy nie programują chipów w asemblerze, prawdopodobnie Math.floorlepiej to zrozumieją . A nawet jeśli nie, ten jest dostępny w Google.
mik01aj
2
@MarkGreen tak, ale sama chęć zrobienia tego nie oznacza, że ​​powinieneś napisać go w dziwnej formie tylko dlatego, że zdarza się to najszybciej, bez wyraźnego powodu - przejrzystość kodu powinna być zazwyczaj Twoim pierwszym problemem. Te testy mogą być zupełnie bez znaczenia po zmianie języka i w różnych przeglądarkach - musisz się profilować, aby dowiedzieć się, co jest wolne w Twojej aplikacji, niezależnie od tego. Jest mało prawdopodobne, aby była to metoda dzielenia liczb całkowitych, chyba że zoptymalizowałeś jeszcze jedną rzecz!
JonnyRaa
150

ES6 wprowadza nową Math.truncmetodę. Pozwala to naprawić odpowiedź @ MarkElliot, aby działała również dla liczb ujemnych:

var div = Math.trunc(y/x);
var rem = y % x;

Zauważ, że Mathmetody mają przewagę nad operatorami bitowymi, ponieważ pracują z liczbami powyżej 2 31 .

Oriol
źródło
var y = 18014398509481984; var x = 5; div =? - błąd?
4esn0k
4
@ 4esn0k To nie jest błąd. Twój numer ma zbyt wiele cyfr, nie możesz mieć tak dużej precyzji w 64-bitowym formacie binarnym IEEE 754. Na przykład 18014398509481984 == 18014398509481985.
Oriol
18014398509481984 == 2 ** 54, i specjalnie użyłem tego numeru, ponieważ jest on reprezentowany dokładnie w formacie binarnym64. i odpowiedź jest dokładnie przedstawiona
4esn0k
1
Myślę, że wybór jest prosty: Potrzebujesz wsparcia dla liczb do 32 bitów podpisanych? Zastosowanie ~~(x/y). Potrzebujesz obsługi większej liczby podpisanych do 54 bitów? Użyj, Math.truncjeśli go masz, lub w Math.floorinny sposób (popraw dla liczb ujemnych). Potrzebujesz obsługi nawet większych liczb? Użyj biblioteki dużej liczby.
Stijn de Witt
4
dla rubyistów tutaj z wyszukiwarki Google divmod, możesz zaimplementować go jako taki:function divmod(x, y) { var div = Math.trunc(x/y); var rem = x % y; return [div, rem]; }
Alex Moore-Niemi
29
var remainder = x % y;
return (x - remainder) / y;
gammax
źródło
1
Ta wersja niestety nie przejdzie testu, gdy x = -100, ponieważ zwraca -34 zamiast -33.
Samuel
1
co z „var x = 0,3; var y = 0,01;” ? (dzięki github.com/JuliaLang/julia/issues/4156#issuecomment-23324163 )
4esn0k
W rzeczywistości @Samuel, z wartościami ujemnymi, ta metoda zwraca poprawne wyniki lub przynajmniej zwraca te same wartości, co metoda wykorzystująca Math.trunc:). Sprawdziłem za pomocą 100,3; -100,3; 100, -3 i -100, -3. Oczywiście minęło wiele czasu od twojego komentarza i rzeczy się zmieniają.
Marjan Venema,
19

Zwykle używam:

const quotient =  (a - a % b) / b;
const remainder = a % b;

To chyba nie jest najbardziej elegancki, ale działa.

Wolf Elkan
źródło
1
Ładne rozwiązanie, ponieważ pozwala uniknąć brzydoty podczas parsowania lub obcinania pływaka.
Dem Pilafian
6
jeśli potrzebujesz zarówno ilorazu, jak i reszty, oblicz najpierw resztę, a następnie ponownie użyj tej wartości w wyrażeniu dla ilorazu, tj. iloraz = (a - reszta) / b;
gb96
2
reszta = a% b; iloraz = (a - reszta) / b;
Zv_oDD,
16

Możesz użyć tej funkcji, parseIntaby uzyskać obcięty wynik.

parseInt(a/b)

Aby uzyskać resztę, użyj operatora mod:

a%b

parseInt ma pewne pułapki z łańcuchami, aby uniknąć użycia parametru radix z bazą 10

parseInt("09", 10)

W niektórych przypadkach ciąg znaków reprezentujący liczbę może być notacją naukową, w tym przypadku parsowanie spowoduje niepoprawny wynik.

parseInt(100000000000000000000000000000000, 10) // 1e+32

W wyniku tego wywołanie wygeneruje 1.

Édipo Costa Rebouças
źródło
7
parseIntw miarę możliwości należy tego unikać. Oto ostrzeżenie Douglasa Crockforda: „Jeśli pierwszym znakiem ciągu jest 0, to łańcuch jest przetwarzany w podstawie 8 zamiast w podstawie 10. W bazie 8, 8 i 9 nie są cyframi, więc parsuj („ 08 ”) i parsuj („09”) dają wynik 0. Błąd ten powoduje problemy w programach, które analizują daty i godziny. Na szczęście parseInt może przyjąć parametr radix, więc parseInt („08”, 10) daje 8. Zalecam, abyś zawsze podaj parametr radix. ” archive.oreilly.com/pub/a/javascript/excerpts/…
Powers
3
W dziale oczekuję, że otrzymam liczbę, a nie ciąg znaków, ale to dobra uwaga.
Édipo Costa Rebouças
2
@Powers, więc dodaj podstawkę. Nie mówi, że parseIntnależy go unikać; po prostu trzeba pamiętać o kilku problemach. Musisz zdawać sobie sprawę z tych rzeczy i być przygotowanym na poradzenie sobie.
Brak
2
Nigdy nie dzwoń parseIntz argumentem liczbowym. parseIntma analizować ciągi częściowo numeryczne, a nie obcinać liczby.
Oriol
1
Tylko dlatego, że pierwotnie rzeczy nie mają być używane w określony sposób, nie oznacza to, że nie powinieneś. Ta odpowiedź działa.
fregante
6

JavaScript oblicza na podstawie dolnej liczby liczb ujemnych i pozostałej liczby niecałkowitej, zgodnie z ich matematycznymi definicjami.

FLOOR jest zdefiniowany jako „największa liczba całkowita mniejsza niż parametr”, a zatem:

  • liczby dodatnie: PODŁOGA (X) = całkowita liczba X;
  • liczby ujemne: PODŁOGA (X) = całkowita liczba X minus 1 (ponieważ musi być MNIEJSZA niż parametr, tzn. więcej ujemna!)

REMAINDER jest zdefiniowany jako „pozostały” podziału (arytmetyka euklidesowa). Gdy dywidenda nie jest liczbą całkowitą, iloraz zwykle nie jest również liczbą całkowitą, tzn. Nie ma reszty, ale jeśli iloraz jest zmuszony do bycia liczbą całkowitą (i tak się dzieje, gdy ktoś próbuje uzyskać resztę lub moduł liczba zmiennoprzecinkowa), oczywiście pozostanie „liczba całkowita”.

JavaScript oblicza wszystko zgodnie z oczekiwaniami, więc programista musi uważnie zadawać właściwe pytania (a ludzie powinni uważać, aby odpowiedzieć na to, co jest zadawane!) Pierwsze pytanie Yarina brzmiało NIE „jaki jest całkowity podział X na Y”, ale: zamiast tego „CAŁA liczba przypadków, gdy dana liczba całkowita PRZEJDZIE DO INNEJ”. W przypadku liczb dodatnich odpowiedź jest taka sama dla obu, ale nie dla liczb ujemnych, ponieważ podział na liczby całkowite (dywidenda przez dzielnik) będzie -1 mniejszy niż czas, w którym liczba (dzielnik) „przechodzi” na inną (dywidenda). Innymi słowy, FLOOR zwróci poprawną odpowiedź dla liczb całkowitych liczb ujemnych, ale Yarin o to nie pytał!

gammax odpowiedział poprawnie, że kod działa zgodnie z zapytaniem Yarina. Z drugiej strony, Samuel się myli, chyba nie zrobił matematyki, bo inaczej zobaczyłby, że to działa (nie powiedział też, jaki był dzielnik jego przykładu, ale mam nadzieję, że tak było 3):

Reszta = X% Y = -100% 3 = -1

GoesInto = (X - pozostały) / Y = (-100 - -1) / 3 = -99 / 3 = -33

Nawiasem mówiąc, przetestowałem kod w przeglądarce Firefox 27.0.1, działał on zgodnie z oczekiwaniami, z liczbami dodatnimi i ujemnymi, a także z wartościami niecałkowitymi, zarówno dla dywidendy, jak i dzielnika. Przykład:

-100,34 / 3,57: GoesInto = -28, reszta = -0,3800000000000079

Tak, zauważyłem, że jest tam problem z precyzją, ale nie miałem czasu go sprawdzić (nie wiem, czy to problem z Firefoksem, Windows 7 lub z FPU mojego procesora). W przypadku pytania Yarina, które dotyczy tylko liczb całkowitych, kod gammax działa doskonale.

Cyberknight
źródło
5

Math.floor(operation) zwraca zaokrągloną wartość operacji.

Przykład 1 st pytania:

var x = 5;
var y = 10.4;
var z = Math.floor(x + y);

console.log(z);

Konsola:

15

Przykład drugiego pytania:

var x = 14;
var y = 5;
var z = Math.floor(x%y);

console.log(x);

Konsola:

4

Aetricity
źródło
2

Obliczanie liczby stron można wykonać w jednym kroku: Math.ceil (x / y)

użytkownik1920925
źródło
Nie rozumiem, jak to zapewnia resztę.
Paul Rooney,
1

Komentarz Alexa Moore-Niemiego jako odpowiedź:

Dla rubyistów tutaj z Google w poszukiwaniu divmod, możesz zaimplementować go jako taki:

function divmod(x, y) {
  var div = Math.trunc(x/y);
  var rem = x % y;
  return [div, rem];
}

Wynik:

// [2, 33]
Alex
źródło
2
Zwykle divmodużywa dzielnika dzielonego ( Math.floor), który różni się od skróconego dzielenia ( Math.trunc), gdy występują liczby ujemne. Tak jest w przypadku pakietu NPMdivmod , Rubydivmod , SWI-Prologdivmod i prawdopodobnie wielu innych implementacji.
Palec
Skrócony podział daje bardziej naturalnie wyglądające wyniki niż podział zmiennoprzecinkowy, ale zgodność przebija to, IMO. Być może istnieją również matematyczne lub wydajnościowe powody stosowania dzielenia zmiennoprzecinkowego. Zauważ, że zwykle divmodistnieje, ponieważ wykonuje się go dwa razy szybciej niż osobne obliczenie dwóch operacji. Zapewnienie takiej funkcji bez tej korzyści w zakresie wydajności może być mylące.
Palec
1

Jeśli dzielisz tylko potęgami dwóch, możesz użyć operatorów bitowych:

export function divideBy2(num) {
  return [num >> 1, num & 1];
}

export function divideBy4(num) {
  return [num >> 2, num & 3];
}

export function divideBy8(num) {
  return [num >> 3, num & 7];
}

(Pierwszy to iloraz, drugi to reszta)

Konstantin Möllers
źródło
Generalnie function divideByPowerOf2(num, exponent) { return [num >> exponent, num & ((1 << exponent) - 1)]; }.
Palec
0

Możesz użyć trójskładnika, aby zdecydować, jak obsługiwać dodatnie i ujemne wartości całkowite.

var myInt = (y > 0) ? Math.floor(y/x) : Math.floor(y/x) + 1

Jeśli liczba jest dodatnia, wszystko jest w porządku. Jeśli liczba jest ujemna, dodaje 1, ponieważ Math.floor obsługuje negatywy.

John Galt
źródło
0

To zawsze skróci się do zera. Nie jestem pewien, czy jest za późno, ale oto:

function intdiv(dividend, divisor) { 
    divisor = divisor - divisor % 1;
    if (divisor == 0) throw new Error("division by zero");
    dividend = dividend - dividend % 1;
    var rem = dividend % divisor;
    return { 
        remainder: rem, 
        quotient: (dividend - rem) / divisor
    };
}
bzim
źródło
0

Jeśli musisz obliczyć resztę dla bardzo dużych liczb całkowitych, których środowisko wykonawcze JS nie może reprezentować jako takie (każda liczba całkowita większa niż 2 ^ 32 jest reprezentowana jako liczba zmiennoprzecinkowa, a zatem traci precyzję), musisz zrobić jakąś sztuczkę.

Jest to szczególnie ważne przy sprawdzaniu wielu przypadków cyfr czekowych, które są obecne w wielu przypadkach naszego codziennego życia (numery kont bankowych, karty kredytowe, ...)

Przede wszystkim potrzebujesz numeru jako łańcucha (w przeciwnym razie straciłeś już precyzję, a reszta nie ma sensu).

str = '123456789123456789123456789'

Teraz musisz podzielić sznurek na mniejsze części, wystarczająco małe, aby konkatenacja każdej pozostałej części i kawałka sznurka mieściła się w 9 cyfrach.

digits = 9 - String(divisor).length

Przygotuj wyrażenie regularne, aby podzielić ciąg

splitter = new RegExp(`.{1,${digits}}(?=(.{${digits}})+$)`, 'g')

Na przykład, jeśli digitsjest to 7, wyrażenie regularne to

/.{1,7}(?=(.{7})+$)/g

Odpowiada niepustemu podciągowi o maksymalnej długości 7, po którym następuje ( (?=...)jest to znak z wyprzedzeniem) liczba znaków, która jest wielokrotnością liczby 7. „g” powoduje, że wyrażenie przechodzi przez cały ciąg, nie zatrzymując się przy pierwszym dopasowaniu.

Teraz przekonwertuj każdą część na liczbę całkowitą i oblicz resztę przez reduce(dodając poprzednią resztę - lub 0 - pomnożoną przez prawidłową moc 10):

reducer = (rem, piece) => (rem * Math.pow(10, digits) + piece) % divisor

Działa to z powodu algorytmu reszty „odejmowania”:

n mod d = (n - kd) mod d

który pozwala zastąpić dowolną „początkową część” dziesiętnej reprezentacji liczby jej pozostałą częścią, bez wpływu na końcową resztę.

Ostateczny kod wyglądałby następująco:

function remainder(num, div) {
  const digits = 9 - String(div).length;
  const splitter = new RegExp(`.{1,${digits}}(?=(.{${digits}})+$)`, 'g');
  const mult = Math.pow(10, digits);
  const reducer = (rem, piece) => (rem * mult + piece) % div;

  return str.match(splitter).map(Number).reduce(reducer, 0);
}
przepisane
źródło