Negative Zero (-0) ใน Jasmine

Chonlasith Jucksriporn
odds.team
Published in
3 min readMay 4, 2023

ลองเปรียบเทียบ 0 และ -0 บน Jasmine

คิดว่าโค้ดทดสอบบน jasmine ด้านล่างจะให้ผลลัพธ์เป็นอะไร

const negativeZero: number = -0;
const positiveZero: number = 0;

expect(positiveZero).toEqual(negativeZero);

ลองไปรันจริง ๆ ดูบน stackblitz กัน

ก็จะเห็น error message ที่น่าประหลาดใจว่า “Expected 0 to equal -0”

Social Responses

ลองเอาไปถามใน Facebook ดูเหมือนกัน

ก็ไม่ได้รับความกระจ่างเท่าไหร่ น่าจะเพราะโลกของ Javascript เป็นเหมือนกล่องดำ หรือจักรวาลคู่ขนานที่เข้าใจพฤติกรรมได้ยาก หรือไม่คู่ควรเสียเวลาไปทำความเข้าใจพฤติกรรมมัน

แต่เอาจริง ๆ เรายังต้องใช้มันหากินอยู่ ลองเปิดประตูเข้าไปดูสักหน่อยน่าจะดี

-0 (ลบศูนย์)! คืออะไร

หลังจากที่ไปลอง google ดู ก็เจอว่าใน Javascript มีการ implement data type ที่เป็น Number ตาม standard IEEE Standard for Floating-Point Arithmetic (IEEE 754) ซึ่งบอกไว้ว่าในข้อมูลประเภทที่เป็น floating point (นึกถึงตอนสมัยเรียนนะ ที่มันจะมี bit เรียงกันหน้าตาประมาณตามรูปข้างล่างนี้) มันจะมี bit ซ้ายสุด (sign bit) ที่ทำหน้าที่บอกว่าข้อมูลนี้เป็นค่าติดลบหรือไม่ ดังนั้น ค่าศูนย์ที่เป็นไปได้ มันเลยมีความเป็นไปได้ 2 แบบคือ 0 ที่มี sign bit เป็น 0 (หรือ positive zero) และ 0 ที่มี sign bit เป็น 1 (หรือ negative zero) นั่นเอง

ภาพ Negative Zero จาก https://en.wikipedia.org/wiki/Signed_zero

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

1/−0 = −∞ และ 1/+0 = +∞ โดยที่ผลหารจะเป็น undefined ในกรณี ±0/±0 และ ±∞/±∞ เท่านั้น

-0 บน Javascript

เรามาลองต่อกันไปอีกนิดด้วย Javascript บน NodeJS เผื่อจะเห็นอะไรมากขึ้น

ตัวอย่างข้างบนทำการทดสอบด้วย operator == และ === มาเปรียบเทียบความเท่ากัน หลาย ๆ คนน่าจะรู้จักอยู่แล้วว่า operator == จะทำการเปรียบเทียบความเท่ากันแบบ Loose equality นั่นคือ ค่าที่เอามาเปรียบเทียบกัน จะถูก convert type ให้เป็น type เดียวกันก่อน ถึงจะทำการ evaluate ความเท่ากัน ส่วน === จะทำการเปรียบเทียบแบบ Strict equality ซึ่งจะทำการเปรียบเทียบด้วยวิธีเหมือน == เพียงแค่จะไม่มีการ convert type ก่อนเท่านั้น ดังนั้น ถ้าค่าสองค่า ถึงเขียนเหมือนกัน แต่คนละ type ก็จะนับว่าแตกต่างกัน

ลองดูตัวอย่างด้านล่าง น่าจะเห็นภาพชัดขึ้น

จะเห็นได้ว่า ตอนเปรียบเทียบ 0 และ -0 ด้วย == และ === ต่างก็ให้ผลลัพธ์ true เหมือนกันอย่างที่ควรจะเป็น

แล้ว Jasmine ใช้วิธีเปรียบเทียบแบบไหน?

ถ้าลองเข้าไปดูใน Equality comparisons and sameness จะพบว่า จริง ๆ แล้ว การเปรียบเทียบความเท่ากันใน Javascript ไม่ได้มีแค่ == และ === แต่ยังมี Object.is() อีกด้วย แล้ว Jasmine ใช้วิธีนี้หรือ? ลองไปล้วงดู code ของ Jasmine ดูดีกว่าว่าเค้าเปรียบเทียบด้วยอะไร

พอไล่คำสั่ง toEqual ใน Jasmine ไปเรื่อย ๆ จนเจอจุดที่ใช้เปรียบเทียบกัน ก็เจอวิธีที่ Jasmine ใช้ในการเปรียบเทียบ

https://github.com/jasmine/jasmine/blob/4c13c2b00b971407eaf0722f78968887c3098430/src/core/matchers/matchersUtil.js

จะเห็นได้ว่า Jasmine ไม่ได้ใช้ Object.is() แต่จะใช้วิธีตามที่เขียนไว้ก่อนนี้คือ

1/−0 = −∞ และ 1/+0 = +∞ โดยที่ผลหารจะเป็น undefined ในกรณี ±0/±0 และ ±∞/±∞ เท่านั้น

ดังนั้น ตอนที่ Jasmine เปรียบเทียบค่า 0 กับ -0 เค้าตัดสินใจไปแล้วว่า มันคือคนละตัว ถึงแม้ value เท่ากัน แต่ไม่ identical เพราะเครื่องหมายมันต่างกัน

ในที่สุดก็เข้าใจเสียทีว่าทำไมมันถึงแสดง error แบบนั้นใน Jasmine

ทิ้งท้าย

เอาจริง ๆ ตัว Javascript เองก็มีเรื่องชวนประหลาดใจอยู่หลายอย่าง เกิดจากความหลวม ๆ ของมันเองนั่นแหละที่ทำให้หลาย ๆ เรื่องดูเป็นเรื่องชวนฉงน หลาย ๆ เรื่องกลายเป็นปมด้อยให้คนเขียนภาษาอื่นเอามาล้อกัน สำหรับคนที่ strict มาก ๆ น่าจะไม่ชอบ Javascript กันสักเท่าไหร่ ส่วนคนที่ไม่ strict อาจจะชอบเรื่องที่ท้าทายใน Javascript ก็เป็นไปได้ สำหรับผมแล้ว Javascript มันยังเป็น mainstream อยู่ ดังนั้นมันน่าจะไม่เสียหาย ถ้าเราจะไปทำความรู้จักพฤติกรรมมันให้มากขึ้นอีกหน่อย จะได้ใช้ชีวิตไปด้วยกันได้ยาว ๆ

--

--