แคสต์ที่ไม่ได้ลงชื่อเพื่อลงนามที่มีประสิทธิภาพหลีกเลี่ยงพฤติกรรมที่กำหนดการนำไปใช้งาน


94

ฉันต้องการกำหนดฟังก์ชันที่รับunsigned intอาร์กิวเมนต์เป็นและส่งคืนintโมดูโลที่สอดคล้องกัน UINT_MAX + 1 ให้กับอาร์กิวเมนต์

ความพยายามครั้งแรกอาจมีลักษณะดังนี้:

int unsigned_to_signed(unsigned n)
{
    return static_cast<int>(n);
}

แต่อย่างที่นักกฎหมายภาษาใด ๆ ทราบการคัดเลือกจากไม่ได้ลงนามเป็นเซ็นชื่อสำหรับค่าที่มีขนาดใหญ่กว่า INT_MAX นั้นถูกกำหนดให้ใช้งานได้

ฉันต้องการใช้สิ่งนี้ซึ่ง (ก) อาศัยเฉพาะพฤติกรรมที่กำหนดโดยข้อมูลจำเพาะเท่านั้น และ (b) รวบรวมเป็น no-op บนเครื่องที่ทันสมัยและปรับแต่งคอมไพเลอร์

สำหรับเครื่องจักรที่แปลกประหลาด ... หากไม่มีโมดูโลคอนดักเตอร์ int ที่ลงนาม UINT_MAX + 1 ไปยัง int ที่ไม่ได้ลงนามสมมติว่าฉันต้องการยกเว้น ถ้ามีมากกว่าหนึ่ง (ฉันไม่แน่ใจว่าเป็นไปได้) สมมติว่าฉันต้องการอันที่ใหญ่ที่สุด

ตกลงครั้งที่สอง:

int unsigned_to_signed(unsigned n)
{
    int int_n = static_cast<int>(n);

    if (n == static_cast<unsigned>(int_n))
        return int_n;

    // else do something long and complicated
}

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

ตอนนี้ความพยายามครั้งที่สองใกล้เคียงกับที่ฉันต้องการ แม้ว่าการแคสต์ถึงจะintถูกกำหนดให้ใช้งานสำหรับอินพุตบางตัว แต่การส่งกลับไปยังunsignedได้รับการรับรองตามมาตรฐานเพื่อรักษาค่าโมดูโล UINT_MAX + 1 ดังนั้นเงื่อนไขจะตรวจสอบสิ่งที่ฉันต้องการและมันจะรวมเข้ากับระบบใด ๆ ที่ฉันน่าจะพบ

อย่างไรก็ตาม ... ฉันยังคงแคสต์intโดยไม่ได้ตรวจสอบก่อนว่าจะเรียกใช้พฤติกรรมที่กำหนดการนำไปใช้หรือไม่ ในระบบสมมุติบางอย่างในปี 2050 มันสามารถทำใครรู้อะไรได้ สมมติว่าฉันต้องการหลีกเลี่ยงสิ่งนั้น

คำถาม: "ความพยายามครั้งที่สาม" ของฉันควรมีลักษณะอย่างไร

ในการสรุปฉันต้องการ:

  • ส่งจาก int ที่ไม่ได้ลงนามเป็น int ที่ลงชื่อ
  • เก็บค่า mod UINT_MAX + 1
  • เรียกใช้เฉพาะพฤติกรรมที่ได้รับคำสั่งมาตรฐาน
  • คอมไพล์เป็น no-op บนเครื่อง twos-complement ทั่วไปด้วยการปรับแต่งคอมไพเลอร์

[อัปเดต]

ให้ฉันยกตัวอย่างเพื่อแสดงว่าเหตุใดจึงไม่ใช่คำถามที่ไม่สำคัญ

พิจารณาการใช้งาน C ++ สมมุติด้วยคุณสมบัติต่อไปนี้:

  • sizeof(int) เท่ากับ 4
  • sizeof(unsigned) เท่ากับ 4
  • INT_MAX เท่ากับ 32767
  • INT_MINเท่ากับ -2 32 + 32768
  • UINT_MAXเท่ากับ 2 32 - 1
  • เลขคณิตบนintคือโมดูโล 2 32 (เข้าสู่ช่วงINT_MINถึงINT_MAX)
  • std::numeric_limits<int>::is_modulo เป็นความจริง
  • หล่อที่ไม่มีการลงชื่อnเพื่อรักษา int ค่าสำหรับ 0 <= n <= 32767 และอัตราผลตอบแทนเป็นศูนย์มิฉะนั้น

ในการใช้งานสมมุตินี้มีintค่าหนึ่งที่สอดคล้องกัน (mod UINT_MAX + 1) สำหรับแต่ละunsignedค่า ดังนั้นคำถามของฉันจะถูกกำหนดไว้อย่างชัดเจน

ฉันอ้างว่าการใช้งาน C ++ สมมุตินี้สอดคล้องกับข้อกำหนด C ++ 98, C ++ 03 และ C ++ 11 อย่างสมบูรณ์ ฉันยอมรับว่าฉันไม่ได้จำทุกคำของพวกเขาทั้งหมด ... แต่ฉันเชื่อว่าฉันได้อ่านส่วนที่เกี่ยวข้องอย่างละเอียดแล้ว ดังนั้นหากคุณต้องการให้ฉันยอมรับคำตอบของคุณคุณต้อง (a) อ้างอิงข้อมูลจำเพาะที่กำหนดกฎเกณฑ์การใช้งานสมมุติฐานนี้หรือ (b) จัดการอย่างถูกต้อง

อันที่จริงคำตอบที่ถูกต้องจัดการทุกการดำเนินงานสมมุติได้รับอนุญาตตามมาตรฐาน นั่นคือสิ่งที่ "เรียกใช้เฉพาะพฤติกรรมที่ได้รับคำสั่งมาตรฐาน" ตามคำจำกัดความ

อนึ่งโปรดทราบว่าที่std::numeric_limits<int>::is_moduloนี่ไม่มีประโยชน์ด้วยเหตุผลหลายประการ ประการหนึ่งอาจเป็นได้trueแม้ว่าการร่ายที่ไม่ได้ลงชื่อจะไม่สามารถใช้ได้กับค่าที่ไม่ได้ลงชื่อจำนวนมาก สำหรับอีกระบบหนึ่งอาจเป็นได้trueแม้กระทั่งในระบบส่วนเติมเต็มหรือขนาดเครื่องหมายถ้าเลขคณิตเป็นเพียงโมดูโลช่วงจำนวนเต็มทั้งหมด และอื่น ๆ ถ้าคำตอบของคุณขึ้นอยู่กับis_moduloว่ามันผิด

[อัปเดต 2]

คำตอบของ hvdสอนฉันบางอย่าง: การนำ C ++ สมมุติของฉันไปใช้กับจำนวนเต็มไม่ได้รับอนุญาตจาก C สมัยใหม่มาตรฐาน C99 และ C11 มีความเฉพาะเจาะจงมากเกี่ยวกับการแสดงจำนวนเต็มที่ลงนาม แท้จริงพวกเขาอนุญาตให้ใช้เฉพาะส่วนเสริมสองส่วนส่วนเติมเต็มและขนาดเครื่องหมาย (หัวข้อ 6.2.6.2 ย่อหน้า (2);)

แต่ C ++ ไม่ใช่ C ตามที่ปรากฎข้อเท็จจริงนี้อยู่ในหัวใจหลักของคำถามของฉัน

มาตรฐาน C ++ 98 ดั้งเดิมอ้างอิงจาก C89 ที่เก่ากว่ามากซึ่งระบุว่า (หัวข้อ 3.1.2.5):

สำหรับประเภทจำนวนเต็มที่ลงนามแต่ละประเภทจะมีประเภทจำนวนเต็มไม่ได้ลงนามที่ตรงกัน (แต่ต่างกัน) (กำหนดด้วยคีย์เวิร์ดที่ไม่ได้ลงนาม) ซึ่งใช้พื้นที่เก็บข้อมูลเท่ากัน (รวมถึงข้อมูลเครื่องหมาย) และมีข้อกำหนดการจัดตำแหน่งเดียวกัน ช่วงของค่าที่ไม่เป็นค่าลบของประเภทจำนวนเต็มที่ลงนามเป็นช่วงย่อยของประเภทจำนวนเต็มที่ไม่ได้ลงชื่อที่เกี่ยวข้องและการแทนค่าเดียวกันในแต่ละประเภทจะเหมือนกัน

C89 ไม่ได้บอกอะไรเกี่ยวกับการมีเพียงบิตเครื่องหมายเดียวหรืออนุญาตให้ใช้เฉพาะ twos-complement / ones-complement / sign-magnitude

มาตรฐาน C ++ 98 ใช้ภาษานี้เกือบทุกคำ (มาตรา 3.9.1 ย่อหน้า (3)):

สำหรับประเภทจำนวนเต็มที่ลงนามแต่ละประเภทจะมีประเภทจำนวนเต็มไม่ได้ลงนามที่ตรงกัน (แต่ต่างกัน) : " unsigned char", " unsigned short int", " unsigned int" และ " unsigned long int" ซึ่งแต่ละประเภทใช้พื้นที่เก็บข้อมูลเท่ากันและมีข้อกำหนดในการจัดตำแหน่งเดียวกัน (3.9 ) เป็นประเภทจำนวนเต็มลงนามที่เกี่ยวข้อง นั่นคือประเภทจำนวนเต็มที่ลงนามแต่ละประเภทจะมีการแทนอ็อบเจ็กต์เหมือนกับประเภทจำนวนเต็มที่ไม่ได้ลงชื่อ ช่วงของค่าที่ไม่เป็นค่าลบของประเภทจำนวนเต็มที่ลงนามเป็นช่วงย่อยของประเภทจำนวนเต็มไม่ได้ลงนามที่เกี่ยวข้องและการแทนค่าของประเภทที่ลงนาม / ไม่ได้ลงนามแต่ละประเภทจะต้องเหมือนกัน

มาตรฐาน C ++ 03 ใช้ภาษาที่เหมือนกันเป็นหลักเช่นเดียวกับ C ++ 11

ไม่มีข้อมูลจำเพาะ C ++ มาตรฐานที่ จำกัด การแสดงจำนวนเต็มที่ลงนามกับข้อมูลจำเพาะ C ใด ๆ เท่าที่ฉันสามารถบอกได้ และไม่มีอะไรบังคับบิตเซ็นเดียวหรืออะไรก็ได้ สิ่งที่กล่าวมาก็คือจำนวนเต็มที่ลงนามที่ไม่ใช่ค่าลบจะต้องเป็นช่วงย่อยของจำนวนเต็มที่ไม่ได้ลงนาม

ดังนั้นอีกครั้งฉันอ้างว่าอนุญาตให้ใช้ INT_MAX = 32767 กับ INT_MIN = -2 32 +32768 หากคำตอบของคุณถือว่าเป็นอย่างอื่นมันไม่ถูกต้องเว้นแต่คุณจะอ้างถึงมาตรฐานC ++ ที่พิสูจน์ว่าฉันผิด


@SteveJessop: อันที่จริงฉันได้ระบุสิ่งที่ฉันต้องการในกรณีนั้นอย่างแน่นอน: "ถ้าไม่มีโมดูโลที่สอดคล้องกัน int ที่ลงนาม UINT_MAX + 1 ไปยัง int ที่ไม่ได้ลงนามสมมติว่าฉันต้องการยกเว้น" นั่นคือฉันต้องการ int ที่ลงชื่อ "ถูกต้อง" หากมีอยู่ หากไม่มีอยู่ - อย่างที่อาจเกิดขึ้นในกรณีของเช่น padding bits หรือ one-complement representations - ฉันต้องการตรวจจับสิ่งนั้นและจัดการมันสำหรับการเรียกใช้ cast นั้น ๆ
Nemo

ขออภัยไม่แน่ใจว่าฉันพลาดไปได้อย่างไร
Steve Jessop

แต่ฉันคิดว่าในการใช้งานที่ยุ่งยากโดยสมมุติของคุณintต้องการอย่างน้อย 33 บิตเพื่อแสดงถึงมัน ฉันรู้ว่ามันเป็นเพียงเชิงอรรถดังนั้นคุณสามารถโต้แย้งได้ว่ามันไม่ใช่เรื่องปกติ แต่ฉันคิดว่าเชิงอรรถ 49 ใน C ++ 11 นั้นตั้งใจให้เป็นจริง (เนื่องจากเป็นคำจำกัดความของคำที่ใช้ในมาตรฐาน) และไม่ขัดแย้ง สิ่งที่ระบุไว้อย่างชัดเจนในข้อความเชิงบรรทัดฐาน ดังนั้นค่าเชิงลบทั้งหมดจะต้องแสดงด้วยรูปแบบบิตซึ่งตั้งค่าบิตสูงสุดดังนั้นคุณจึงไม่สามารถอัด2^32 - 32768เป็น 32 บิตได้ ไม่ใช่ว่าการโต้แย้งของคุณขึ้นอยู่กับขนาดของint.
Steve Jessop

และเกี่ยวกับการแก้ไขของคุณในคำตอบของ hvd ฉันคิดว่าคุณตีความโน้ต 49 ผิดคุณบอกว่าขนาดของเครื่องหมายเป็นสิ่งต้องห้าม แต่ก็ไม่ใช่ คุณอ่านแล้วว่า: "ค่าที่แสดงโดยบิตต่อเนื่องเป็นส่วนเสริมเริ่มต้นด้วย 1 และ (คูณด้วยกำลังอินทิกรัลที่ต่อเนื่องกันของ 2 ยกเว้นบิตที่มีตำแหน่งสูงสุด)" ฉันเชื่อว่ามันควรจะอ่าน "ค่าที่แสดงด้วยบิตต่อเนื่องกัน (เป็นส่วนเสริมเริ่มต้นด้วย 1 และคูณด้วยกำลังอินทิกรัลต่อเนื่องเป็น 2) ยกเว้นบิตที่มีตำแหน่งสูงสุด" นั่นคือการเดิมพันทั้งหมดจะถูกปิดหากตั้งค่าบิตสูง
Steve Jessop

@SteveJessop: การตีความของคุณอาจถูกต้อง ถ้าเป็นเช่นนั้นมันจะตัดทอนสมมุติฐานของฉันออกไป ... แต่มันยังแนะนำความเป็นไปได้มากมายอย่างแท้จริงทำให้คำถามนี้ตอบยากมาก นี่ดูเหมือนบั๊กในสเป็คสำหรับฉันจริงๆ (เห็นได้ชัดว่าคณะกรรมการ C คิดอย่างนั้นและแก้ไขอย่างน่ากลัวใน C99 ฉันสงสัยว่าทำไม C ++ 11 ถึงไม่ใช้แนวทางของพวกเขา)
Nemo

คำตอบ:


70

ขยายคำตอบของ user71404:

int f(unsigned x)
{
    if (x <= INT_MAX)
        return static_cast<int>(x);

    if (x >= INT_MIN)
        return static_cast<int>(x - INT_MIN) + INT_MIN;

    throw x; // Or whatever else you like
}

หากx >= INT_MIN(คำนึงถึงกฎการส่งเสริมการขายINT_MINถูกแปลงเป็นunsigned) x - INT_MIN <= INT_MAXดังนั้นสิ่งนี้จะไม่มีทางล้น

หากไม่ชัดเจนให้ดูที่การอ้างสิทธิ์ "If x >= -4u, then x + 4 <= 3." และโปรดทราบว่าINT_MAXค่าทางคณิตศาสตร์อย่างน้อยคือ -INT_MIN - 1

ในระบบที่ใช้กันทั่วไปโดย!(x <= INT_MAX)นัยx >= INT_MINแล้วเครื่องมือเพิ่มประสิทธิภาพควรจะสามารถ (และในระบบของฉันสามารถ) เพื่อลบการตรวจสอบครั้งที่สองตรวจสอบว่าreturnคำสั่งทั้งสองสามารถรวบรวมเป็นรหัสเดียวกันและลบการตรวจสอบครั้งแรกด้วย สร้างรายการประกอบ:

__Z1fj:
LFB6:
    .cfi_startproc
    movl    4(%esp), %eax
    ret
    .cfi_endproc

การใช้สมมุติฐานในคำถามของคุณ:

  • INT_MAX เท่ากับ 32767
  • INT_MIN เท่ากับ -2 32 + 32768

เป็นไปไม่ได้จึงไม่จำเป็นต้องได้รับการพิจารณาเป็นพิเศษ INT_MINจะเท่ากับอย่างใดอย่างหนึ่งหรือ-INT_MAX -INT_MAX - 1สิ่งนี้ตามมาจากการแทนค่าประเภทจำนวนเต็มของ C (6.2.6.2) ซึ่งกำหนดให้nบิตเป็นบิตค่าหนึ่งบิตเป็นบิตเครื่องหมายและอนุญาตให้มีการแทนกับดักเพียงรายการเดียว (ไม่รวมการแทนค่าที่ไม่ถูกต้องเนื่องจากบิตช่องว่างภายใน) คือหนึ่งที่อาจจะเป็นตัวแทนของศูนย์ -INT_MAX - 1/ C ++ ไม่อนุญาตการแสดงจำนวนเต็มนอกเหนือจากที่ C อนุญาต

อัปเดต : คอมไพเลอร์ของ Microsoft ไม่สังเกตเห็นสิ่งนั้นx > 10และx >= 11ทดสอบสิ่งเดียวกัน จะสร้างรหัสที่ต้องการx >= INT_MINก็ต่อเมื่อถูกแทนที่ด้วยx > INT_MIN - 1uซึ่งสามารถตรวจจับได้ว่าเป็นการปฏิเสธของx <= INT_MAX(บนแพลตฟอร์มนี้)

[อัปเดตจากผู้ถาม (Nemo) อธิบายรายละเอียดเกี่ยวกับการสนทนาของเราด้านล่าง]

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

เริ่มต้นด้วย C ++ 11 ส่วน 18.3.3:

ตาราง 31 <climits>อธิบายส่วนหัว

...

<limits.h>เนื้อหาเป็นเช่นเดียวกับส่วนหัวของห้องสมุดมาตรฐาน C

ในที่นี้ "Standard C" หมายถึง C99 ซึ่งข้อกำหนดนี้ จำกัด การแสดงจำนวนเต็มที่ลงนามอย่างรุนแรง พวกมันเหมือนกับจำนวนเต็มที่ไม่ได้ลงนาม แต่มีบิตหนึ่งที่ทุ่มเทให้กับ "เครื่องหมาย" และบิตที่เป็นศูนย์หรือมากกว่าสำหรับ "ช่องว่างภายใน" บิตช่องว่างภายในไม่ได้นำไปสู่ค่าของจำนวนเต็มและบิตเครื่องหมายก่อให้เกิดเป็นส่วนประกอบสองส่วนส่วนเติมเต็มหรือขนาดเครื่องหมายเท่านั้น

เนื่องจาก C ++ 11 สืบทอด<climits>มาโครจาก C99 INT_MIN จึงเป็น -INT_MAX หรือ -INT_MAX-1 และรับประกันว่าโค้ดของ hvd จะทำงาน (โปรดทราบว่าเนื่องจากการเพิ่มช่องว่าง INT_MAX อาจน้อยกว่า UINT_MAX / 2 มาก ... แต่ด้วยวิธีการใช้งานแบบหล่อที่ไม่ได้ลงชื่อ -> คำตอบนี้จึงจัดการได้ดี)

C ++ 03 / C ++ 98 นั้นยากกว่า ใช้ถ้อยคำเดียวกันในการสืบทอด<climits>จาก "Standard C" แต่ตอนนี้ "Standard C" หมายถึง C89 / C90

ทั้งหมดนี้ - C ++ 98, C ++ 03, C89 / C90 - มีข้อความที่ฉันให้ไว้ในคำถามของฉัน แต่รวมถึงสิ่งนี้ด้วย (C ++ 03 ส่วน 3.9.1 ย่อหน้า 7):

การแสดงประเภทอินทิกรัลต้องกำหนดค่าโดยใช้ระบบเลขฐานสองที่บริสุทธิ์ (44) [ ตัวอย่าง : มาตรฐานสากลนี้อนุญาตให้มีส่วนเติมเต็ม 2 ส่วนเติมเต็ม 1 และการแสดงขนาดที่มีลายเซ็นสำหรับประเภทอินทิกรัล]

เชิงอรรถ (44) กำหนด "ระบบเลขฐานสองบริสุทธิ์":

การแสดงตำแหน่งสำหรับจำนวนเต็มที่ใช้เลขฐานสอง 0 และ 1 ซึ่งค่าที่แสดงโดยบิตต่อเนื่องเป็นส่วนเสริมเริ่มต้นด้วย 1 และคูณด้วยกำลังอินทิกรัลที่ต่อเนื่องกันเป็น 2 ยกเว้นบิตที่มีตำแหน่งสูงสุด

สิ่งที่น่าสนใจเกี่ยวกับคำนี้คือมันขัดแย้งในตัวเองเนื่องจากคำจำกัดความของ "ระบบเลขฐานสองบริสุทธิ์" ไม่อนุญาตให้มีการแสดงเครื่องหมาย / ขนาด! อนุญาตให้บิตสูงมีค่า -2 n-1 (ส่วนเติมเต็ม twos) หรือ - (2 n-1 -1) (ส่วนเติมเต็ม) แต่ไม่มีค่าสำหรับบิตสูงที่ให้ผลลัพธ์เป็นเครื่องหมาย / ขนาด

อย่างไรก็ตาม "การใช้งานสมมุติ" ของฉันไม่ถือว่าเป็น "ไบนารีบริสุทธิ์" ภายใต้คำจำกัดความนี้ดังนั้นจึงถูกตัดออก

อย่างไรก็ตามความจริงที่ว่าบิตสูงเป็นสิ่งพิเศษหมายความว่าเราสามารถจินตนาการได้ว่ามันมีส่วนทำให้เกิดมูลค่าใด ๆ เลย: ค่าบวกเล็กน้อยค่าบวกจำนวนมากค่าลบเล็กน้อยหรือค่าลบจำนวนมาก (ถ้าบิตเครื่องหมายสามารถมีส่วนร่วม - (2 n-1 -1) ทำไมไม่ - (2 n-1 -2)? ฯลฯ )

ลองนึกภาพการแสดงจำนวนเต็มที่มีลายเซ็นซึ่งกำหนดค่าแปลกประหลาดให้กับบิต "เครื่องหมาย"

ค่าบวกเล็กน้อยสำหรับบิตเครื่องหมายจะส่งผลให้เกิดช่วงบวกสำหรับint(อาจใหญ่เท่าunsigned) และโค้ดของ hvd จัดการได้ดี

ค่าบวกอย่างมากสำหรับบิตเครื่องหมายจะส่งผลให้intมีค่าสูงสุดที่มากกว่าunsignedซึ่งเป็นสิ่งต้องห้าม

ค่าลบจำนวนมากสำหรับบิตเครื่องหมายจะส่งผลให้intแสดงช่วงของค่าที่ไม่ติดกันและคำอื่น ๆ ในกฎข้อมูลจำเพาะที่ออก

สุดท้ายแล้วบิตเครื่องหมายที่ก่อให้เกิดปริมาณเชิงลบเล็กน้อยเป็นอย่างไร? เราขอ 1 ใน "บิตเครื่องหมาย" บอกว่า -37 ของค่า int ได้ไหม ดังนั้น INT_MAX จะเป็น (พูด) 2 31 -1 และ INT_MIN จะเป็น -37?

สิ่งนี้จะส่งผลให้ตัวเลขบางตัวมีการแทนค่าสองค่า ... แต่ส่วนเสริมจะให้การแทนค่าสองค่าเป็นศูนย์และอนุญาตตาม "ตัวอย่าง" สเป็คไม่มีที่ไหนบอกว่าศูนย์เป็นจำนวนเต็มเดียวที่อาจมีสองตัวแทน ดังนั้นฉันคิดว่าสมมุติฐานใหม่นี้ได้รับอนุญาตตามข้อกำหนด

อันที่จริงค่าเชิงลบใด ๆ ตั้งแต่ -1 ลงไปจนถึง-INT_MAX-1ดูเหมือนจะอนุญาตให้เป็นค่าสำหรับ "บิตเครื่องหมาย" แต่จะไม่มีอะไรเล็กลง (เพื่อมิให้ช่วงนั้นไม่ติดกัน) กล่าวอีกนัยหนึ่งINT_MINอาจเป็นอะไรก็ได้ตั้งแต่-INT_MAX-1ถึง -1

ตอนนี้เดาอะไร? สำหรับนักแสดงที่สองในรหัส HVD เพื่อหลีกเลี่ยงพฤติกรรมการดำเนินงานที่กำหนดไว้เราก็ต้องน้อยกว่าหรือเท่ากับx - (unsigned)INT_MIN INT_MAXเราเพียงแค่แสดงให้เห็นเป็นอย่างน้อยINT_MIN -INT_MAX-1เห็นได้ชัดว่าเป็นที่มากที่สุดx หล่อจำนวนลบที่จะได้รับการรับรองเป็นเช่นเดียวกับการเพิ่มUINT_MAX UINT_MAX+1รวมทั้งหมดเข้าด้วยกัน:

x - (unsigned)INT_MIN <= INT_MAX

ถ้าและต่อเมื่อ

UINT_MAX - (INT_MIN + UINT_MAX + 1) <= INT_MAX
-INT_MIN-1 <= INT_MAX
-INT_MIN <= INT_MAX+1
INT_MIN >= -INT_MAX-1

สุดท้ายนี้คือสิ่งที่เราเพิ่งแสดงให้เห็นดังนั้นแม้ในกรณีที่ไม่เหมาะสมนี้โค้ดก็ใช้งานได้จริง

นั่นทำให้หมดความเป็นไปได้ทั้งหมดจึงยุติแบบฝึกหัดเชิงวิชาการนี้

บรรทัดล่าง: มีพฤติกรรมบางอย่างที่ไม่ได้ระบุไว้อย่างจริงจังสำหรับจำนวนเต็มที่ลงนามใน C89 / C90 ที่รับช่วงโดย C ++ 98 / C ++ 03 ได้รับการแก้ไขใน C99 และ C ++ 11 สืบทอดการแก้ไขทางอ้อมโดยการรวม<limits.h>จาก C99 แต่ถึงแม้ C ++ 11 จะยังคงใช้ถ้อยคำ "การแสดงไบนารีบริสุทธิ์" ที่ขัดแย้งกันเอง ...


อัปเดตคำถามแล้ว ฉันลงคะแนนคำตอบนี้ (ตอนนี้) เพื่อกีดกันคนอื่น ... ฉันจะยกเลิกการลงคะแนนในภายหลังเพราะคำตอบน่าสนใจ (ถูกต้องสำหรับ C แต่ผิดสำหรับ C ++ ฉันคิดว่า)
Nemo

@Nemo มาตรฐาน C ใช้กับ C ++ ในกรณีนี้ อย่างน้อยที่สุดค่าใน<limits.h>ถูกกำหนดไว้ในมาตรฐาน C ++ ว่ามีความหมายเช่นเดียวกับในมาตรฐาน C ดังนั้นข้อกำหนดทั้งหมดของ C สำหรับINT_MINและINT_MAXสืบทอดใน C ++ คุณถูกต้องที่ C ++ 03 อ้างถึง C90 และ C90 คลุมเครือเกี่ยวกับการแสดงจำนวนเต็มที่อนุญาต แต่การเปลี่ยนแปลง C99 (รับช่วงอย่างน้อย<limits.h>โดย C ++ 11 หวังว่าจะเป็นวิธีที่ตรงไปตรงมามากขึ้นด้วย) เพื่อ จำกัด ให้ ทั้งสามเป็นหนึ่งเดียวที่ประมวลผลการปฏิบัติที่มีอยู่: ไม่มีการนำไปใช้อื่น ๆ

ฉันยอมรับว่าความหมายของINT_MINฯลฯ นั้นสืบทอดมาจาก C. แต่ไม่ได้หมายความว่าค่าต่างๆ (อันที่จริงมันเป็นไปได้อย่างไรเนื่องจากการใช้งานทุกครั้งแตกต่างกัน) การอนุมานของคุณที่INT_MINอยู่ใน 1 ของ-INT_MAXขึ้นอยู่กับถ้อยคำที่ไม่ปรากฏในข้อมูลจำเพาะ C ++ ใด ๆ ดังนั้นในขณะที่ C ++ สืบทอดความหมายเชิงความหมายของมาโคร แต่ข้อมูลจำเพาะจะไม่ระบุ (หรือสืบทอด) ถ้อยคำที่สนับสนุนการอนุมานของคุณ สิ่งนี้ดูเหมือนจะเป็นการกำกับดูแลในข้อมูลจำเพาะ C ++ ที่ป้องกันไม่ให้มีการแคสต์ที่ไม่ได้ลงนามเพื่อลงนามอย่างมีประสิทธิภาพ
Nemo

@Nemo หากคุณ (อาจจะถูกต้อง) อ้างว่า C ++ อนุญาตให้มีการแสดงอื่น ๆ จากนั้นในการใช้งานดังกล่าวฉันอ้างว่าINT_MIN ไม่จำเป็นต้องเป็นค่าที่แสดงได้น้อยที่สุดของประเภทintเพราะเท่าที่เกี่ยวข้องกับ C หากประเภทไม่ ตรงกับข้อกำหนดของintมาตรฐาน C ไม่สามารถครอบคลุมถึงการนำไปใช้ไม่ว่าด้วยวิธีใดก็ตามและมาตรฐาน C ++ ไม่ได้ให้คำจำกัดความใด ๆ นอกเหนือจาก "สิ่งที่มาตรฐาน C กล่าว" ฉันจะตรวจสอบว่ามีคำอธิบายที่ตรงไปตรงมามากกว่านี้หรือไม่

7
สวยมาก ไม่รู้ว่าตอนนั้นฉันพลาดคำถามนี้ไปได้อย่างไร
Lightness Races ใน Orbit

17

รหัสนี้อาศัยเฉพาะพฤติกรรมซึ่งได้รับคำสั่งจากข้อมูลจำเพาะดังนั้นข้อกำหนด (a) จึงเป็นที่พอใจได้ง่าย:

int unsigned_to_signed(unsigned n)
{
  int result = INT_MAX;

  if (n > INT_MAX && n < INT_MIN)
    throw runtime_error("no signed int for this number");

  for (unsigned i = INT_MAX; i != n; --i)
    --result;

  return result;
}

มันไม่ง่ายเลยกับข้อกำหนด (b) สิ่งนี้รวบรวมเป็น no-op ด้วย gcc 4.6.3 (-Os, -O2, -O3) และ clang 3.0 (-Os, -O, -O2, -O3) Intel 12.1.0 ปฏิเสธที่จะเพิ่มประสิทธิภาพนี้ และฉันไม่มีข้อมูลเกี่ยวกับ Visual C.


1
โอเคนี่สุดยอดมาก ฉันหวังว่าฉันจะแบ่งค่าหัว 80:20 ได้ ... ฉันสงสัยว่าเหตุผลของคอมไพเลอร์จะดำเนินต่อไป: หากลูปไม่สิ้นสุดก็จะresultล้น; จำนวนเต็มล้นไม่ได้กำหนดไว้ ดังนั้นลูปจึงสิ้นสุดลง ดังนั้นi == nเมื่อสิ้นสุด; จึงเท่ากับresult nฉันยังคงต้องชอบคำตอบของ hvd (สำหรับพฤติกรรมที่ไม่ใช่พยาธิวิทยาในคอมไพเลอร์ที่ฉลาดน้อยกว่า) แต่สิ่งนี้สมควรได้รับการโหวตมากขึ้น
Nemo

1
Unsigned ถูกกำหนดให้เป็นโมดูโล นอกจากนี้ลูปยังรับประกันว่าจะสิ้นสุดลงเนื่องจากnเป็นค่าที่ไม่ได้ลงชื่อและiในที่สุดจะต้องถึงค่าที่ไม่ได้ลงชื่อ
idupree

7

คำตอบเดิมแก้ปัญหาเฉพาะสำหรับ=>unsigned intจะเป็นอย่างไรหากเราต้องการแก้ไขปัญหาทั่วไปของ "บางประเภทที่ไม่ได้ลงชื่อ" เป็นประเภทที่มีการลงนามที่เกี่ยวข้อง นอกจากนี้คำตอบเดิมนั้นยอดเยี่ยมในการอ้างถึงส่วนต่างๆของมาตรฐานและการวิเคราะห์บางกรณี แต่ก็ไม่ได้ช่วยให้ฉันรู้สึกได้ว่าทำไมมันถึงได้ผลดังนั้นคำตอบนี้จะพยายามให้แนวคิดที่ชัดเจน คำตอบนี้จะพยายามช่วยอธิบาย "ทำไม" และใช้คุณลักษณะ C ++ ที่ทันสมัยเพื่อพยายามทำให้โค้ดง่ายขึ้น

C ++ 20 คำตอบ

ปัญหาได้ง่ายขึ้นอย่างมากกับP0907: จำนวนเต็มที่ลงนามเป็นส่วนเสริมของสองและP1236 ถ้อยคำสุดท้ายที่ได้รับการโหวตให้เป็นมาตรฐาน C ++ 20 ตอนนี้คำตอบนั้นง่ายที่สุด:

template<std::unsigned_integral T>
constexpr auto cast_to_signed_integer(T const value) {
    return static_cast<std::make_signed_t<T>>(value);
}

แค่นั้นแหละ. ในที่สุด A static_cast(หรือ C-style cast) ได้รับการรับรองว่าจะทำสิ่งที่คุณต้องการสำหรับคำถามนี้และสิ่งที่โปรแกรมเมอร์หลายคนคิดว่ามันทำมาตลอด

C ++ 17 คำตอบ

ใน C ++ 17 สิ่งต่างๆซับซ้อนกว่านี้มาก เราต้องจัดการกับการแทนค่าจำนวนเต็มที่เป็นไปได้สามแบบ (ส่วนเติมเต็มสองส่วนส่วนเติมเต็มและขนาดเครื่องหมาย) แม้ในกรณีที่เรารู้ว่ามันต้องเป็นส่วนเสริมของสองค่าเนื่องจากเราตรวจสอบช่วงของค่าที่เป็นไปได้การแปลงค่าที่อยู่นอกช่วงของจำนวนเต็มที่ลงนามเป็นจำนวนเต็มที่ลงชื่อนั้นยังคงให้ผลลัพธ์ที่กำหนดให้เราใช้งานได้ เราต้องใช้กลเม็ดเหมือนที่เคยเห็นในคำตอบอื่น ๆ

ขั้นแรกนี่คือรหัสสำหรับวิธีแก้ปัญหาโดยทั่วไป:

template<typename T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
constexpr auto cast_to_signed_integer(T const value) {
    using result = std::make_signed_t<T>;
    using result_limits = std::numeric_limits<result>;
    if constexpr (result_limits::min() + 1 != -result_limits::max()) {
        if (value == static_cast<T>(result_limits::max()) + 1) {
            throw std::runtime_error("Cannot convert the maximum possible unsigned to a signed value on this system");
        }
    }
    if (value <= result_limits::max()) {
        return static_cast<result>(value);
    } else {
        using promoted_unsigned = std::conditional_t<sizeof(T) <= sizeof(unsigned), unsigned, T>;
        using promoted_signed = std::make_signed_t<promoted_unsigned>;
        constexpr auto shift_by_window = [](auto x) {
            // static_cast to avoid conversion warning
            return x - static_cast<decltype(x)>(result_limits::max()) - 1;
        };
        return static_cast<result>(
            shift_by_window( // shift values from common range to negative range
                static_cast<promoted_signed>(
                    shift_by_window( // shift large values into common range
                        static_cast<promoted_unsigned>(value) // cast to avoid promotion to int
                    )
                )
            )
        );
    }
}

สิ่งนี้มีการร่ายมากกว่าคำตอบที่ยอมรับเล็กน้อยและนั่นคือเพื่อให้แน่ใจว่าไม่มีคำเตือนที่ไม่ตรงกัน / ไม่ได้ลงนามจากคอมไพเลอร์ของคุณและเพื่อจัดการกับกฎการส่งเสริมจำนวนเต็มอย่างเหมาะสม

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

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

พื้นฐานแนวคิด: เส้นจำนวน

ประการแรกwindowแนวคิดนี้คืออะไร? พิจารณาบรรทัดตัวเลขต่อไปนี้:

   |   signed   |
<.........................>
          |  unsigned  |

ปรากฎว่าสำหรับจำนวนเต็มสองจำนวนคุณสามารถแบ่งส่วนย่อยของเส้นจำนวนที่สามารถเข้าถึงได้โดยประเภทใดประเภทหนึ่งออกเป็นสามประเภทที่มีขนาดเท่ากัน:

- => signed only
= => both
+ => unsigned only

<..-------=======+++++++..>

สิ่งนี้สามารถพิสูจน์ได้ง่ายโดยพิจารณาจากการเป็นตัวแทน จำนวนเต็มที่ไม่ได้ลงนามเริ่มต้นที่0และใช้บิตทั้งหมดเพื่อเพิ่มค่าในพาวเวอร์ของ 2 จำนวนเต็มที่ลงนามจะเหมือนกันทุกประการสำหรับบิตทั้งหมดยกเว้นบิตเครื่องหมายซึ่งมีค่า-(2^position)แทน 2^positionซึ่งหมายความว่าสำหรับn - 1บิตทั้งหมดจะแสดงถึงค่าเดียวกัน จากนั้นจำนวนเต็มที่ไม่ได้ลงนามจะมีบิตปกติอีกหนึ่งบิตซึ่งจะเพิ่มจำนวนค่าทั้งหมดเป็นสองเท่า (หรืออีกนัยหนึ่งก็คือมีค่ามากพอ ๆ กับที่ตั้งค่าบิตนั้นโดยไม่ได้ตั้งค่า) ตรรกะเดียวกันนี้มีไว้สำหรับจำนวนเต็มที่ลงชื่อยกเว้นว่าค่าทั้งหมดที่มีชุดบิตนั้นเป็นค่าลบ

การแทนค่าจำนวนเต็มตามกฎหมายอีกสองรายการส่วนเติมเต็มและขนาดเครื่องหมายมีค่าเหมือนกันทั้งหมดเป็นจำนวนเต็มเสริมสองค่ายกเว้นค่าหนึ่งค่าที่เป็นลบมากที่สุด C ++ กำหนดทุกอย่างเกี่ยวกับประเภทจำนวนเต็มยกเว้นreinterpret_cast(และ C ++ 20 std::bit_cast) ในแง่ของช่วงของค่าที่เป็นตัวแทนไม่ใช่ในแง่ของการแทนค่าบิต ซึ่งหมายความว่าการวิเคราะห์ของเราจะคงไว้สำหรับการเป็นตัวแทนทั้งสามนี้ตราบเท่าที่เราไม่เคยพยายามสร้างการแสดงกับดัก ค่าที่ไม่ได้ลงชื่อซึ่งจะจับคู่กับค่าที่ขาดหายไปนี้เป็นค่าที่ค่อนข้างโชคร้าย: ค่าที่อยู่ตรงกลางของค่าที่ไม่ได้ลงนาม โชคดีที่เงื่อนไขแรกของเราตรวจสอบ (ในเวลาคอมไพล์) ว่ามีการแสดงดังกล่าวหรือไม่จากนั้นจึงจัดการเป็นพิเศษด้วยการตรวจสอบรันไทม์

เงื่อนไขแรกจัดการกรณีที่เราอยู่ใน=ส่วนซึ่งหมายความว่าเราอยู่ในพื้นที่ที่ทับซ้อนกันซึ่งสามารถแสดงค่าในอีกค่าหนึ่งได้โดยไม่มีการเปลี่ยนแปลง shift_by_windowฟังก์ชั่นในรหัสย้ายค่าทั้งหมดลงตามขนาดของแต่ละกลุ่มเหล่านี้ (เราต้องลบค่าสูงสุดแล้วลบ 1 เพื่อหลีกเลี่ยงปัญหาล้นเลขคณิต) หากเราอยู่นอกภูมิภาคนั้น (เราอยู่ใน+ภูมิภาค) เราจำเป็นต้องกระโดดลงมาทีละขนาดหน้าต่าง สิ่งนี้ทำให้เราอยู่ในช่วงที่ทับซ้อนกันซึ่งหมายความว่าเราสามารถแปลงจากที่ไม่ได้ลงนามเป็นเซ็นชื่อได้อย่างปลอดภัยเนื่องจากไม่มีการเปลี่ยนแปลงค่า อย่างไรก็ตามเรายังไม่เสร็จสิ้นเนื่องจากเราได้จับคู่ค่าที่ไม่ได้ลงนามสองค่ากับค่าที่ลงนามแต่ละค่า ดังนั้นเราต้องเลื่อนลงไปที่หน้าต่างถัดไป (ไฟล์- ภูมิภาค) เพื่อให้เรามีการทำแผนที่เฉพาะอีกครั้ง

ตอนนี้สิ่งนี้ให้ผลลัพธ์ที่สอดคล้องกันUINT_MAX + 1ตามที่ร้องขอในคำถามหรือไม่? UINT_MAX + 1คือการเทียบเท่า2^nซึ่งnเป็นจำนวนบิตในการเป็นตัวแทนค่า ค่าที่เราใช้สำหรับขนาดหน้าต่างของเราเท่ากับ2^(n - 1)(ดัชนีสุดท้ายในลำดับค่ามีค่าน้อยกว่าขนาด) เราลบค่าที่สองซึ่งหมายความว่าเราลบซึ่งเท่ากับ2 * 2^(n - 1) 2^nบวกและลบxเป็น no-op ในสมัยเลขคณิตxดังนั้นเราจึงไม่ได้รับผลกระทบ 2^nmod

จัดการโปรโมชั่นจำนวนเต็มอย่างเหมาะสม

เพราะนี่คือฟังก์ชั่นทั่วไปและไม่ได้เป็นเพียงintและunsignedเรายังมีความกังวลตัวเองด้วยกฎโปรโมชั่นหนึ่ง มีสองกรณีที่น่าสนใจอาจจะเป็นหนึ่งในที่ที่shortมีขนาดเล็กกว่าintและเป็นหนึ่งในที่ที่มีขนาดเดียวกับshortint

ตัวอย่าง: shortเล็กกว่าint

ถ้าshortมีขนาดเล็กกว่าint(ทั่วไปในแพลตฟอร์มสมัยใหม่) เราก็รู้ด้วยว่าunsigned shortสามารถใส่ใน an intได้ซึ่งหมายความว่าการดำเนินการใด ๆ ในนั้นจะเกิดขึ้นจริงintดังนั้นเราจึงโยนไปยังประเภทที่โปรโมตอย่างชัดเจนเพื่อหลีกเลี่ยงสิ่งนี้ คำกล่าวสุดท้ายของเราค่อนข้างเป็นนามธรรมและเข้าใจได้ง่ายขึ้นหากเราใช้แทนค่าจริง สำหรับกรณีแรกที่น่าสนใจของเราโดยไม่มีการสูญเสียทั่วไปให้เราพิจารณา 16 บิตshortและ 17 บิตint(ซึ่งยังคงได้รับอนุญาตภายใต้กฎใหม่และจะหมายความว่าอย่างน้อยหนึ่งในสองประเภทนี้มีบิตช่องว่างภายใน ):

constexpr auto shift_by_window = [](auto x) {
    return x - static_cast<decltype(x)>(32767) - 1;
};
return static_cast<int16_t>(
    shift_by_window(
        static_cast<int17_t>(
            shift_by_window(
                static_cast<uint17_t>(value)
            )
        )
    )
);

การแก้ปัญหาค่า 16 บิตที่ไม่ได้ลงนามให้มากที่สุด

constexpr auto shift_by_window = [](auto x) {
    return x - static_cast<decltype(x)>(32767) - 1;
};
return int16_t(
    shift_by_window(
        int17_t(
            shift_by_window(
                uint17_t(65535)
            )
        )
    )
);

ลดความซับซ้อนเป็น

return int16_t(
    int17_t(
        uint17_t(65535) - uint17_t(32767) - 1
    ) -
    int17_t(32767) -
    1
);

ลดความซับซ้อนเป็น

return int16_t(
    int17_t(uint17_t(32767)) -
    int17_t(32767) -
    1
);

ลดความซับซ้อนเป็น

return int16_t(
    int17_t(32767) -
    int17_t(32767) -
    1
);

ลดความซับซ้อนเป็น

return int16_t(-1);

เราใส่สิ่งที่ไม่ได้ลงนามมากที่สุดเท่าที่จะเป็นไปได้และกลับมา-1ประสบความสำเร็จ

ตัวอย่าง: shortขนาดเดียวกับint

หากshortมีขนาดเท่ากับint(ผิดปกติในแพลตฟอร์มสมัยใหม่) กฎการส่งเสริมแบบอินทิกรัลจะแตกต่างกันเล็กน้อย ในกรณีนี้shortส่งเสริมการintและส่งเสริมการunsigned short unsignedโชคดีที่เราโยนผลลัพธ์แต่ละรายการไปยังประเภทที่เราต้องการคำนวณอย่างชัดเจนดังนั้นเราจึงไม่มีการส่งเสริมการขายที่เป็นปัญหา โดยไม่สูญเสียความเป็นทั่วไปให้เราพิจารณา 16 บิตshortและ 16 บิตint:

constexpr auto shift_by_window = [](auto x) {
    return x - static_cast<decltype(x)>(32767) - 1;
};
return static_cast<int16_t>(
    shift_by_window(
        static_cast<int16_t>(
            shift_by_window(
                static_cast<uint16_t>(value)
            )
        )
    )
);

การแก้ปัญหาค่า 16 บิตที่ไม่ได้ลงนามให้มากที่สุด

auto x = int16_t(
    uint16_t(65535) - uint16_t(32767) - 1
);
return int16_t(
    x - int16_t(32767) - 1
);

ลดความซับซ้อนเป็น

return int16_t(
    int16_t(32767) - int16_t(32767) - 1
);

ลดความซับซ้อนเป็น

return int16_t(-1);

เราใส่สิ่งที่ไม่ได้ลงนามมากที่สุดเท่าที่จะเป็นไปได้และกลับมา-1ประสบความสำเร็จ

จะเป็นอย่างไรหากฉันสนใจintและunsignedและไม่สนใจคำเตือนเช่นคำถามเดิม

constexpr int cast_to_signed_integer(unsigned const value) {
    using result_limits = std::numeric_limits<int>;
    if constexpr (result_limits::min() + 1 != -result_limits::max()) {
        if (value == static_cast<unsigned>(result_limits::max()) + 1) {
            throw std::runtime_error("Cannot convert the maximum possible unsigned to a signed value on this system");
        }
    }
    if (value <= result_limits::max()) {
        return static_cast<int>(value);
    } else {
        constexpr int window = result_limits::min();
        return static_cast<int>(value + window) + window;
    }
}

ดูถ่ายทอดสด

https://godbolt.org/z/74hY81

ที่นี่เราจะเห็นว่า clang, gcc และ icc ไม่สร้างรหัสสำหรับcastและcast_to_signed_integer_basicที่-O2และ-O3และ MSVC ไม่สร้างรหัสที่/O2ดังนั้นวิธีแก้ปัญหาจึงเหมาะสมที่สุด


3

คุณสามารถบอกคอมไพเลอร์ได้อย่างชัดเจนว่าคุณต้องการทำอะไร:

int unsigned_to_signed(unsigned n) {
  if (n > INT_MAX) {
    if (n <= UINT_MAX + INT_MIN) {
      throw "no result";
    }
    return static_cast<int>(n + INT_MIN) - (UINT_MAX + INT_MIN + 1);
  } else {
    return static_cast<int>(n);
  }
}

คอมไพล์ด้วยgcc 4.7.2for x86_64-linux( g++ -O -S test.cpp) ถึง

_Z18unsigned_to_signedj:
    movl    %edi, %eax
    ret

UINT_MAXคือการแสดงออกของประเภทunsigned intและนั่นทำให้คุณเป็นstatic_cast<int>(n + INT_MIN) - (UINT_MAX + INT_MIN + 1)ประเภทนั้นทั้งหมด มันควรจะเป็นไปได้ที่จะแก้ไขและฉันคาดว่ามันจะยังคงคอมไพล์เหมือนเดิม

2

ถ้าxเป็นข้อมูลของเรา ...

ถ้าx > INT_MAXเราต้องการที่จะหาอย่างต่อเนื่องkดังกล่าวว่า0< <x - k*INT_MAXINT_MAX

นี้เป็นเรื่องง่าย unsigned int k = x / INT_MAX;- จากนั้นให้unsigned int x2 = x - k*INT_MAX;

ตอนนี้เราสามารถส่งx2ให้intปลอดภัยได้แล้ว ปล่อยint x3 = static_cast<int>(x2);

ตอนนี้เราต้องการที่จะลบสิ่งที่ต้องการUINT_MAX - k * INT_MAX + 1จากถ้าx3k > 0

ตอนนี้บนระบบเสริม 2s ตราบใดที่x > INT_MAXสิ่งนี้ใช้ได้กับ:

unsigned int k = x / INT_MAX;
x -= k*INT_MAX;
int r = int(x);
r += k*INT_MAX;
r -= UINT_MAX+1;

โปรดทราบว่าUINT_MAX+1เป็นศูนย์ใน C ++ ที่รับประกันการแปลงเป็น int เป็น noop และเราลบออกk*INT_MAXแล้วกลับเข้าไปใน "ค่าเดียวกัน" ดังนั้นเครื่องมือเพิ่มประสิทธิภาพที่ยอมรับได้ควรจะสามารถลบข้อมูลทั้งหมดนั้นได้!

ที่ออกจากปัญหาx > INT_MAXหรือไม่ เราสร้าง 2 สาขาหนึ่งสาขาและอีกสาขาx > INT_MAXหนึ่งไม่มี อันที่ไม่มีช่องแคบซึ่งคอมไพเลอร์ปรับให้เหมาะสมกับ noop คนที่มี ... ทำ noop หลังจากที่เครื่องมือเพิ่มประสิทธิภาพเสร็จสิ้น เครื่องมือเพิ่มประสิทธิภาพอัจฉริยะทำให้ทั้งสองสาขาเป็นสิ่งเดียวกันและลดสาขาลง

ปัญหา: หากUINT_MAXมีขนาดใหญ่มากเมื่อเทียบกับINT_MAXข้างต้นอาจใช้ไม่ได้ ฉันสมมติว่าk*INT_MAX <= UINT_MAX+1โดยปริยาย

เราอาจโจมตีสิ่งนี้ด้วย enums บางอย่างเช่น:

enum { divisor = UINT_MAX/INT_MAX, remainder = UINT_MAX-divisor*INT_MAX };

ซึ่งได้ผลเป็น 2 และ 1 ในระบบเสริม 2 วินาทีที่ฉันเชื่อ (เรารับประกันหรือไม่ว่าคณิตศาสตร์นั้นจะใช้งานได้นั่นเป็นเรื่องยุ่งยาก ... ) และใช้ตรรกะตามสิ่งเหล่านี้ที่ปรับให้เหมาะสมกับระบบเสริมที่ไม่ใช่ 2

นอกจากนี้ยังเปิดกรณีข้อยกเว้น จะเป็นไปได้ก็ต่อเมื่อ UINT_MAX มีขนาดใหญ่กว่า (INT_MIN-INT_MAX) มากดังนั้นคุณสามารถใส่รหัสข้อยกเว้นของคุณในบล็อก if ที่ถามคำถามนั้นอย่างตรงไปตรงมาและจะไม่ทำให้ระบบแบบเดิมช้าลง

ฉันไม่แน่ใจว่าจะสร้างค่าคงที่เวลาคอมไพล์เหล่านั้นอย่างไรเพื่อจัดการกับสิ่งนั้นอย่างถูกต้อง


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

@Nemo เพียงแค่ติดตามกระทู้นี้ดังนั้นโปรดยกโทษให้กับคำถามที่ชัดเจนของฉัน: คำสั่งของคุณ " UINT_MAX+1เป็นศูนย์ในทุกระบบ" ที่ตั้งขึ้นใน '03 -spec หรือไม่ถ้าเป็นเช่นนั้นมีส่วนย่อยเฉพาะที่ฉันควรดูหรือไม่ขอบคุณ
WhozCraig

@WhozCraig: ส่วน 3.9.1 วรรค 4: "จำนวนเต็มที่ไม่ได้ลงนามซึ่งประกาศว่าไม่ได้ลงนามจะต้องปฏิบัติตามกฎของโมดูโลเลขคณิต 2 ^ n โดยที่ n คือจำนวนบิตในการแทนค่าของจำนวนเต็มขนาดนั้น ๆ " โดยมีเชิงอรรถว่า "นี่หมายความว่าเลขคณิตที่ไม่ได้ลงชื่อจะไม่ล้นออกไปเนื่องจากผลลัพธ์ที่ไม่สามารถแสดงโดยประเภทจำนวนเต็มที่ไม่ได้ลงนามที่เป็นผลลัพธ์จะถูกลดโมดูโลจำนวนที่มากกว่าค่าที่มากที่สุดซึ่งสามารถแสดงโดยประเภทจำนวนเต็มที่ไม่ได้ลงนามซึ่งเป็นผลลัพธ์" โดยทั่วไปไม่ได้ลงนามจะถูกระบุให้ทำงานตามที่คุณต้องการ / คาดหวัง
Nemo

@Nemo ขอบคุณ ชื่นชมมาก
WhozCraig

1

std::numeric_limits<int>::is_moduloคือค่าคงที่ของเวลาคอมไพล์ เพื่อให้คุณสามารถใช้สำหรับความเชี่ยวชาญพิเศษของเทมเพลต แก้ไขปัญหาได้อย่างน้อยถ้าคอมไพเลอร์เล่นพร้อมกับอินไลน์

#include <limits>
#include <stdexcept>
#include <string>

#ifdef TESTING_SF
    bool const testing_sf = true;
#else
    bool const testing_sf = false;
#endif

// C++ "extensions"
namespace cppx {
    using std::runtime_error;
    using std::string;

    inline bool hopefully( bool const c ) { return c; }
    inline bool throw_x( string const& s ) { throw runtime_error( s ); }

}  // namespace cppx

// C++ "portability perversions"
namespace cppp {
    using cppx::hopefully;
    using cppx::throw_x;
    using std::numeric_limits;

    namespace detail {
        template< bool isTwosComplement >
        int signed_from( unsigned const n )
        {
            if( n <= unsigned( numeric_limits<int>::max() ) )
            {
                return static_cast<int>( n );
            }

            unsigned const u_max = unsigned( -1 );
            unsigned const u_half = u_max/2 + 1;

            if( n == u_half )
            {
                throw_x( "signed_from: unsupported value (negative max)" );
            }

            int const i_quarter = static_cast<int>( u_half/2 );
            int const int_n1 = static_cast<int>( n - u_half );
            int const int_n2 = int_n1 - i_quarter;
            int const int_n3 = int_n2 - i_quarter;

            hopefully( n == static_cast<unsigned>( int_n3 ) )
                || throw_x( "signed_from: range error" );

            return int_n3;
        }

        template<>
        inline int signed_from<true>( unsigned const n )
        {
            return static_cast<int>( n );
        }
    }    // namespace detail

    inline int signed_from( unsigned const n )
    {
        bool const is_modulo = numeric_limits< int >::is_modulo;
        return detail::signed_from< is_modulo && !testing_sf >( n );
    }
}    // namespace cppp

#include <iostream>
using namespace std;
int main()
{
    int const x = cppp::signed_from( -42u );
    wcout << x << endl;
}


แก้ไข : แก้ไขโค้ดเพื่อหลีกเลี่ยงการดักจับที่เป็นไปได้บนเครื่องที่ไม่ใช่โมดูลาร์ - int (มีเพียงอันเดียวเท่านั้นที่มีอยู่คือ Unisys Clearpath รุ่นที่กำหนดค่าแบบโบราณ เพื่อความเรียบง่ายสิ่งนี้ทำได้โดยไม่สนับสนุนค่า -2 n -1โดยที่nคือจำนวนintบิตของค่าบนเครื่องดังกล่าว (เช่นบน Clearpath) ในทางปฏิบัติค่านี้จะไม่ได้รับการสนับสนุนจากเครื่อง (กล่าวคือด้วยเครื่องหมายและขนาดหรือการแสดงส่วนประกอบเสริม 1)


1

ฉันคิดว่าประเภท int มีอย่างน้อยสองไบต์ดังนั้น INT_MIN และ INT_MAX อาจเปลี่ยนไปในแพลตฟอร์มที่ต่างกัน

ประเภทพื้นฐาน

≤climits≥ส่วนหัว


ฉันสาปแช่งให้ใช้คอมไพเลอร์สำหรับ 6809 ซึ่งกำหนดค่าด้วย "-mint8" โดยค่าเริ่มต้นโดยที่ int คือ 8 บิต :-( (นี่คือสภาพแวดล้อมการพัฒนาสำหรับ Vectrex) ยาว 2 ไบต์ยาวยาวคือ 4 ไบต์และ ฉันไม่รู้ว่าอะไรสั้น ๆ ...
Graham Toal

1

เงินของฉันใช้ memcpy คอมไพเลอร์ที่ดีทุกคนรู้ที่จะปรับให้เหมาะสม:

#include <stdio.h>
#include <memory.h>
#include <limits.h>

static inline int unsigned_to_signed(unsigned n)
{
    int result;
    memcpy( &result, &n, sizeof(result));
    return result;
}

int main(int argc, const char * argv[])
{
    unsigned int x = UINT_MAX - 1;
    int xx = unsigned_to_signed(x);
    return xx;
}

สำหรับฉัน (Xcode 8.3.2, Apple LLVM 8.1, -O3) ที่สร้าง:

_main:                                  ## @main
Lfunc_begin0:
    .loc    1 21 0                  ## /Users/Someone/main.c:21:0
    .cfi_startproc
## BB#0:
    pushq    %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    ##DEBUG_VALUE: main:argc <- %EDI
    ##DEBUG_VALUE: main:argv <- %RSI
Ltmp3:
    ##DEBUG_VALUE: main:x <- 2147483646
    ##DEBUG_VALUE: main:xx <- 2147483646
    .loc    1 24 5 prologue_end     ## /Users/Someone/main.c:24:5
    movl    $-2, %eax
    popq    %rbp
    retq
Ltmp4:
Lfunc_end0:
    .cfi_endproc

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