Number.sign () ในจาวาสคริปต์


101

สงสัยว่ามีวิธีที่ไม่สำคัญในการค้นหาเครื่องหมายของตัวเลข ( ฟังก์ชัน signum ) หรือไม่?
อาจจะสั้น / เร็วขึ้น / สวยหรูกว่าโซลูชันที่ชัดเจน

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

ตอบสั้น ๆ !

ใช้สิ่งนี้แล้วคุณจะปลอดภัยและรวดเร็ว (ที่มา: moz )

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

คุณอาจต้องการดูที่การเปรียบเทียบประสิทธิภาพและการบังคับประเภทซอ

เวลาผ่านไปเนิ่นนาน นอกจากนี้ส่วนใหญ่เป็นเหตุผลทางประวัติศาสตร์


ผล

สำหรับตอนนี้เรามีวิธีแก้ปัญหาเหล่านี้:


1.ชัดเจนและรวดเร็ว

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

1.1. การดัดแปลงจากkbec - ประเภทหนึ่งร่ายน้อยลงมีประสิทธิภาพมากขึ้นสั้นลง[เร็วที่สุด]

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

ข้อควรระวัง: sign("0") -> 1


2.สง่าสั้นไม่เร็วมาก[ช้าที่สุด]

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

ข้อควรระวัง: sign(+-Infinity) -> NaN ,sign("0") -> NaN

Infinityเป็นตัวเลขทางกฎหมายใน JS โซลูชันนี้ดูเหมือนจะไม่ถูกต้องทั้งหมด


3.ศิลปะ ... แต่ช้ามาก[ช้าที่สุด]

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

4.ใช้ bit-shift
เร็ว แต่sign(-Infinity) -> 0

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

5.ประเภทปลอดภัย [megafast]

! ดูเหมือนว่าเบราว์เซอร์ (โดยเฉพาะอย่างยิ่ง v8 ของ chrome) ทำการเพิ่มประสิทธิภาพเวทย์มนตร์และโซลูชันนี้มีประสิทธิภาพมากกว่าตัวอื่น ๆ มากแม้จะมากกว่า (1.1) แม้ว่าจะมีการดำเนินการเพิ่มเติม 2 อย่างและเหตุผลก็ไม่สามารถเร็วกว่านี้ได้

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

เครื่องมือ

  • jsperfการทดสอบ preformance ;
  • ซอ - การทดสอบประเภทนักแสดง

ยินดีต้อนรับการปรับปรุง!


[Offtopic] คำตอบที่ยอมรับ

  • Andrey Tarantsov - +100 สำหรับงานศิลปะ แต่น่าเศร้าที่มันช้ากว่าแนวทางที่ชัดเจนประมาณ 5 เท่า

  • Frédéric Hamidi - คำตอบที่ได้รับการโหวตมากที่สุด (สำหรับเวลาที่เขียน) และมันก็ค่อนข้างดี แต่มันก็ไม่ใช่สิ่งที่ควรทำอย่างแน่นอน imho นอกจากนี้ยังไม่จัดการกับตัวเลขอินฟินิตี้อย่างถูกต้องซึ่งเป็นตัวเลขด้วย

  • kbec - เป็นการปรับปรุงวิธีการแก้ปัญหาที่ชัดเจน ไม่ใช่การปฏิวัติ แต่เมื่อรวมทั้งหมดเข้าด้วยกันฉันคิดว่าแนวทางนี้ดีที่สุด โหวตให้เขา :)


3
ประเด็นก็คือบางครั้ง0เป็นกรณีพิเศษ
ทำให้เสียโฉม

1
ฉันได้ทำการทดสอบ JSPerf ชุดหนึ่ง (ด้วยอินพุตประเภทต่างๆ) เพื่อทดสอบทุกอัลกอริทึมซึ่งสามารถพบได้ที่นี่: jsperf.com/signs ผลลัพธ์อาจไม่ตรงตามที่ระบุไว้ในโพสต์นี้!
Alba Mendez

2
@ ติดอยู่อันไหน? แน่นอนว่าหากคุณใช้test everythingเวอร์ชันนี้ Safe จะปฏิเสธที่จะทดสอบค่าพิเศษดังนั้นมันจะเร็วขึ้น! ลองเรียกใช้การonly integersทดสอบแทน นอกจากนี้ JSPerf กำลังทำหน้าที่ของเขามันไม่ใช่เรื่องที่จะชอบ :)
Alba Mendez

2
จากการทดสอบของ jsperf ปรากฎว่าtypeof x === "number"ทำให้มีเวทมนตร์ในการแสดง โปรดทำการรันให้มากขึ้นโดยเฉพาะ FF, Opera และ IE เพื่อให้ชัดเจน
เสียโฉม

4
เพื่อความสมบูรณ์ฉันได้เพิ่มการทดสอบใหม่jsperf.com/signs/7สำหรับMath.sign()(0 === 0 ไม่เร็วเท่า "ปลอดภัย") ซึ่งปรากฏใน FF25 และกำลังจะมาใน chrome
Alex K.

คำตอบ:



28

การหารจำนวนด้วยค่าสัมบูรณ์จะให้เครื่องหมายของมันด้วย การใช้ตัวดำเนินการทางตรรกะและการลัดวงจรทำให้เราสามารถใช้กรณีพิเศษได้0ดังนั้นเราจึงไม่ต้องหารด้วยมัน:

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

6
คุณอาจต้องการvar sign = number && number / Math.abs(number);ในกรณีนี้number = 0
NullUserException

@NullUserException คุณพูดถูก0ต้องต้องได้รับการกำหนดเป็นพิเศษ คำตอบอัปเดตตามนั้น ขอบคุณ :)
Frédéric Hamidi

คุณดีที่สุดสำหรับตอนนี้ แต่ฉันหวังว่าจะมีคำตอบเพิ่มเติมในอนาคต
เสียโฉม

24

ฟังก์ชันที่คุณกำลังมองหาเรียกว่าsignumและวิธีที่ดีที่สุดในการนำไปใช้คือ:

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

3
รอ. มีข้อผิดพลาด: สำหรับ (x = -2; x <= 2; x ++) console.log ((x> 1) - (x <1)); ให้ [-1, -1, -1, 0, 1] สำหรับ (x = -2; x <= 2; x ++) console.log ((x> 0) - (x <0)); ให้ถูกต้อง [-1, -1, 0, 1, 1]
ทำให้เสียโฉม

13

สิ่งนี้ไม่ควรสนับสนุนการเซ็นชื่อศูนย์ของ JavaScript (ECMAScript) หรือไม่ ดูเหมือนว่าจะใช้งานได้เมื่อคืนค่า x แทนที่จะเป็น 0 ในฟังก์ชัน "megafast":

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

สิ่งนี้ทำให้เข้ากันได้กับร่างMath.sign ( MDN ) ของECMAScript :

ส่งคืนเครื่องหมายของ x ระบุว่า x เป็นบวกลบหรือศูนย์

  • ถ้า x เป็น NaN ผลลัพธ์คือ NaN
  • ถ้า x เป็น −0 ผลลัพธ์คือ −0
  • ถ้า x คือ +0 ผลลัพธ์คือ +0
  • ถ้า x เป็นลบและไม่ใช่ −0 ผลลัพธ์คือ −1
  • ถ้า x เป็นบวกและไม่ใช่ +0 ผลลัพธ์คือ +1

กลไกที่รวดเร็วและน่าสนใจอย่างไม่น่าเชื่อฉันประทับใจ กำลังรอการทดสอบเพิ่มเติม
kbec

10

สำหรับผู้ที่สนใจสิ่งที่เกิดขึ้นกับเบราว์เซอร์ล่าสุดในเวอร์ชัน ES6 มีวิธีMath.signดั้งเดิม คุณสามารถตรวจสอบการสนับสนุนได้ที่นี่การสนับสนุนที่นี่

โดยทั่วไปก็จะส่งกลับ-1, 1, 0หรือNaN

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;

เร็วมากหากคุณไม่ต้องการอินฟินิตี้และรู้ว่าตัวเลขนั้นเป็นจำนวนเต็มพบได้ใน openjdk-7 ที่มา: java.lang.Integer.signum()


1
สิ่งนี้ล้มเหลวสำหรับเศษส่วนเชิงลบขนาดเล็กเช่น -0.5 (ดูเหมือนว่าแหล่งที่มาจะมาจากการใช้งานสำหรับจำนวนเต็มโดยเฉพาะ)
starwed

1

ฉันคิดว่าฉันจะเพิ่มสิ่งนี้เพื่อความสนุก:

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

0 และ NaN จะส่งกลับ -1
ทำงานได้ดีใน +/- Infinity


1

วิธีแก้ปัญหาที่ใช้ได้กับตัวเลขทั้งหมดตลอดจน0และ-0ตลอดจนInfinityและ-Infinityคือ:

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

ดูคำถาม " +0 และ -0 เหมือนกันหรือไม่ " สำหรับข้อมูลเพิ่มเติม


คำเตือน:ไม่มีคำตอบเหล่านี้รวมถึงมาตรฐานMath.signการทำงานจะในกรณีที่VS0 -0นี่อาจไม่ใช่ปัญหาสำหรับคุณ แต่ในการใช้งานทางฟิสิกส์บางอย่างอาจมีความสำคัญ


0

คุณสามารถเปลี่ยนตัวเลขเล็กน้อยและตรวจสอบบิตที่มีนัยสำคัญที่สุด (MSB) ถ้า MSB เป็น 1 แสดงว่าจำนวนนั้นเป็นลบ ถ้าเป็น 0 แสดงว่าจำนวนนั้นเป็นบวก (หรือ 0)


@ NullUserException ฉันอาจจะยังผิดอยู่ แต่จากการอ่านของฉัน "ตัวถูกดำเนินการของตัวดำเนินการบิตไวซ์ทั้งหมดจะถูกแปลงเป็นจำนวนเต็ม 32 บิตที่เซ็นชื่อในลำดับที่ใหญ่ที่สุดและในรูปแบบส่วนเติมเต็มสอง" นำมาจากMDN
Brombomb

นั่นยังดูเหมือนงานที่แย่มาก คุณยังต้องแปลง 1 และ 0 เป็น -1 และ 1 และ 0 ยังต้องได้รับการดูแล ถ้า OP ต้องการแค่นั้นก็จะง่ายกว่าที่จะใช้var sign = number < 0 : 1 : 0
NullUserException

+1. ไม่จำเป็นต้องเปลี่ยน แต่คุณสามารถทำn & 0x80000000เหมือน bitmask ได้ สำหรับการแปลงเป็น 0,1, -1:n && (n & 0x80000000 ? -1 : 1)
davin

@davin ตัวเลขทั้งหมดรับประกันว่าจะใช้งานได้กับ bitmask นั้นหรือไม่? เสียบ-5e32แล้วมันแตก
NullUserException

@NullUserException ToInt32ఠ_ఠตัวเลขที่ถือป้ายเดียวกันเมื่อนำไปใช้มาตรฐาน หากคุณอ่านตรงนั้น (ส่วนที่ 9.5) มีโมดูลัสที่มีผลต่อค่าของตัวเลขเนื่องจากช่วงของจำนวนเต็ม 32 บิตน้อยกว่าช่วงของประเภท js Number ดังนั้นมันจะใช้ไม่ได้กับค่าเหล่านั้นหรือ infinities ฉันยังคงชอบคำตอบแม้ว่า
davin

0

ฉันเพิ่งจะถามคำถามเดียวกัน แต่มาถึงวิธีแก้ปัญหาก่อนที่ฉันจะเขียนเสร็จเห็นคำถามนี้มีอยู่แล้ว แต่ไม่เห็นวิธีแก้ปัญหานี้

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

ดูเหมือนว่าจะเร็วกว่าโดยการเพิ่ม ternary (n >> 31) + (n>0?1:0)


ดีมาก. รหัสของคุณค่อนข้างเร็วกว่า (1) เล็กน้อย (n> 0? 1: 0) เร็วกว่าเนื่องจากไม่มีการร่ายประเภทใด ช่วงเวลาที่น่าผิดหวังเพียงอย่างเดียวคือเครื่องหมาย (-Infinity) ให้ 0 อัปเดตการทดสอบ
เสียโฉม

0

คล้ายกับคำตอบของ Martijn

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

ฉันคิดว่ามันน่าอ่านมากขึ้น นอกจากนี้ (หรือขึ้นอยู่กับมุมมองของคุณ) มันยังรวบรวมสิ่งต่างๆที่สามารถตีความเป็นตัวเลขได้อีกด้วย เช่นก็จะส่งกลับเมื่อนำเสนอด้วย-1'-5'


0

ฉันไม่เห็นเหตุผลที่เป็นประโยชน์ในการคืนค่า -0 และ 0 Math.signดังนั้นเวอร์ชันของฉันคือ:

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

นี่ไม่ใช่ฟังก์ชั่นสัญญาณ
ทำให้เสียโฉม

0

วิธีการที่ฉันรู้มีดังนี้:

Math.sign (n)

var s = Math.sign(n)

นี่เป็นฟังก์ชันดั้งเดิม แต่ช้าที่สุดเนื่องจากค่าใช้จ่ายของการเรียกฟังก์ชัน อย่างไรก็ตามมันจัดการ 'NaN' โดยที่คนอื่น ๆ ด้านล่างอาจถือว่า 0 (เช่น Math.sign ('abc') คือ NaN)

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

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

ในกรณีนี้มีเพียงด้านซ้ายหรือด้านขวาเท่านั้นที่สามารถเป็น 1 ตามเครื่องหมาย ผลลัพธ์เป็น1-0(1), 0-1(-1) หรือ0-0(0)

ความเร็วของความเร็วนี้ดูเหมือนว่าคอและคอกับความเร็วถัดไปด้านล่างใน Chrome

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

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

ใช้ "เครื่องหมาย - การเลื่อนขวา" โดยพื้นฐานแล้วการขยับโดย 31 จะลดบิตทั้งหมดยกเว้นเครื่องหมาย หากตั้งค่าเครื่องหมายผลลัพธ์จะเป็น -1 มิฉะนั้นจะเป็น 0 ทางขวาของ|มันจะทดสอบค่าบวกโดยการแปลงค่าเป็นบูลีน (0 หรือ 1 [BTW: สตริงที่ไม่ใช่ตัวเลขเช่น!!'abc'กลายเป็น 0 ในกรณีนี้และ ไม่ใช่ NaN]) จากนั้นใช้บิตหรือการดำเนินการเพื่อรวมบิต

ดูเหมือนว่าจะมีประสิทธิภาพโดยเฉลี่ยที่ดีที่สุดในทุกเบราว์เซอร์ (ดีที่สุดใน Chrome และ Firefox เป็นอย่างน้อย) แต่ไม่เร็วที่สุดในทุกเบราว์เซอร์ ด้วยเหตุผลบางประการตัวดำเนินการ ternary จึงเร็วกว่าใน IE

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

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

เร็วที่สุดใน IE ด้วยเหตุผลบางประการ

jsPerf

ทำการทดสอบ: https://jsperf.com/get-sign-from-value


0

สองเซ็นต์ของฉันพร้อมฟังก์ชันที่ส่งกลับผลลัพธ์เดียวกันกับที่ Math.sign ทำเช่นเครื่องหมาย (-0) -> -0, เครื่องหมาย (-Infinity) -> -Infinity, เครื่องหมาย (null) -> 0 , ลงชื่อ (ไม่ได้กำหนด) -> NaN ฯลฯ

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

Jsperf จะไม่ให้ฉันสร้างการทดสอบหรือแก้ไขขออภัยที่ไม่สามารถให้การทดสอบแก่คุณได้ (ฉันได้ลองใช้ jsbench.github.io แล้ว แต่ผลลัพธ์ดูเหมือนจะใกล้เคียงกันมากกว่า Jsperf ... )

หากมีใครสามารถเพิ่มลงในการแก้ไข Jsperf ฉันอยากรู้อยากเห็นว่ามันเปรียบเทียบกับโซลูชันที่ให้มาก่อนหน้านี้อย่างไร ...

ขอบคุณ!

จิม.

แก้ไข :

ฉันควรจะเขียน:

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

( (+x && -1)แทน(x && -1)) เพื่อจัดการsign('abc')อย่างถูกต้อง (-> NaN)


0

ไม่รองรับ Math.sign บน IE 11 ฉันกำลังรวมคำตอบที่ดีที่สุดเข้ากับคำตอบ Math.sign:

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

ตอนนี้เราสามารถใช้ Math.sign ได้โดยตรง


1
คุณผลักดันให้ฉันอัปเดตคำถามของฉัน 8 ปีผ่านไปตั้งแต่ถูกถาม อัปเดต jsfiddle ของฉันเป็น es6 และ window.performance api ด้วย แต่ฉันชอบเวอร์ชั่นของ mozilla เป็น polyfill เพราะมันตรงกับการบังคับประเภทของ Math.sign ประสิทธิภาพไม่น่ากังวลมากนักในปัจจุบัน
เสียโฉม
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.