ใช้ bitwise หรือ 0 เพื่อปูพื้นตัวเลข


193

เพื่อนร่วมงานของฉันสะดุดเมื่อวิธีการตั้งค่าตัวเลขลอยพื้นโดยใช้บิตหรือ:

var a = 13.6 | 0; //a == 13

เรากำลังพูดถึงมันและสงสัยบางสิ่ง

  • มันทำงานยังไง? ทฤษฏีของเราคือการใช้โอเปอเรเตอร์ดังกล่าวปลดจำนวนให้เป็นจำนวนเต็มดังนั้นจึงลบส่วนที่เป็นเศษส่วน
  • มันมีข้อดีมากกว่าการทำMath.floorหรือไม่? อาจเร็วกว่านี้ไหม? (เล่นสำนวนไม่ได้ตั้งใจ)
  • มันมีข้อเสียหรือไม่? อาจจะไม่ได้ผลในบางกรณี? ความชัดเจนเป็นสิ่งที่ชัดเจนเนื่องจากเราต้องหาและฉันก็เขียนคำถามนี้

ขอบคุณ


6
ข้อเสีย: ใช้งานได้มากถึง 2 ^ 31−1 ซึ่งอยู่ที่ประมาณ 2 พันล้าน (10 ^ 9) ค่า Number สูงสุดอยู่ที่ประมาณ 10 ^ 308 btw
Šime Vidas

12
ตัวอย่าง: 3000000000.1 | 0ประเมินเป็น -1294967296 ดังนั้นวิธีนี้จึงไม่สามารถใช้กับการคำนวณเงินได้ (โดยเฉพาะในกรณีที่คุณคูณด้วย 100 เพื่อหลีกเลี่ยงตัวเลขทศนิยม)
Šime Vidas

13
@ ŠimeVidas Floats ไม่ควรใช้ในการคำนวณเงินเช่นกัน
George Reith

20
ไม่ใช่การปูพื้น แต่เป็นการตัด (ปัดเศษเป็น 0)
Bartłomiej Zalewski

3
@ ผลที่ตามมาลองพิมพ์0.1 + 0.2 == 0.3ในคอนโซล JavaScript หากภาษาของคุณรองรับคุณควรใช้ทศนิยม ถ้าไม่เก็บเซ็นต์แทน
Alex Turpin

คำตอบ:


161

มันทำงานยังไง? ทฤษฏีของเราคือการใช้โอเปอเรเตอร์ดังกล่าวปลดจำนวนให้เป็นจำนวนเต็มดังนั้นจึงลบส่วนที่เป็นเศษส่วน

การดำเนินการระดับบิตทั้งหมดยกเว้นการเลื่อนขวาที่ไม่ได้ลงชื่อ>>>, ทำงานกับจำนวนเต็ม 32 บิตที่ลงชื่อ ดังนั้นการใช้การดำเนินการในระดับบิตจะแปลงลอยเป็นจำนวนเต็ม

มันมีข้อดีมากกว่าการทำ Math.floor หรือไม่? อาจเร็วกว่านี้ไหม? (เล่นสำนวนไม่ได้ตั้งใจ)

http://jsperf.com/or-vs-floor/2ดูเหมือนจะเร็วขึ้นเล็กน้อย

มันมีข้อเสียหรือไม่? อาจจะไม่ได้ผลในบางกรณี? ความชัดเจนเป็นสิ่งที่ชัดเจนเนื่องจากเราต้องหาและฉันก็เขียนคำถามนี้

  • จะไม่ผ่าน jsLint
  • เลขจำนวนเต็มแบบ 32 บิตเท่านั้น
  • พฤติกรรมเปรียบเทียบแปลก: Math.floor(NaN) === NaNในขณะที่(NaN | 0) === 0

9
@harold แน่นอนเพราะมันไม่ได้อยู่ในความเป็นจริงเพียงตัดทอน
Alex Turpin

5
อีกข้อเสียที่เป็นไปได้ว่าในขณะที่Math.floor(NaN) === NaN (NaN | 0) === 0ความแตกต่างนั้นอาจมีความสำคัญในบางแอปพลิเคชัน
Ted Hopp

4
jsperf ของคุณกำลังให้ข้อมูลประสิทธิภาพสำหรับลูปที่ว่างเปล่าบนโครเมี่ยมเนื่องจากการเคลื่อนที่แบบวนรอบของโค้ดไม่คงที่ การทดสอบแบบ perf ที่ดีขึ้นเล็กน้อยคือ: jsperf.com/floor-performance/2
Sam Giles

4
นี่เป็นส่วนมาตรฐานของasm.js(ที่ฉันเรียนรู้ครั้งแรกเกี่ยวกับมัน) มันเร็วกว่าถ้าไม่มีเหตุผลอื่น ๆ เพราะมันไม่ได้เรียกฟังก์ชั่นในการที่วัตถุฟังก์ชั่นที่สามารถได้ตลอดเวลาถูกแทนที่ในขณะที่Math Math.floor = function(...)
gman

3
(value | 0) === valueสามารถใช้เพื่อตรวจสอบว่าค่าในความเป็นจริงแล้วเป็นจำนวนเต็มและเป็นจำนวนเต็มเท่านั้น (เช่นเดียวกับในซอร์สโค้ด Elm @ dwayne-crooks ที่เชื่อมโยง) และfoo = foo | 0สามารถใช้เพื่อบังคับค่าใด ๆ ให้เป็นจำนวนเต็ม (โดยที่ตัวเลข 32 บิตถูกตัดทอนและไม่ใช่ตัวเลขทั้งหมดกลายเป็น 0)
David Michael Gregg

36

นี่คือการตัดทอนเมื่อเทียบกับการปูพื้น คำตอบของฮาวเวิร์ดนั้นถูกต้อง แต่ฉันจะเพิ่มมันMath.floorทำสิ่งที่มันควรจะเกี่ยวกับจำนวนลบ ศาสตร์นั่นคือสิ่งที่พื้น

ในกรณีที่คุณอธิบายไว้ข้างต้นโปรแกรมเมอร์มีความสนใจในการตัดหรือตัดทศนิยมออกอย่างสมบูรณ์มากขึ้น แม้ว่าไวยากรณ์ที่พวกเขาใช้นั้นจะปิดบังความจริงที่ว่าพวกเขากำลังแปลง float ให้เป็น int


7
นี่คือคำตอบที่ถูกต้องซึ่งเป็นที่ยอมรับไม่ใช่ เพิ่มไปว่าMath.floor(8589934591.1)ก่อผลที่คาดว่าจะไม่ได้8589934591.1 | 0
Salman

21

ใน ECMAScript 6 ค่าเทียบเท่า|0คือMath.truncฉันควรจะบอกว่า:

ส่งคืนส่วนหนึ่งของตัวเลขโดยการลบเศษส่วนใด ๆ มันแค่ตัดทอนจุดและตัวเลขที่อยู่ด้านหลังไม่ว่าอาร์กิวเมนต์จะเป็นจำนวนบวกหรือจำนวนลบ

Math.trunc(13.37)   // 13
Math.trunc(42.84)   // 42
Math.trunc(0.123)   //  0
Math.trunc(-0.123)  // -0
Math.trunc("-1.123")// -1
Math.trunc(NaN)     // NaN
Math.trunc("foo")   // NaN
Math.trunc()        // NaN

6
ยกเว้นความจริงที่Math.trunc()ทำงานกับจำนวนที่สูงกว่าหรือเท่ากับ 2 ^ 31 และ| 0ไม่ได้
Nolyurn

10

จุดแรกของคุณถูกต้อง จำนวนจะถูกแปลงเป็นเลขจำนวนเต็มดังนั้นตัวเลขทศนิยมจะถูกลบ โปรดทราบว่าการMath.floorปัดเศษเป็นจำนวนเต็มถัดไปเพื่อลบอนันต์จึงให้ผลลัพธ์ที่แตกต่างเมื่อนำไปใช้กับจำนวนลบ


5

จาวาสคริหมายถึงNumberเป็นคู่แม่นยำตัวเลขลอย 64 บิต

Math.floor ทำงานร่วมกับสิ่งนี้ในใจ

การทำงานของ Bitwise ทำงานในจำนวนเต็ม32 บิตที่ลงนามแล้ว เลขจำนวนเต็ม 32 บิตที่ใช้บิตแรกเป็นตัวบ่งชี้เชิงลบและอีกบิตที่ 31 คือจำนวน ด้วยเหตุนี้จำนวนนาทีและสูงสุดที่อนุญาตให้ใช้หมายเลขที่เซ็นชื่อแบบ 32 บิตคือ -2,147,483,648 และ 2147483647 (0x7FFFFFFFF) ตามลำดับ

ดังนั้นเมื่อคุณกำลังทำคุณกำลังทำหลักคือ| 0 & 0xFFFFFFFFซึ่งหมายความว่าตัวเลขใด ๆ ที่แสดงเป็น 0x80000000 (2147483648) หรือมากกว่านั้นจะส่งคืนเป็นจำนวนลบ

ตัวอย่างเช่น:

 // Safe
 (2147483647.5918 & 0xFFFFFFFF) ===  2147483647
 (2147483647      & 0xFFFFFFFF) ===  2147483647
 (200.59082098    & 0xFFFFFFFF) ===  200
 (0X7FFFFFFF      & 0xFFFFFFFF) ===  0X7FFFFFFF

 // Unsafe
 (2147483648      & 0xFFFFFFFF) === -2147483648
 (-2147483649     & 0xFFFFFFFF) ===  2147483647
 (0x80000000      & 0xFFFFFFFF) === -2147483648
 (3000000000.5    & 0xFFFFFFFF) === -1294967296

ด้วย การใช้งานระดับบิตไม่ใช่ "ชั้น" พวกเขาตัด0ซึ่งเป็นเช่นเดียวกับว่าพวกเขารอบที่อยู่ใกล้ เมื่อคุณไปรอบ ๆ ไปยังหมายเลขลบMath.floorรอบลงในขณะที่เริ่มต้นบิตปัดเศษขึ้น

อย่างที่ฉันพูดไปก่อนหน้าMath.floorนี้ปลอดภัยกว่าเพราะทำงานกับตัวเลขลอยตัว 64 บิต Bitwise เร็วกว่าใช่ แต่ จำกัด ขอบเขตการลงชื่อ 32 บิต

เพื่อสรุป:

  • Bitwise 0 to 2147483647ทำงานเดียวกันถ้าคุณทำงานจากที่
  • ค่าที่เหมาะสมคือ 1 -2147483647 to 0เลขที่ออกถ้าคุณทำงานจากที่
  • Bitwise แตกต่างอย่างสิ้นเชิงสำหรับตัวเลขที่น้อยกว่าและมากกว่า-21474836482147483647

หากคุณจริงๆต้องการปรับแต่งประสิทธิภาพการทำงานและการใช้งานทั้ง:

function floor(n) {
    if (n >= 0 && n < 0x80000000) {
      return n & 0xFFFFFFFF;
    }
    if (n > -0x80000000 && n < 0) {
      return (n - 1) & 0xFFFFFFFF;
    }
    return Math.floor(n);
}

เพียงเพิ่มMath.truncงานเช่นการดำเนินการระดับบิต ดังนั้นคุณสามารถทำสิ่งนี้:

function trunc(n) {
    if (n > -0x80000000 && n < 0x80000000) {
      return n & 0xFFFFFFFF;
    }
    return Math.trunc(n);
}

5
  • รายละเอียดบอกว่ามันจะถูกแปลงเป็นจำนวนเต็ม:

    ให้ lnum เป็น ToInt32 (lval)

  • ประสิทธิภาพการทำงาน: สิ่งนี้ได้รับการทดสอบที่jsperfมาก่อน

หมายเหตุ: ลิงค์ที่เชื่อมโยงไปยังข้อมูลจำเพาะถูกลบแล้ว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.