Number.sign () trong javascript


101

Tự hỏi nếu có bất kỳ cách quan trọng nào để tìm dấu của một số ( hàm dấu hiệu )?
Có thể là giải pháp ngắn hơn / nhanh hơn / thanh lịch hơn so với giải pháp hiển nhiên

var sign = number > 0 ? 1 : number < 0 ? -1 : 0;

Câu trả lời ngắn!

Sử dụng cái này và bạn sẽ an toàn và nhanh chóng (nguồn: moz )

if (!Math.sign) Math.sign = function(x) { return ((x > 0) - (x < 0)) || +x; };

Bạn có thể muốn nhìn vào hiệu suất và kiểu ép buộc so fiddle

Đã lâu rồi. Hơn nữa chủ yếu là vì lý do lịch sử.


Các kết quả

Hiện tại, chúng tôi có các giải pháp sau:


1. Rõ ràng và nhanh chóng

function sign(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; }

1.1. Sửa đổi từ kbec - một loại đúc ít hơn, hiệu suất hơn, ngắn hơn [nhanh nhất]

function sign(x) { return x ? x < 0 ? -1 : 1 : 0; }

thận trọng: sign("0") -> 1


2. Thanh lịch, ngắn gọn, không quá nhanh [chậm nhất]

function sign(x) { return x && x / Math.abs(x); }

cảnh báo: sign(+-Infinity) -> NaN ,sign("0") -> NaN

Đối với Infinitymột số hợp pháp trong JS, giải pháp này dường như không hoàn toàn chính xác.


3. Nghệ thuật ... nhưng rất chậm [chậm nhất]

function sign(x) { return (x > 0) - (x < 0); }

4. Sử dụng bit-shift
nhanh, nhưngsign(-Infinity) -> 0

function sign(x) { return (x >> 31) + (x > 0 ? 1 : 0); }

5. Loại an toàn [megafast]

! Có vẻ như các trình duyệt (đặc biệt là chrome's v8) thực hiện một số tối ưu hóa kỳ diệu và giải pháp này hóa ra hoạt động hiệu quả hơn nhiều so với các trình duyệt khác, thậm chí hơn cả (1.1) mặc dù nó có chứa 2 hoạt động bổ sung và về mặt logic thì không bao giờ có thể nhanh hơn.

function sign(x) {
    return typeof x === 'number' ? x ? x < 0 ? -1 : 1 : x === x ? 0 : NaN : NaN;
}

Công cụ

Cải tiến được hoan nghênh!


[Offtopic] Câu trả lời được chấp nhận

  • Andrey Tarantsov - +100 cho nghệ thuật, nhưng đáng buồn là nó chậm hơn khoảng 5 lần so với cách tiếp cận rõ ràng

  • Frédéric Hamidi - bằng cách nào đó là câu trả lời được ủng hộ nhiều nhất (trong thời gian viết bài) và nó khá tuyệt, nhưng chắc chắn không phải là cách mọi thứ nên được thực hiện, imho. Ngoài ra, nó không xử lý chính xác số Vô cực, đó cũng là số, bạn biết đấy.

  • kbec - là một cải tiến của giải pháp rõ ràng. Không phải là cách mạng, nhưng tổng hợp tất cả lại với nhau, tôi coi cách tiếp cận này là tốt nhất. Bình chọn cho anh ấy :)


3
vấn đề là đôi khi 0là một trường hợp đặc biệt
bỏ

1
Tôi đã thực hiện một tập hợp các bài kiểm tra JSPerf (với các loại đầu vào khác nhau) để kiểm tra mọi thuật toán, có thể tìm thấy tại đây: jsperf.com/signs Kết quả có thể không được liệt kê trong bài đăng này!
Alba Mendez

2
@disfated, cái nào trong số đó? Tất nhiên, nếu bạn chạy test everythingphiên bản, Safe sẽ từ chối kiểm tra các giá trị đặc biệt, vì vậy nó sẽ nhanh hơn! Hãy thử chạy only integersthử nghiệm thay thế. Ngoài ra, JSPerf chỉ đang làm công việc của mình, không phải là vấn đề thích nó. :)
Alba Mendez

2
Theo các bài kiểm tra của jsperf, nó hóa ra điều đó typeof x === "number"tạo ra một số phép thuật cho biểu diễn. Xin vui lòng chạy thêm, đặc biệt là FF, Opera và IE để làm cho nó rõ ràng.
disfated

4
Để hoàn thiện, tôi đã thêm một thử nghiệm mới jsperf.com/signs/7 cho Math.sign()(0 === 0, không nhanh như "An toàn") đã xuất hiện trong FF25 và sắp có trong chrome.
Alex K.

Câu trả lời:



28

Chia số cho giá trị tuyệt đối của nó cũng cho biết dấu hiệu của nó. Việc sử dụng toán tử logic AND ngắn mạch cho phép chúng ta phân biệt trường hợp đặc biệt 0để chúng ta không phải chia cho nó:

var sign = number && number / Math.abs(number);

6
Bạn có thể muốn var sign = number && number / Math.abs(number);trong trường hợpnumber = 0
NullUserException

@NullUserException, bạn hoàn toàn đúng, 0cần phải có thái độ đặc biệt. Câu trả lời được cập nhật cho phù hợp. Cảm ơn :)
Frédéric Hamidi

Bạn là tốt nhất cho bây giờ. Nhưng tôi hy vọng sẽ có nhiều câu trả lời hơn trong tương lai.
disfated

24

Hàm bạn đang tìm kiếm được gọi là signum và cách tốt nhất để triển khai nó là:

function sgn(x) {
  return (x > 0) - (x < 0);
}

3
Chờ đợi. Có lỗi: for (x = -2; x <= 2; x ++) console.log ((x> 1) - (x <1)); cho [-1, -1, -1, 0, 1] for (x = -2; x <= 2; x ++) console.log ((x> 0) - (x <0)); cho đúng [-1, -1, 0, 1, 1]
disfated

13

Điều này có nên không hỗ trợ các số 0 có chữ ký của JavaScript (ECMAScript) không? Nó dường như hoạt động khi trả về x thay vì 0 trong hàm “megafast”:

function sign(x) {
    return typeof x === 'number' ? x ? x < 0 ? -1 : 1 : x === x ? x : NaN : NaN;
}

Điều này làm cho nó tương thích với bản nháp của Math.sign ( MDN ) của ECMAScript :

Trả về dấu của x, cho biết x là dương, âm hay 0.

  • Nếu x là NaN, kết quả là NaN.
  • Nếu x là −0, kết quả là −0.
  • Nếu x là +0, kết quả là +0.
  • Nếu x âm và không phải là −0, kết quả là −1.
  • Nếu x dương chứ không phải +0, kết quả là +1.

Cơ chế cực kỳ nhanh và thú vị, tôi rất ấn tượng. Đang chờ thêm các bài kiểm tra.
kbec

10

Đối với những người quan tâm đến những gì đang xảy ra với các trình duyệt mới nhất, trong phiên bản ES6 có một phương thức Math.sign riêng . Bạn có thể kiểm tra hỗ trợ tại đây .

Về cơ bản nó sẽ trả về -1, 1, 0hoặcNaN

Math.sign(3);     //  1
Math.sign(-3);    // -1
Math.sign('-3');  // -1
Math.sign(0);     //  0
Math.sign(-0);    // -0
Math.sign(NaN);   // NaN
Math.sign('foo'); // NaN
Math.sign();      // NaN

4
var sign = number >> 31 | -number >>> 31;

Siêu nhanh nếu bạn không cần Infinity và biết rằng con số là số nguyên, được tìm thấy trong nguồn openjdk-7: java.lang.Integer.signum()


1
Điều này không thành công đối với các phân số âm nhỏ như -0,5. (Có vẻ như nguồn là từ một triển khai dành riêng cho Số nguyên)
bắt đầu từ

1

Tôi nghĩ rằng tôi sẽ thêm điều này chỉ cho vui:

function sgn(x){
  return 2*(x>0)-1;
}

0 và NaN sẽ trả về -1
hoạt động tốt trên +/- Infinity


1

Một giải pháp hoạt động trên tất cả các số, cũng như 0-0, cũng như Infinity-Infinity, là:

function sign( number ) {
    return 1 / number > 0 ? 1 : -1;
}

Xem câu hỏi " +0 và -0 có giống nhau không? " Để biết thêm thông tin.


Cảnh báo: Không ai trong số những câu trả lời, kể cả bây giờ chuẩn Math.signcông việc ý chí về vụ việc 0vs -0. Đây có thể không phải là vấn đề đối với bạn, nhưng trong một số ứng dụng vật lý nhất định, nó có thể quan trọng.


0

Bạn có thể thay đổi số và kiểm tra Bit quan trọng nhất (MSB). Nếu MSB là 1 thì số âm. Nếu nó là 0 thì số đó là số dương (hoặc 0).


@ NullUserException Tôi vẫn có thể sai nhưng từ bài đọc của tôi "Các toán hạng của tất cả các toán tử bitwise được chuyển đổi thành số nguyên 32 bit có dấu theo thứ tự big-endian và ở định dạng bổ sung của hai." lấy từ MDN
Brombomb

Điều đó dường như vẫn còn rất nhiều công việc; bạn vẫn phải chuyển 1 và 0 thành -1 và 1, và 0 cũng phải được quan tâm. Nếu OP chỉ muốn rằng, nó sẽ dễ dàng hơn chỉ để sử dụngvar sign = number < 0 : 1 : 0
NullUserException

+1. Tuy nhiên, không cần phải thay đổi, bạn chỉ có thể làm n & 0x80000000như một bitmask. Đối với chuyển đổi thành 0,1, -1:n && (n & 0x80000000 ? -1 : 1)
davin

@davin Có phải tất cả các số đều được đảm bảo hoạt động với bitmask đó không? Tôi cắm vào -5e32và nó bị hỏng.
NullUserException

@NullUserException ఠ_ఠ, các số có cùng dấu khi áp dụng các tiêu chuẩn ToInt32. Nếu bạn đọc ở đó (phần 9.5) thì có một mô đun ảnh hưởng đến giá trị của các số vì phạm vi của số nguyên 32 bit nhỏ hơn phạm vi của loại Số js. Vì vậy, nó sẽ không hoạt động cho các giá trị đó hoặc các giá trị vô hạn. Tôi vẫn thích câu trả lời mặc dù.
davin

0

Tôi vừa định hỏi câu hỏi tương tự, nhưng đã tìm ra giải pháp trước khi tôi viết xong, thấy Câu hỏi này đã tồn tại, nhưng không thấy giải pháp này.

(n >> 31) + (n > 0)

nó có vẻ nhanh hơn bằng cách thêm vào (n >> 31) + (n>0?1:0)


Rất đẹp. Mã của bạn có vẻ nhanh hơn một chút so với (1). (n> 0? 1: 0) nhanh hơn vì không có kiểu ép kiểu nào. Thời điểm thất vọng duy nhất là dấu (-Infinity) cho 0. Các bài kiểm tra cập nhật.
disfated

0

Rất giống với câu trả lời của Martijn là

function sgn(x) {
    isNaN(x) ? NaN : (x === 0 ? x : (x < 0 ? -1 : 1));
}

Tôi thấy nó dễ đọc hơn. Ngoài ra (hoặc, tuy nhiên, tùy thuộc vào quan điểm của bạn), nó cũng dò tìm những thứ có thể được hiểu là một con số; ví dụ, nó trả về -1khi được trình bày với '-5'.


0

Tôi không thấy bất kỳ trường hợp thực tế nào trả về -0 và 0 từ Math.signđó phiên bản của tôi là:

function sign(x) {
    x = Number(x);
    if (isNaN(x)) {
        return NaN;
    }
    if (x === -Infinity || 1 / x < 0) {
        return -1;
    }
    return 1;
};

sign(100);   //  1
sign(-100);  // -1
sign(0);     //  1
sign(-0);    // -1

Đây không phải là một hàm dấu hiệu
vô hiệu hóa

0

Các phương pháp tôi biết như sau:

Math.sign (n)

var s = Math.sign(n)

Đây là hàm gốc, nhưng chậm nhất là do chi phí của một lệnh gọi hàm. Tuy nhiên, nó xử lý 'NaN' trong đó những người khác bên dưới có thể chỉ giả sử 0 (tức là Math.sign ('abc') là NaN).

((n> 0) - (n <0))

var s = ((n>0) - (n<0));

Trong trường hợp này, chỉ bên trái hoặc bên phải có thể là 1 dựa trên dấu hiệu. Điều này dẫn đến 1-0(1), 0-1(-1) hoặc 0-0(0).

Tốc độ của tốc độ này dường như cổ xưa với tốc độ tiếp theo bên dưới trong Chrome.

(n >> 31) | (!! n)

var s = (n>>31)|(!!n);

Sử dụng "Dịch chuyển bên phải lan truyền biển báo". Về cơ bản dịch chuyển 31 giảm tất cả các bit ngoại trừ dấu hiệu. Nếu dấu hiệu được đặt, kết quả là -1, ngược lại là 0. Quyền của |nó sẽ kiểm tra dương tính bằng cách chuyển đổi giá trị thành boolean (0 hoặc 1 [BTW: chuỗi không phải số, chẳng hạn như !!'abc'trở thành 0 trong trường hợp này, và không phải NaN]) sau đó sử dụng phép toán OR theo bit để kết hợp các bit.

Đây có vẻ là hiệu suất trung bình tốt nhất trên các trình duyệt (ít nhất là tốt nhất trong Chrome và Firefox), nhưng không phải là nhanh nhất trong TẤT CẢ chúng. Vì một số lý do, toán tử bậc ba nhanh hơn trong IE.

n? n <0? -1: 1: 0

var s = n?n<0?-1:1:0;

Nhanh nhất trong IE vì một số lý do.

jsPerf

Các thử nghiệm đã thực hiện: https://jsperf.com/get-sign-from-value


0

Hai xu của tôi, với một hàm trả về kết quả tương tự như Math.sign sẽ làm, tức là dấu (-0) -> -0, dấu (-Infinity) -> -Infinity, sign (null) -> 0 , dấu (không xác định) -> NaN, v.v.

function sign(x) {
    return +(x > -x) || (x && -1) || +x;
}

Jsperf sẽ không cho phép tôi tạo bài kiểm tra hoặc bản sửa đổi, xin lỗi vì không thể cung cấp cho bạn các bài kiểm tra (tôi đã thử jsbench.github.io, nhưng kết quả có vẻ gần nhau hơn nhiều so với với Jsperf ...)

Nếu ai đó có thể vui lòng thêm nó vào bản sửa đổi Jsperf, tôi sẽ rất tò mò muốn xem nó như thế nào so với tất cả các giải pháp đã đưa ra trước đó ...

Cảm ơn bạn!

Jim.

CHỈNH SỬA :

Tôi nên viết:

function sign(x) {
    return +(x > -x) || (+x && -1) || +x;
}

( (+x && -1)thay vì (x && -1)) để xử lý sign('abc')đúng cách (-> NaN)


0

Math.sign không được hỗ trợ trên IE 11. Tôi đang kết hợp câu trả lời hay nhất với câu trả lời Math.sign:

Math.sign = Math.sign || function(number){
    var sign = number ? ( (number <0) ? -1 : 1) : 0;
    return sign;
};

Bây giờ, người ta có thể sử dụng Math.sign trực tiếp.


1
Bạn đã thúc đẩy tôi cập nhật câu hỏi của tôi. 8 năm trôi qua kể từ khi nó được hỏi. Cũng đã cập nhật jsfiddle của tôi thành es6 và api window.performance. Nhưng tôi thích phiên bản của mozilla dưới dạng polyfill vì nó khớp với kiểu ép buộc của Math.sign. Ngày nay, hiệu suất không còn là mối quan tâm nữa.
disfated
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.