+0 และ -0 แสดงพฤติกรรมที่แตกต่างกันสำหรับข้อมูล int และข้อมูลลอย


16

ฉันได้อ่านโพสต์เชิงลบและเชิงบวกศูนย์

เพื่อความเข้าใจของฉันรหัสต่อไปนี้ควรให้true และtrue เป็นผลลัพธ์

อย่างไรก็ตามมันจะให้falseและtrueเป็นผลลัพธ์

ฉันกำลังเปรียบเทียบศูนย์ลบกับศูนย์บวก

public class Test {
     public static void main(String[] args) {
            float f = 0;
            float f2 = -f;
            Float F = new Float(f);
            Float F1 = new Float(f2);
            System.out.println(F1.equals(F));

            int i = 0;
            int i2 = -i;
            Integer I = new Integer(i);
            Integer I1 = new Integer(i2);
            System.out.println(I1.equals(I));
      }
  }

ทำไมเราถึงมีพฤติกรรมที่แตกต่างกันสำหรับ 0 สำหรับIntegerและFloat?


11
หากคุณตรวจสอบ javadocs, docs.oracle.com/javase/8/docs/api/java/lang/…คำจำกัดความอนุญาตให้ตารางแฮชทำงานได้อย่างถูกต้อง นอกจากนี้ยังไม่มีจำนวนเต็ม -0
ด้าน

@ Matt ถ้า -0 ไม่ได้จำนวนเต็มแล้วมันควรจะได้รับการประเมินเป็นเท็จ ...
โจ๊ก

3
เมื่อคุณพูดว่า i2 = -i; i2 ใช้การแทนบิตที่แน่นอนของ i ไม่มีวิธีแยกแยะพวกมัน iและi2เหมือนกันทุกประการ จากนั้นเมื่อคุณสร้างใหม่Integerพวกเขาทั้งสองห่อค่าเดียวกันแน่นอน I1.equals(I)จะเป็นจริง
ด้าน

1
ลองint i = Integer.MIN_VALUE, i2 = -i;...
โฮลเกอร์

1
ยังไม่มีเหตุผลที่จะใช้newสำหรับประเภทเสื้อคลุมที่นี่ เพียงใช้เช่นInteger i = 0, i2 = -i; System.out.println(i.equals(i2)); Float f1 = 0f, f2 = -f1; System.out.println(f1.equals(f2));
Holger

คำตอบ:


19

Ints and floats เป็นสัตว์ที่แตกต่างกันใน Java Ints ถูกเข้ารหัสเป็นส่วนเติมเต็มของสองซึ่งมีค่า 0 เดียว ทุ่นลอยใช้IEEE 754 ( ตัวแปร 32 บิตสำหรับทุ่นและ64 บิตสำหรับคู่) IEEE 754 ค่อนข้างซับซ้อน แต่สำหรับจุดประสงค์ของคำตอบนี้คุณเพียงแค่ต้องรู้ว่ามันมีสามส่วนส่วนแรกคือบิตเครื่องหมาย นั่นหมายความว่าสำหรับทุ่นใด ๆ มีตัวแปรที่เป็นบวกและลบ¹ นั่นรวมถึง 0 ดังนั้นการลอยจะมีค่า "ศูนย์" สองค่าคือ +0 และ -0

นอกเหนือจากส่วนประกอบทั้งสองที่ ints ใช้ไม่ใช่วิธีเดียวที่จะเข้ารหัสจำนวนเต็มในวิทยาศาสตร์คอมพิวเตอร์ มีวิธีการอื่น ๆ เช่นส่วนเติมเต็มแต่มีลักษณะแปลก ๆ เช่นมีทั้ง +0 และ -0 เป็นค่าที่แตกต่างกัน ;-)

เมื่อคุณเปรียบเทียบการคำนวณเบื้องต้นแบบลอย (และดับเบิล) Java จะถือว่า +0 และ -0 เท่ากัน แต่เมื่อคุณกล่องพวกเขา, Java Float#equalsถือว่าพวกเขาต่างหากที่อธิบายไว้ใน วิธีนี้ช่วยให้วิธีการที่เท่าเทียมกันสอดคล้องกับhashCodeการใช้งานของพวกเขา(เช่นเดียวกับcompareTo) ซึ่งเพิ่งใช้บิตของการลอย (รวมถึงค่าที่ลงนามแล้ว) และผลักพวกเขาตามที่เป็นเข้า

พวกเขาอาจเลือกตัวเลือกอื่นสำหรับเท่ากับ / hashCode / comparTo แต่ไม่ได้ ฉันไม่แน่ใจว่าสิ่งที่ต้องพิจารณาในการออกแบบคืออะไร แต่อย่างน้อยหนึ่งเรื่อง, Float#equalsก็มักจะเปลี่ยนไปจากการลอยของดั้งเดิม==: ในวิทยาการ, NaN != NaNแต่สำหรับวัตถุทั้งหมดo.equals(o)จะต้องเป็นจริง หมายถึงว่าถ้าคุณมีFloat f = Float.NaNแล้วแม้ว่าf.equals(f)f.floatValue() != f.floatValue()


¹ค่า NaN (ไม่ใช่ตัวเลข) มีเครื่องหมายบิต แต่ไม่มีความหมายใด ๆ นอกจากการสั่งซื้อและ Java จะไม่สนใจ (แม้กระทั่งการสั่งซื้อ)


10

นี่เป็นหนึ่งในโฟลตเท่ากับข้อยกเว้น

มีข้อยกเว้นสองประการ:

หาก f1 หมายถึง + 0.0f ขณะที่ f2 หมายถึง -0.0fหรือกลับกันการทดสอบที่เท่ากันจะมีค่าเป็นเท็จ

เหตุผลที่อธิบายยัง:

คำจำกัดความนี้อนุญาตให้ตารางแฮชทำงานได้อย่างถูกต้อง

-0 และ 0 จะแทนต่างกันโดยใช้บิต 31 ของ Float:

บิต 31 (บิตที่ถูกเลือกโดยมาสก์ 0x80000000) แสดงถึงสัญลักษณ์ของตัวเลขทศนิยม

นี่ไม่ใช่กรณี Integer


คำถามคือทำไม คือกฎอย่างหนักและรวดเร็วนี้ว่าเราจะต้องอัด :(
โจ๊ก

@Joker เพิ่มเครื่องหมายคำพูดเพื่อให้ตารางแฮชทำงานได้อย่างถูกต้อง
user7294900

4
ชิ้นส่วนสำคัญที่คำตอบนี้ (และ javadoc) ไม่ได้กล่าวถึงคือความแตกต่างคือในแบบลอย +0 และ -0 เป็นค่าที่ต่างกัน - เทียบเท่า แต่ต่างกัน โดยพื้นฐานแล้วการลอยมีสามส่วนและส่วนแรกเป็นบิตเดียวที่บอกว่าการลอยเป็นบวกหรือลบ นั่นคือไม่ได้กรณีสำหรับ ints (เป็นตัวแทนใน Java) ซึ่งมีเพียงคนเดียวค่า 0
yshavit

@yshavit ขอบคุณคุณช่วยแบ่งปันคำตอบให้กับคุณได้ไหม
Joker

3
@Joker Bit 31 (บิตที่เลือกโดย mask 0x80000000) แสดงถึงเครื่องหมายของตัวเลขทศนิยม
user7294900

5

สำหรับจำนวนเต็มไม่มีความแตกต่างระหว่าง -0 และ 0 สำหรับจำนวนเต็มเพราะใช้การเป็นตัวแทนของTwos ตัวอย่างจำนวนเต็มของคุณiและi1เหมือนกันทุกประการ

สำหรับการลอยมีการแสดง -0 และค่าของมันเท่ากับ 0 แต่การแทนบิตแตกต่างกัน ดังนั้นโฟลตใหม่ (0f) และโฟลตใหม่ (-0f) จะมีการแสดงต่างกัน

คุณสามารถเห็นความแตกต่างในการเป็นตัวแทนบิต

System.out.println(Float.floatToIntBits(-0f) + ", " + Float.floatToIntBits(0f));

-2147483648, 0

และถ้าคุณปล่อยออกfไปเพื่อประกาศ-0fมันจะถูกใช้เป็นจำนวนเต็มและคุณจะไม่เห็นความแตกต่างในผลลัพธ์


และทว่าการลอยแบบดั้งเดิมดูเหมือนว่าจะทำงานได้ดี 0.0f == -0.0fกล่าวคือ java.lang.Floatดังนั้นพฤติกรรมที่แตกต่างเป็นเพียงใน
ivant

3
@ ผู้อยู่อาศัยตามมาตรฐาน IEEE754, "การดำเนินการเปรียบเทียบแบบปกติ, ถือว่า NaNs เป็นแบบไม่เรียงลำดับและเปรียบเทียบ −0 และ +0 เท่ากับ" en.m.wikipedia.org/wiki/IEEE_754
Andy Turner

@AndyTurner ใช่ผมเข้าใจว่า ฉันแค่ชี้ให้เห็นว่าใน Java มีความแตกต่างในพฤติกรรมระหว่างชนิดดั้งเดิมfloatซึ่งสอดคล้องกับ IEEE754 ในเรื่องนี้และjava.lang.Floatซึ่งไม่ได้ ดังนั้นความแตกต่างในการเป็นตัวแทนบิตไม่เพียงพอที่จะอธิบายเรื่องนี้
ivant
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.