Phân chia số nguyên với phần còn lại trong JavaScript?


929

Trong JavaScript , làm cách nào để có được:

  1. Toàn bộ số lần một số nguyên cho trước đi vào một số khác?
  2. Phần còn lại?

Câu trả lời:


1239

Đối với một số số yvà một số ước số xtính thương số ( quotient) và phần còn lại ( remainder) là:

var quotient = Math.floor(y/x);
var remainder = y % x;

84
% hoạt động trên các float trong JavaScript (điều này khác với nhiều ngôn ngữ khác), điều này có lẽ không mong muốn: 3.5 % 2ước tính là 1,5. Đảm bảo xử lý (parseInt, sàn, v.v.) theo yêu cầu

16
Phần tích phân của -4,5 trong toán học là -5, vì -5 là "số nguyên tích phân cao nhất có thể vẫn thấp hơn -4,5".
Toughy

9
Tuy nhiên, bất cứ điều gì bạn quyết định làm về số âm, thì nó phải nhất quán giữa thương số và phần còn lại. Sử dụng floor%cùng nhau không nhất quán theo cách đó. Hoặc sử dụng truncthay vì floor(do đó cho phép phần dư âm) hoặc sử dụng phép trừ để lấy phần còn lại ( rem = y - div * x).
Mark Reed

7
1. Nếu bạn vẫn sẽ tính phần còn lại rem, bạn có thể lấy thương số divnhanh hơn mà không cần sàn : (y - rem) / x. 2. Theo cách thức hoạt động modulo theo định nghĩa được đề xuất của Donald Knuth (ký hiệu khớp-ước, không phải là phần còn lại, ví dụ mô-đun Euclide, cũng không phải là mã hóa khớp với ký hiệu JavaScript) là những gì chúng ta có thể viết mã bằng JavaScript function mod (a, n) { return a % n + (Math.sign(a) !== Math.sign(n) ? n : 0); }.
Aaron Mansheim

1
-9 / 2 = -4,5. Sau đó, bạn lấy sàn -4,5, là -5. Hãy nhớ rằng -5 nhỏ hơn -4,5 và thao tác sàn được xác định là số nguyên lớn nhất nhỏ hơn một giá trị đã cho.
Mark Reed

371

Tôi không phải là chuyên gia về toán tử bitwise, nhưng đây là một cách khác để lấy toàn bộ số:

var num = ~~(a / b);

Điều này cũng sẽ hoạt động đúng cho các số âm, trong khi Math.floor()sẽ làm tròn sai hướng.

Điều này có vẻ đúng như là tốt:

var num = (a / b) >> 0;

84
Một người khác, mục đích mà tôi vừa dành 20 phút cuối cùng để cố gắng tìm ra, rõ ràng làa/b | 0
BlueRaja - Danny Pflughoeft

18
@ user113716 @BlueRaja Các thao tác bitwise chỉ có ý nghĩa đối với các kiểu số nguyên và tất nhiên là JS biết điều đó. ~~int, int | 0int >> 0không thay đổi lập luận ban đầu, nhưng làm thông dịch viên vượt qua phần không thể thiếu để điều hành.
Aleksei Zabrodskii

15
floorhầu như không đi sai hướng, được đặt tên - chỉ không phải là hướng mà mọi người thường muốn!
Mark K Cowan

19
Đó là một buu buu. a = 12447132275286670000; b = 128 Math.floor(a/b)-> 97243220900677100~~(a/b)-> -1231452688.
Mirek Rusin

7
Hãy cẩn thận với quyền ưu tiên. ~~(5/2) --> 2cũng như (5/2)>>0 --> 2, nhưng ~~(5/2) + 1 --> 3, trong khi ~~(5/2)>>0 + 1 --> 1. ~~là một lựa chọn tốt vì ưu tiên phù hợp hơn.
timkay

217

Tôi đã làm một số bài kiểm tra tốc độ trên Firefox.

-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

Trên đây là dựa trên 10 triệu thử nghiệm cho mỗi.

Kết luận: Sử dụng (a/b>>0)(hoặc (~~(a/b))hay (a/b|0)) để đạt được khoảng 20% tăng hiệu quả. Cũng nên nhớ rằng tất cả chúng không phù hợp với Math.floor, khi nào a/b<0 && a%b!=0.


54
Lưu ý rằng tối ưu hóa phân chia số nguyên cho tốc độ sẽ chỉ có ý nghĩa nếu bạn làm việc đó nhiều . Trong mọi trường hợp khác, tôi khuyên bạn nên chọn cách đơn giản nhất (bất kỳ trường hợp nào có vẻ đơn giản nhất đối với bạn và đồng nghiệp của bạn).
mik01aj

7
@ m01 hoàn toàn đồng ý - có quá nhiều sự tập trung vào những thứ như thế này trực tuyến
JonnyRaa

2
@ m01 Nhưng cái nào khó hơn: tìm hiểu về Math.floorvà các chức năng API khác, hoặc tìm hiểu về ~toán tử (bitwise-not) và cách hoạt động của bitwise trong JS và sau đó hiểu tác dụng của dấu ngã kép?
Stijn de Witt

11
Chà, nếu đồng nghiệp của bạn không lập trình chip trong trình biên dịch chương trình, họ có thể sẽ hiểu rõ Math.floorhơn. Và thậm chí nếu không, cái này là có thể cho phép.
mik01aj

2
@MarkGreen có nhưng chỉ muốn làm điều đó không có nghĩa là bạn nên viết nó dưới dạng kỳ lạ chỉ vì nó xảy ra nhanh nhất, không có lý do chính đáng - nói chung mã rõ ràng thường là mối quan tâm đầu tiên của bạn. Ngoài ra, các thử nghiệm này có thể hoàn toàn vô nghĩa sau khi thay đổi ngôn ngữ và trên các trình duyệt khác nhau - bạn cần lập hồ sơ để tìm hiểu những gì chậm trong ứng dụng của mình. Đây không phải là phương pháp phân chia số nguyên của bạn trừ khi bạn đã tối ưu hóa từng thứ khác!
JonnyRaa

150

ES6 giới thiệu Math.truncphương pháp mới . Điều này cho phép sửa câu trả lời của @ MarkElliot để làm cho nó hoạt động với các số âm:

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

Lưu ý rằng Mathcác phương thức có lợi thế hơn các toán tử bitwise mà chúng hoạt động với các số trên 2 31 .


var y = 18014398509481984; var x = 5; div =? - bọ cánh cứng ?
4esn0k

4
@ 4esn0k Đây không phải là lỗi. Số của bạn có quá nhiều chữ số, bạn không thể có độ chính xác cao như vậy trong định dạng nhị phân 64 bit số IEEE 754. Ví dụ , 18014398509481984 == 18014398509481985.
Oriol

18014398509481984 == 2 ** 54 và tôi đặc biệt sử dụng số này, vì nó được thể hiện chính xác ở định dạng binary64. và câu trả lời cũng được trình bày chính xác
4esn0k

1
Tôi nghĩ rằng sự lựa chọn rất đơn giản: Bạn cần hỗ trợ cho các số có tới 32 bit được ký? Sử dụng ~~(x/y). Cần hỗ trợ số lượng lớn hơn lên đến 54 bit đã ký? Sử dụng Math.truncnếu bạn có nó, hoặc Math.floornếu không (chính xác cho số âm). Cần hỗ trợ số lượng lớn hơn? Sử dụng một số thư viện số lượng lớn.
Stijn de Witt

4
đối với những người chơi ruby ​​ở đây từ google để tìm kiếm divmod, bạn có thể triển khai nó như sau: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;

1
Phiên bản này không may thất bại khi kiểm tra khi x = -100 vì nó trả về -34 thay vì -33.
Samuel

1
những gì về "var x = 0,3; var y = 0,01;" ? (cảm ơn github.com/JuliaLang/julia/issues/4156#issuecomment-23324163 )
4esn0k

Trên thực tế, @Samuel, với các giá trị âm, phương thức này trả về kết quả chính xác hoặc ít nhất là nó trả về các giá trị giống như phương thức sử dụng Math.trunc:). Tôi đã kiểm tra với 100,3; -100,3; 100, -3 và -100, -3. Tất nhiên, rất nhiều thời gian đã trôi qua kể từ khi bình luận của bạn và mọi thứ thay đổi.
Marjan Venema

19

Tôi thường sử dụng:

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

Nó có thể không phải là thanh lịch nhất, nhưng nó hoạt động.


1
Giải pháp tốt đẹp vì nó tránh được sự xấu xí của việc phân tích cú pháp hoặc cắt xén một cái phao.
Dem Pilafian

6
nếu bạn cần cả thương và phần còn lại, trước tiên hãy tính phần còn lại và sau đó sử dụng lại giá trị đó trong biểu thức cho thương số, tức là thương số = (a - phần còn lại) / b;
gb96

2
phần còn lại = a% b; thương số = (a - phần còn lại) / b;
Zv_oDD

16

Bạn có thể sử dụng hàm parseIntđể có được kết quả rút ngắn.

parseInt(a/b)

Để có phần còn lại, hãy sử dụng toán tử mod:

a%b

parseInt có một số cạm bẫy với chuỗi, để tránh sử dụng tham số cơ số với cơ sở 10

parseInt("09", 10)

Trong một số trường hợp, biểu diễn chuỗi của số có thể là một ký hiệu khoa học, trong trường hợp này, parseInt sẽ tạo ra kết quả sai.

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

Cuộc gọi này sẽ tạo ra 1 kết quả.


7
parseIntnên tránh khi có thể. Dưới đây là cảnh báo của Douglas Crockford: "Nếu ký tự đầu tiên của chuỗi là 0, thì chuỗi được đánh giá ở cơ sở 8 thay vì cơ sở 10. Trong cơ sở 8, 8 và 9 không phải là chữ số, vì vậy parseInt (" 08 ") và parseInt ("09") tạo ra 0 do kết quả của chúng. Lỗi này gây ra sự cố trong các chương trình phân tích ngày và thời gian. May mắn thay, parseInt có thể lấy tham số cơ số, do đó parseInt ("08", 10) tạo ra 8. Tôi khuyên bạn luôn luôn cung cấp thông số cơ số. " archive.oreilly.com/pub/a/javascript/excerpts/ từ
Quyền hạn

3
Trong một bộ phận, tôi mong nhận được một số, không phải là một chuỗi, nhưng đây là một điểm tốt.
Édipo Costa Rebouças

2
@Powers nên thêm cơ số. Anh ấy không nói parseIntnên tránh; chỉ là có một số vấn đề cần chú ý. Bạn phải nhận thức được những điều này và sẵn sàng đối phó.
Không có

2
Không bao giờ gọi parseIntvới một đối số số. parseIntđược cho là phân tích các chuỗi số một phần, không cắt các số.
Oriol

1
Chỉ vì những thứ ban đầu không có nghĩa là được sử dụng theo một cách nhất định, nó không có nghĩa là bạn không nên. Câu trả lời này hoạt động.
fregante

6

JavaScript tính toán đúng sàn các số âm và phần còn lại của các số không nguyên, theo các định nghĩa toán học cho chúng.

FLOOR được định nghĩa là "số nguyên lớn nhất nhỏ hơn tham số", do đó:

  • số dương: FLOOR (X) = phần nguyên của X;
  • số âm: FLOOR (X) = phần nguyên của X trừ 1 (vì nó phải NHỎ hơn tham số, nghĩa là âm hơn!)

REMAINDER được định nghĩa là "phần còn lại" của một bộ phận (số học Euclide). Khi cổ tức không phải là số nguyên, thương số thường cũng không phải là số nguyên, nghĩa là không có phần còn lại, nhưng nếu thương số buộc phải là số nguyên (và đó là điều xảy ra khi ai đó cố gắng lấy phần còn lại hoặc mô đun của số dấu phẩy động), rõ ràng sẽ có một "số dư" không nguyên.

JavaScript không tính toán mọi thứ như mong đợi, vì vậy, lập trình viên phải cẩn thận đặt câu hỏi thích hợp (và mọi người nên cẩn thận trả lời những gì được hỏi!) Câu hỏi đầu tiên của Yarin KHÔNG phải là "phép chia số nguyên của X cho Y", nhưng, thay vào đó, "số lần WHOLE của một số nguyên đã cho ĐI VÀO một số khác". Đối với các số dương, câu trả lời là giống nhau cho cả hai, nhưng không phải cho các số âm, bởi vì phép chia số nguyên (chia theo số chia) sẽ nhỏ hơn -1 so với số lần (số chia) "đi vào" số khác (cổ tức). Nói cách khác, FLOOR sẽ trả về câu trả lời đúng cho phép chia số nguyên của một số âm, nhưng Yarin đã không hỏi điều đó!

gammax trả lời đúng, mã đó hoạt động theo yêu cầu của Yarin. Mặt khác, Samuel đã sai, tôi đã không làm toán, tôi đoán, hoặc anh ta sẽ thấy rằng nó hoạt động (đồng thời, anh ta đã không nói đâu là ước số của ví dụ của mình, nhưng tôi hy vọng đó là 3):

Phần còn lại = X% Y = -100% 3 = -1

GoesInto = (X - Phần còn lại) / Y = (-100 - -1) / 3 = -99 / 3 = -33

Nhân tiện, tôi đã thử nghiệm mã trên Firefox 27.0.1, nó hoạt động như mong đợi, với các số dương và âm và cả với các giá trị không nguyên, cả về cổ tức và ước số. Thí dụ:

-100.34 / 3.57: GoesInto = -28, phần còn lại = -0.3800000000000079

Vâng, tôi nhận thấy, có một vấn đề chính xác ở đó, nhưng tôi không có thời gian để kiểm tra nó (tôi không biết đó là vấn đề với Firefox, Windows 7 hay với FPU CPU của tôi). Tuy nhiên, đối với câu hỏi của Yarin, chỉ liên quan đến số nguyên, mã của gammax hoạt động hoàn hảo.


5

Math.floor(operation) trả về giá trị làm tròn xuống của hoạt động.

Ví dụ về câu hỏi thứ 1 :

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

console.log(z);

Bảng điều khiển:

15

Ví dụ về câu hỏi thứ 2 :

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

console.log(x);

Bảng điều khiển:

4


2

Tính toán số lượng trang có thể được thực hiện trong một bước: Math.ceil (x / y)


Tôi không thấy cách này cung cấp phần còn lại.
Paul Rooney

1

Nhận xét của Alex Moore-Niemi như một câu trả lời:

Đối với Rubyists ở đây từ Google để tìm kiếm divmod, bạn có thể triển khai nó như sau:

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

Kết quả:

// [2, 33]

2
Thông thường, divmodsử dụng phép chia nổi ( Math.floor), khác với phép chia ( Math.trunc) khi các số âm có liên quan. Đây là trường hợp cho gói NPMdivmod , Rubydivmod , SWI-Prologdivmod và có lẽ nhiều triển khai khác nữa.
Palec

Phân chia rút gọn cho kết quả trông tự nhiên hơn so với phân chia nổi, nhưng tương thích hơn hẳn, IMO. Có thể, có những lý do toán học hoặc hiệu suất cho việc sử dụng phân chia thả nổi, quá. Lưu ý rằng thông thường, divmodtồn tại bởi vì nó thực hiện nhanh gấp đôi so với tính toán hai thao tác riêng biệt. Cung cấp một chức năng như vậy mà không có lợi ích hiệu suất này có thể gây nhầm lẫn.
Palec

1

Nếu bạn chỉ phân chia với quyền hạn của hai, bạn có thể sử dụng các toán tử bitwise:

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];
}

(Đầu tiên là thương số, thứ hai là phần còn lại)


Nói chung , function divideByPowerOf2(num, exponent) { return [num >> exponent, num & ((1 << exponent) - 1)]; }.
Palec

0

Bạn cũng có thể sử dụng ternary để quyết định cách xử lý các giá trị nguyên dương và âm.

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

Nếu số là dương, tất cả đều ổn. Nếu số đó là số âm, nó sẽ thêm 1 vì cách Math.floor xử lý số âm.


0

Điều này sẽ luôn cắt ngắn về không. Không chắc là quá muộn, nhưng ở đây nó đi:

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
    };
}

0

Nếu bạn cần tính toán phần còn lại cho các số nguyên rất lớn, thời gian chạy JS không thể biểu diễn như vậy (bất kỳ số nguyên nào lớn hơn 2 ^ 32 được biểu diễn dưới dạng nổi và do đó mất độ chính xác), bạn cần thực hiện một số mẹo.

Điều này đặc biệt quan trọng để kiểm tra nhiều trường hợp chữ số séc có trong nhiều trường hợp trong cuộc sống hàng ngày của chúng tôi (số tài khoản ngân hàng, thẻ tín dụng, ...)

Trước hết bạn cần số của bạn dưới dạng một chuỗi (nếu không bạn đã mất độ chính xác và phần còn lại không có ý nghĩa).

str = '123456789123456789123456789'

Bây giờ bạn cần chia chuỗi của bạn thành các phần nhỏ hơn, đủ nhỏ để việc nối bất kỳ phần còn lại và một đoạn chuỗi có thể khớp với 9 chữ số.

digits = 9 - String(divisor).length

Chuẩn bị một biểu thức chính quy để tách chuỗi

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

Chẳng hạn, nếu digitslà 7, thì biểu thức chính là

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

Nó khớp với một chuỗi con không trống có độ dài tối đa 7, được theo sau ( (?=...)là một cái nhìn tích cực) bởi một số ký tự là bội của 7. 'g' là làm cho biểu thức chạy qua tất cả các chuỗi, không dừng lại ở lần khớp đầu tiên.

Bây giờ chuyển đổi từng phần thành số nguyên và tính toán phần còn lại bằng cách reduce(thêm lại phần còn lại trước đó - hoặc 0 - nhân với công suất chính xác của 10):

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

Điều này sẽ hoạt động vì thuật toán còn lại "trừ":

n mod d = (n - kd) mod d

cho phép thay thế bất kỳ 'phần ban đầu' nào của biểu diễn thập phân của một số bằng phần còn lại của nó, mà không ảnh hưởng đến phần còn lại cuối cùng.

Mã cuối cùng sẽ trông như sau:

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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.