ฉันจะตรวจสอบจำนวนเต็มคูณล้นที่ไม่ได้ลงนามอย่างไร


618

ผมเขียนโปรแกรมใน C ++ เพื่อหาแนวทางแก้ไขปัญหาทั้งหมดของ = ที่, และร่วมกันใช้ตัวเลขทั้งหมด 0-9 ครั้งว่า โปรแกรมวนลูปมากกว่าค่าของaและbและมันรันรูทีนการนับหลักทุกครั้งที่a , bและa bเพื่อตรวจสอบว่าเงื่อนไขหลักเป็นที่พอใจหรือไม่

อย่างไรก็ตามการแก้ปัญหาการปลอมสามารถสร้างขึ้นเมื่อล้นขีด จำกัด จำนวนเต็ม ฉันสิ้นสุดการตรวจสอบนี้โดยใช้รหัสเช่น:

unsigned long b, c, c_test;
...
c_test=c*b;         // Possible overflow
if (c_test/b != c) {/* There has been an overflow*/}
else c=c_test;      // No overflow

มีวิธีทดสอบที่ดีกว่าสำหรับการโอเวอร์โฟลว์หรือไม่? ฉันรู้ว่าชิปบางตัวมีการตั้งค่าสถานะภายในที่กำหนดไว้เมื่อเกิดโอเวอร์โฟลว์ แต่ฉันไม่เคยเห็นมันเข้าถึงผ่าน C หรือ C ++


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


21
ข้อมูลที่อาจเป็นประโยชน์ในเรื่องนี้: บทที่ 5 ของ "การเข้ารหัสที่ปลอดภัยใน C และ C ++" โดย Seacord - http://www.informit.com/content/images/0321335724/samplechapter/seacord_ch05.pdfคลาส SafeInt สำหรับ C ++ - http : //blogs.msdn.com/david_leblanc/archive/2008/09/30/safeint-3-on-codeplex.aspx - http://www.codeplex.com/SafeIntไลบรารี IntSafe สำหรับ C: - [ blogs.msdn .com / michael_howard / archiv
Michael Burr

3
Secure Coding ของ Seacord เป็นทรัพยากรที่ยอดเยี่ยม แต่อย่าใช้ IntegerLib ดูblog.regehr.org/archives/593
jww

32
ตัวเลือกคอมไพเลอร์ gcc -ftrapvจะทำให้เกิดการสร้างล้นจำนวนเต็ม SIGABRT ใน (ลงนาม) ดูที่นี่
nibot

1
มันไม่ได้ตอบคำถามล้น แต่อีกวิธีหนึ่งในการแก้ไขปัญหาคือการใช้ห้องสมุด BigNum เช่นGMPเพื่อรับประกันว่าคุณมีความแม่นยำเพียงพอเสมอ คุณจะไม่ต้องกังวลเกี่ยวกับการล้นหากคุณจัดสรรตัวเลขให้เพียงพอ
wrdieter

1
ข้อมูลที่ @HeadGeek ให้ไว้ในคำตอบของเขานั้นเป็นสิ่งที่ฉันจะพูดเช่นกัน อย่างไรก็ตามด้วยการเพิ่มหนึ่ง วิธีที่คุณตรวจจับการโอเวอร์โฟลว์สำหรับการคูณในตอนนี้น่าจะเร็วที่สุด บน ARM ในขณะที่ฉันแสดงความคิดเห็นในคำตอบของ HeadGeek คุณสามารถใช้clzคำสั่งหรือ__clz(unsigned)ฟังก์ชั่นเพื่อกำหนดอันดับของตัวเลข (โดยที่บิตสูงสุดคือ) เนื่องจากฉันไม่แน่ใจว่าสิ่งนี้มีอยู่ใน x86 หรือ x64 ฉันจะถือว่ามันไม่ได้และบอกว่าการหาบิตที่สำคัญที่สุดจะทำตามlog(sizeof(int)*8)คำแนะนำที่แย่ที่สุด
nonsensickle

คำตอบ:


229

ฉันเห็นว่าคุณกำลังใช้เลขจำนวนเต็มที่ไม่ได้ลงนาม ตามคำนิยามใน C (ฉันไม่รู้เกี่ยวกับ C ++) เลขคณิตที่ไม่ได้ลงชื่อไม่ล้น ... ดังนั้นอย่างน้อยสำหรับ C ประเด็นของคุณคือสิ่งที่สงสัย :)

ด้วยจำนวนเต็มที่ลงนามเมื่อเกิดโอเวอร์โฟลว์พฤติกรรมที่ไม่ได้กำหนด (UB) ได้เกิดขึ้นและโปรแกรมของคุณสามารถทำสิ่งใดก็ได้ (ตัวอย่างเช่น: แสดงผลการทดสอบที่สรุปไม่ได้) 

#include <limits.h>

int a = <something>;
int x = <something>;
a += x;              /* UB */
if (a < 0) {         /* Unreliable test */
  /* ... */
}

ในการสร้างโปรแกรมที่เข้ากันได้คุณจะต้องทดสอบการโอเวอร์โฟลว์ก่อนสร้างการโอเวอร์โฟลว์ดังกล่าว วิธีนี้สามารถใช้กับจำนวนเต็มที่ไม่ได้ลงนามด้วย:

// For addition
#include <limits.h>

int a = <something>;
int x = <something>;
if ((x > 0) && (a > INT_MAX - x)) /* `a + x` would overflow */;
if ((x < 0) && (a < INT_MIN - x)) /* `a + x` would underflow */;

// For subtraction
#include <limits.h>
int a = <something>;
int x = <something>;
if ((x < 0) && (a > INT_MAX + x)) /* `a - x` would overflow */;
if ((x > 0) && (a < INT_MIN + x)) /* `a - x` would underflow */;

// For multiplication
#include <limits.h>

int a = <something>;
int x = <something>;
// There may be a need to check for -1 for two's complement machines.
// If one number is -1 and another is INT_MIN, multiplying them we get abs(INT_MIN) which is 1 higher than INT_MAX
if ((a == -1) && (x == INT_MIN)) /* `a * x` can overflow */
if ((x == -1) && (a == INT_MIN)) /* `a * x` (or `a / x`) can overflow */
// general case
if (a > INT_MAX / x) /* `a * x` would overflow */;
if ((a < INT_MIN / x)) /* `a * x` would underflow */;

สำหรับการแบ่ง (ยกเว้นINT_MINและ-1กรณีพิเศษ) ที่มีอยู่ไม่เป็นไปได้ของไปกว่าใด ๆหรือINT_MININT_MAX


97
จำนวนเต็มที่ไม่ได้ลงทะเบียนไม่ล้นใน C ++ อย่างเคร่งครัด (ISO / IEC 14882: 2003 3.9.1.4) การใช้ 'โอเวอร์โฟลว์' ของฉันในคำถามคือความหมายของคำพูดมากกว่าซึ่งตั้งใจจะรวมการตัดคำที่ไม่ได้ลงนามประเภทที่กำหนดไว้อย่างดีเนื่องจากฉันสนใจ ints ที่ไม่ได้ลงนามแทนจำนวนเต็มบวกเชิงคณิตศาสตร์ไม่ใช่จำนวนเต็มบวก mod 2 ^ 32 (หรือ 2 ^ 64) ความแตกต่างระหว่างล้นเป็นส่วนเบี่ยงเบนจากพฤติกรรมจำนวนเต็มขนาดคณิตศาสตร์อนันต์และล้นเป็นพฤติกรรมที่ไม่ได้กำหนดในภาษาดูเหมือนไม่ค่อยจะชัดเจน
คริสจอห์นสัน

15
การทดสอบนั้นไม่จำเป็นต้องทำx >= 0- x > 0จะพอเพียง (ถ้าx == 0แล้วx + aไม่สามารถล้นด้วยเหตุผลที่ชัดเจน)
caf

2
@ pmg มีคำพูดสนับสนุนจากมาตรฐานหรือไม่
Pacerier

5
ฉันชอบวิธีการนี้ ... อย่างไรก็ตามโปรดระวัง: การตรวจจับการล้นการคูณจะถือว่า x เป็นบวก สำหรับ x == 0 จะนำไปสู่การหารด้วยการตรวจจับที่เป็นศูนย์และสำหรับค่าลบ x จะตรวจจับการโอเวอร์โฟลว์เสมอ
Franz D.

4
if ((a < INT_MIN / x))การทดสอบช้าเกินไป ต้องif (x == -1) ทำการทดสอบก่อน
chux - Reinstate Monica

164

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

นอกจากนี้ตัวถูกดำเนินการสองตัวใด ๆ จะส่งผลให้ (อย่างน้อยที่สุด) หนึ่งบิตมากกว่าตัวถูกดำเนินการหนึ่งบิตที่สูงที่สุด ตัวอย่างเช่น:

bool addition_is_safe(uint32_t a, uint32_t b) {
    size_t a_bits=highestOneBitPosition(a), b_bits=highestOneBitPosition(b);
    return (a_bits<32 && b_bits<32);
}

สำหรับการคูณตัวถูกดำเนินการสองตัวใด ๆ จะส่งผลให้เกิดผลรวมของบิตของตัวถูกดำเนินการ ตัวอย่างเช่น:

bool multiplication_is_safe(uint32_t a, uint32_t b) {
    size_t a_bits=highestOneBitPosition(a), b_bits=highestOneBitPosition(b);
    return (a_bits+b_bits<=32);
}

ในทำนองเดียวกันคุณสามารถประมาณขนาดสูงสุดของผลลัพธ์ที่ได้จากaกำลังของbสิ่งนี้:

bool exponentiation_is_safe(uint32_t a, uint32_t b) {
    size_t a_bits=highestOneBitPosition(a);
    return (a_bits*b<=32);
}

(แทนจำนวนบิตสำหรับจำนวนเต็มเป้าหมายของคุณแน่นอน)

ฉันไม่แน่ใจว่าวิธีที่เร็วที่สุดในการกำหนดตำแหน่งสูงสุดหนึ่งบิตในจำนวนนี่เป็นวิธีการบังคับสัตว์เดรัจฉาน:

size_t highestOneBitPosition(uint32_t a) {
    size_t bits=0;
    while (a!=0) {
        ++bits;
        a>>=1;
    };
    return bits;
}

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


98
และแน่นอนคุณสามารถเปลี่ยนชื่อ highestOneBitPosition เข้าสู่ระบบ :)
โอลิเวอร์ Hallam

37
ใช่มันเป็นการดำเนินการเดียวกันlog2แต่ไม่จำเป็นว่าจะต้องชัดเจนสำหรับคนที่ไม่มีพื้นฐานทางคณิตศาสตร์
Head Geek

48
อัลกอริทึมนี้ไม่ประมาทคำตอบที่ปลอดภัยหรือไม่ 2 ^ 31 + 0 จะตรวจจับว่าไม่ปลอดภัยเนื่องจากมีค่าสูงสุด OneBitPosition (2 ^ 31) = 32 (2 ^ 32 - 1) * 1 จะตรวจพบว่าไม่ปลอดภัยตั้งแต่ 32 + 1> 32 1 ^ 100 จะตรวจจับว่าไม่ปลอดภัยตั้งแต่ 1 * 100 > 32.
clahey

19
ตามที่คุณmultiplication_is_safe 0x8000 * 0x10000ต้องการมากเกินไป(ตำแหน่งบิตคือ 16 + 17 = 33 ซึ่งคือ> 32 ) แม้ว่ามันจะไม่ใช่เพราะ0x8000 * 0x10000 = 0x80000000สิ่งที่เห็นได้ชัดว่ายังเหมาะกับ int 32 บิตที่ไม่ได้ลงนาม นี่เป็นเพียงหนึ่งในตัวอย่างที่รหัสนี้ใช้ไม่ได้ 0x8000 * 0x10001, ...
Michi

13
@GT_mh: ประเด็นของคุณ? อย่างที่ฉันบอกไปมันไม่สมบูรณ์แบบ มันเป็นกฎของหัวแม่มือที่แตกหักจะบอกว่าเมื่อสิ่งที่เป็นความปลอดภัย แต่มีวิธีการตรวจสอบว่าการคำนวณทุกคนจะไม่เป็นไรไม่ต้องทำการคำนวณไม่เต็ม 0x8000 * 0x10000ไม่ได้ "ปลอดภัย" ตามคำจำกัดความนี้ถึงแม้ว่ามันจะไม่เป็นไรก็ตาม
Head Geek

147

ดังสนั่น 3.4+และGCC 5+เสนอตัวตรวจสอบทางคณิตศาสตร์ พวกเขาเสนอวิธีแก้ปัญหาที่รวดเร็วมากโดยเฉพาะเมื่อเปรียบเทียบกับการตรวจสอบความปลอดภัยของการทดสอบบิต

สำหรับตัวอย่างในคำถามของ OP มันจะทำงานดังนี้:

unsigned long b, c, c_test;
if (__builtin_umull_overflow(b, c, &c_test))
{
    // Returned non-zero: there has been an overflow
}
else
{
    // Return zero: there hasn't been an overflow
}

เอกสารเสียงดังกราวไม่ได้ระบุว่าc_testมีผลลัพธ์ล้นหากเกิดโอเวอร์โฟลว์หรือไม่ แต่เอกสารประกอบของ GCC ระบุว่าเป็นเช่นนั้น ระบุว่าทั้งสองชอบที่จะเป็น__builtinเข้ากันได้ก็อาจจะปลอดภัยที่จะคิดว่านี่เป็นวิธีการทำงานของ Clang เช่นกัน

มี__builtinการดำเนินการทางคณิตศาสตร์สำหรับการดำเนินการทางคณิตศาสตร์ที่สามารถล้น (นอกจากนี้การลบการคูณ) ด้วยตัวแปรที่ลงนามและไม่ได้ลงนามสำหรับขนาด int ขนาดยาวและขนาดยาวยาว ไวยากรณ์สำหรับชื่อคือ__builtin_[us](operation)(l?l?)_overflow:

  • uสำหรับการได้รับการรับรองหรือsสำหรับการลงนาม ;
  • การดำเนินงานที่เป็นหนึ่งadd, subหรือmul;
  • ไม่มีlคำต่อท้ายหมายความว่าตัวถูกดำเนินการเป็นints; หนึ่งในlวิธีการlong; สองllong longเฉลี่ย s

ดังนั้นสำหรับการเพิ่มจำนวนเต็มแบบยาวที่มีลายเซ็นแล้วนั้นจะเป็น __builtin_saddl_overflowดังนั้นสำหรับการตรวจสอบการลงนามยาวจำนวนเต็มนอกจากนี้มันจะเป็นรายการเต็มรูปแบบที่สามารถพบได้ในหน้าเอกสารประกอบเสียงดังกราว

GCC 5+ และเสียงดังกราว 3.8+ ยังนำเสนอ builtins ทั่วไปที่ทำงานได้โดยไม่ต้องระบุชนิดของค่านี้__builtin_add_overflow,__builtin_sub_overflow__builtin_mul_overflowและ สิ่งเหล่านี้ยังทำงานกับประเภทที่เล็กกว่าintเหล่านี้ทำงานยังอยู่ในประเภทมีขนาดเล็กกว่า

builtins ที่ต่ำกว่าสิ่งที่ดีที่สุดสำหรับแพลตฟอร์ม บน x86 พวกเขาตรวจสอบธงพกล้นและเครื่องหมาย

cl.exe ของVisual Studioไม่มีการเทียบเท่าโดยตรง สำหรับการบวกและการลบที่ไม่ได้ลงชื่อรวมถึง<intrin.h>จะช่วยให้คุณใช้addcarry_uNNและsubborrow_uNN(โดยที่ NN คือจำนวนบิตเช่นaddcarry_u8หรือsubborrow_u64) ลายเซ็นของพวกเขาค่อนข้างป้าน:

unsigned char _addcarry_u32(unsigned char c_in, unsigned int src1, unsigned int src2, unsigned int *sum);
unsigned char _subborrow_u32(unsigned char b_in, unsigned int src1, unsigned int src2, unsigned int *diff);

c_in/b_inคือแฟล็ก carry / borrow บนอินพุตและค่าส่งคืนคือ carry / ยืมบนเอาต์พุต ไม่ปรากฏว่ามีสิ่งเทียบเท่าสำหรับการดำเนินการที่ลงชื่อหรือการคูณ

ไม่เช่นนั้น Clang for Windows ก็พร้อมใช้งานแล้ว (ดีพอสำหรับ Chrome) ดังนั้นอาจเป็นตัวเลือกก็ได้เช่นกัน


__builtin_sub_overflowไม่แน่นอนใน Clang 3.4
Richard Cook

2
@RichardCook มันใช้เวลาพอสมควร แต่เสียงดังกังวานมีตัวสร้างทั่วไปในเวอร์ชัน 3.9
zneak

@ tambre ฉันไม่คิดว่ามี
zneak

4
อ้างอิงถึงเอกสาร , __builtin_add_overflowและเพื่อน ๆ แล้วควรจะมีอยู่บนเสียงดังกราว 3.8
Lekensteyn

2
ขอบคุณ มันใช้งานได้ดี ความคิดใด ๆ ที่เกี่ยวข้องกับฟังก์ชั่นสำหรับ visual c ++? ดูเหมือนจะไม่พบพวกเขา
Mudit Jain

53

คอมไพเลอร์บางตัวให้การเข้าถึงค่าสถานะโอเวอร์โฟลว์จำนวนเต็มใน CPU ซึ่งคุณสามารถทดสอบได้ แต่นี่ไม่ใช่มาตรฐาน

คุณสามารถทดสอบความเป็นไปได้ของการล้นก่อนที่จะทำการคูณ:

if ( b > ULONG_MAX / a ) // a * b would overflow

11
... หรือใช้ numeric_limits <TYPE> :: max ()
Jonas Gulle

20
อย่าลืมจัดการ = 0 - การแบ่งเวลานั้น
Thelema

16
@Thelema: "อย่าลืมจัดการ = 0" - และ INT_MIN / -1
jww

1
เกิดอะไรขึ้นถ้าb == ULONG_MAX / a? จากนั้นมันจะยังคงพอดีโดยที่aหารULONG_MAXโดยไม่เหลือ
สุกร

ตลกที่ประสิทธิภาพฉลาดการคูณค่อนข้างเร็วเมื่อเทียบกับการหารและคุณเพิ่มการหารสำหรับการคูณทุกครั้ง นี้ไม่ได้เสียงเหมือนวิธีการแก้ปัญหา
DrumM

40

คำเตือน: GCC สามารถเพิ่มประสิทธิภาพการตรวจสอบ-O2มากเกินไปเมื่อรวบรวม ตัวเลือก-Wallจะให้คำเตือนในบางกรณีเช่น

if (a + b < a) { /* Deal with overflow */ }

แต่ไม่ใช่ในตัวอย่างนี้:

b = abs(a);
if (b < 0) { /* Deal with overflow */ }

วิธีเดียวที่ปลอดภัยคือการตรวจสอบการล้นก่อนที่จะเกิดขึ้นตามที่อธิบายไว้ในกระดาษ CERTและสิ่งนี้จะน่าเบื่ออย่างไม่น่าเชื่อที่จะใช้อย่างเป็นระบบ

การคอมไพล์ด้วยการ-fwrapvแก้ปัญหา แต่ปิดใช้งานการเพิ่มประสิทธิภาพบางอย่าง

เราต้องการทางออกที่ดีกว่า ฉันคิดว่าคอมไพเลอร์ควรออกคำเตือนตามค่าเริ่มต้นเมื่อทำการปรับให้เหมาะสมที่อาศัยการโอเวอร์โฟลว์ที่ไม่เกิดขึ้น สถานการณ์ปัจจุบันอนุญาตให้คอมไพเลอร์ปรับออพชั่นการตรวจสอบโอเวอร์โฟลว์ซึ่งยอมรับไม่ได้ในความคิดของฉัน


8
โปรดทราบว่าคอมไพเลอร์สามารถทำได้ด้วยประเภทจำนวนเต็มที่ลงนามแล้วเท่านั้น overflow ถูกกำหนดไว้อย่างสมบูรณ์สำหรับประเภทจำนวนเต็มที่ไม่ได้ลงนาม ถึงกระนั้นใช่มันเป็นกับดักที่อันตรายมาก!
SamB

1
"ฉันคิดว่าคอมไพเลอร์ควรออกคำเตือนตามค่าเริ่มต้นเมื่อทำการปรับให้เหมาะสมที่อาศัยการโอเวอร์โฟลว์ที่ไม่เกิดขึ้น" - ดังนั้นfor(int k = 0; k < 5; k++) {...}ควรเพิ่มคำเตือน?
user253751

2
@ กลไก: ทำไมมันควร? kสามารถกำหนดค่าของเวลาที่รวบรวมได้อย่างง่ายดาย คอมไพเลอร์ไม่จำเป็นต้องตั้งสมมติฐานใด ๆ
MikeMB

2
@immibis: เพื่ออ้างถึงข้างต้น: "ฉันคิดว่าคอมไพเลอร์ควรออกคำเตือนโดยค่าเริ่มต้นเมื่อทำการเพิ่มประสิทธิภาพที่อาศัยล้นไม่เกิดขึ้น"
MikeMB

1
@MikeMB การเพิ่มประสิทธิภาพที่คอมไพเลอร์ไม่สนใจที่จะตรวจสอบว่าnน้อยกว่า 32 ก่อนที่จะส่งคำสั่ง shift ที่ใช้เพียง 5 บิตต่ำกว่าn?
user253751

30

Clang สนับสนุนการตรวจสอบโอเวอร์โฟลว์แบบไดนามิกสำหรับทั้งจำนวนเต็มที่ลงชื่อและไม่ได้ลงชื่อ ดู-fsanitize = จำนวนเต็มสวิทช์ สำหรับตอนนี้มันเป็นคอมไพเลอร์ C ++ เพียงตัวเดียวที่มีการตรวจสอบโอเวอร์โฟลว์ไดนามิกที่สนับสนุนอย่างเต็มที่สำหรับวัตถุประสงค์ในการดีบัก


25

ฉันเห็นว่าผู้คนจำนวนมากตอบคำถามเกี่ยวกับการล้น แต่ฉันต้องการที่จะแก้ไขปัญหาเดิมของเขา เขากล่าวว่าปัญหาคือการหาb = c เช่นนั้นตัวเลขทั้งหมดจะถูกใช้โดยไม่ต้องทำซ้ำ ตกลงนั่นไม่ใช่สิ่งที่เขาถามในโพสต์นี้ แต่ฉันก็ยังคิดว่ามันจำเป็นที่จะต้องศึกษาขอบเขตของปัญหาและสรุปว่าเขาไม่จำเป็นต้องคำนวณหรือตรวจพบการไหลล้น (หมายเหตุ: ฉันไม่ชำนาญ ในวิชาคณิตศาสตร์ฉันทำทีละขั้นตอน แต่ผลลัพธ์สุดท้ายนั้นง่ายมากจนอาจมีสูตรง่าย ๆ )

ประเด็นหลักคือขอบเขตบนที่ปัญหาต้องการสำหรับ a, b หรือ c คือ 98.765.432 อย่างไรก็ตามเริ่มต้นด้วยการแยกปัญหาในส่วนที่ไม่สำคัญและไม่สำคัญ:

  • x 0 == 1 (การเรียงลำดับทั้งหมดของ 9, 8, 7, 6, 5, 4, 3, 2 เป็นคำตอบ)
  • x 1 == x (ไม่สามารถหาทางออกได้)
  • 0 b == 0 (ไม่สามารถหาทางออกได้)
  • 1 b == 1 (ไม่สามารถหาทางออกได้)
  • a b , a> 1, b> 1 (ไม่สำคัญ)

ตอนนี้เราเพียงแค่ต้องแสดงให้เห็นว่าไม่มีวิธีการแก้ปัญหาอื่นใดที่เป็นไปได้และมีเพียงวิธีการเปลี่ยนลำดับที่ถูกต้องเท่านั้น เรากลับไปที่ขอบเขตบน ที่จริงแล้วขอบเขตบนคือ c ≤ 98.765.432 มันคือขอบเขตสูงสุดเพราะเป็นจำนวนที่มากที่สุดด้วย 8 หลัก (รวม 10 หลักลบ 1 สำหรับแต่ละ a และ b) ขอบเขตบนนี้มีไว้สำหรับ c เท่านั้นเนื่องจากขอบเขตสำหรับ a และ b ต้องต่ำกว่ามากเนื่องจากการเติบโตแบบเอ็กซ์โปเนนเชียลเนื่องจากเราสามารถคำนวณได้เปลี่ยนแปลง b จาก 2 ถึงขอบเขตบน:

    9938.08^2 == 98765432
    462.241^3 == 98765432
    99.6899^4 == 98765432
    39.7119^5 == 98765432
    21.4998^6 == 98765432
    13.8703^7 == 98765432
    9.98448^8 == 98765432
    7.73196^9 == 98765432
    6.30174^10 == 98765432
    5.33068^11 == 98765432
    4.63679^12 == 98765432
    4.12069^13 == 98765432
    3.72429^14 == 98765432
    3.41172^15 == 98765432
    3.15982^16 == 98765432
    2.95305^17 == 98765432
    2.78064^18 == 98765432
    2.63493^19 == 98765432
    2.51033^20 == 98765432
    2.40268^21 == 98765432
    2.30883^22 == 98765432
    2.22634^23 == 98765432
    2.15332^24 == 98765432
    2.08826^25 == 98765432
    2.02995^26 == 98765432
    1.97741^27 == 98765432

ประกาศตัวอย่างเช่นบรรทัดสุดท้าย: มันบอกว่า 1.97 ^ 27 ~ 98M ตัวอย่างเช่น 1 ^ 27 == 1 และ 2 ^ 27 == 134.217.728 และนั่นไม่ใช่วิธีแก้ปัญหาเพราะมันมี 9 หลัก (2> 1.97 ดังนั้นมันจึงใหญ่กว่าที่ควรทดสอบ) จะเห็นได้ว่าชุดค่าผสมที่มีสำหรับการทดสอบ a และ b นั้นมีขนาดเล็กมาก สำหรับ b == 14 เราต้องลอง 2 และ 3 สำหรับ b == 3 เราเริ่มที่ 2 และหยุดที่ 462 ผลลัพธ์ทั้งหมดจะได้รับน้อยกว่า ~ 98M

ตอนนี้เพียงทดสอบชุดค่าผสมทั้งหมดด้านบนแล้วค้นหาค่าที่ไม่ซ้ำตัวเลขใด ๆ :

    ['0', '2', '4', '5', '6', '7', '8'] 84^2 = 7056
    ['1', '2', '3', '4', '5', '8', '9'] 59^2 = 3481
    ['0', '1', '2', '3', '4', '5', '8', '9'] 59^2 = 3481 (+leading zero)
    ['1', '2', '3', '5', '8'] 8^3 = 512
    ['0', '1', '2', '3', '5', '8'] 8^3 = 512 (+leading zero)
    ['1', '2', '4', '6'] 4^2 = 16
    ['0', '1', '2', '4', '6'] 4^2 = 16 (+leading zero)
    ['1', '2', '4', '6'] 2^4 = 16
    ['0', '1', '2', '4', '6'] 2^4 = 16 (+leading zero)
    ['1', '2', '8', '9'] 9^2 = 81
    ['0', '1', '2', '8', '9'] 9^2 = 81 (+leading zero)
    ['1', '3', '4', '8'] 3^4 = 81
    ['0', '1', '3', '4', '8'] 3^4 = 81 (+leading zero)
    ['2', '3', '6', '7', '9'] 3^6 = 729
    ['0', '2', '3', '6', '7', '9'] 3^6 = 729 (+leading zero)
    ['2', '3', '8'] 2^3 = 8
    ['0', '2', '3', '8'] 2^3 = 8 (+leading zero)
    ['2', '3', '9'] 3^2 = 9
    ['0', '2', '3', '9'] 3^2 = 9 (+leading zero)
    ['2', '4', '6', '8'] 8^2 = 64
    ['0', '2', '4', '6', '8'] 8^2 = 64 (+leading zero)
    ['2', '4', '7', '9'] 7^2 = 49
    ['0', '2', '4', '7', '9'] 7^2 = 49 (+leading zero)

ไม่มีใครตรงกับปัญหา (ซึ่งสามารถเห็นได้โดยไม่มี '0', '1', ... , '9')

ตัวอย่างรหัสที่แก้ได้ตามมา นอกจากนี้โปรดทราบว่าเขียนไว้ใน Python ไม่ใช่เพราะมันต้องการจำนวนเต็มความแม่นยำตามอำเภอใจ (รหัสไม่คำนวณอะไรที่ใหญ่กว่า 98 ล้าน) แต่เนื่องจากเราพบว่าจำนวนการทดสอบมีขนาดเล็กมากจนเราควรใช้ภาษาระดับสูงเพื่อ ใช้ประโยชน์จากคอนเทนเนอร์และไลบรารีในตัว (โปรดทราบว่า: รหัสมี 28 บรรทัด)

    import math

    m = 98765432
    l = []
    for i in xrange(2, 98765432):
        inv = 1.0/i
        r = m**inv
        if (r < 2.0): break
        top = int(math.floor(r))
        assert(top <= m)

        for j in xrange(2, top+1):
            s = str(i) + str(j) + str(j**i)
            l.append((sorted(s), i, j, j**i))
            assert(j**i <= m)

    l.sort()
    for s, i, j, ji in l:
        assert(ji <= m)
        ss = sorted(set(s))
        if s == ss:
            print '%s %d^%d = %d' % (s, i, j, ji)

        # Try with non significant zero somewhere
        s = ['0'] + s
        ss = sorted(set(s))
        if s == ss:
            print '%s %d^%d = %d (+leading zero)' % (s, i, j, ji)

1
ทำไมคุณไม่ใช้ 9.876.543.210 เป็นขีด จำกัด บน?
Tom Roggero

3
เนื่องจากต้องใช้ตัวเลข 2 หลักสำหรับด้านซ้ายมือของสมการ
hdante

2
ไม่ว่าจะสร้างความแตกต่าง แต่ขีด จำกัด สูงสุดสามารถนำมาเป็น 98765410 ตามที่คุณได้ระบุค่าใน LHS คือ> 1
Paul Childs

24

ต่อไปนี้เป็นคำตอบของคำถามที่ "ไม่พกพาได้" Intel x86 และ x64 CPUs มีชื่อที่เรียกว่าEFLAGS-registerซึ่งจะถูกเติมโดยโปรเซสเซอร์หลังจากการดำเนินการทางคณิตศาสตร์จำนวนเต็มแต่ละตัว ฉันจะข้ามคำอธิบายโดยละเอียดที่นี่ ธงที่เกี่ยวข้องคือธง "ล้น" (หน้ากาก 0x800) และธง "พกพา" (หน้ากาก 0x1) ในการตีความอย่างถูกต้องควรพิจารณาว่าตัวถูกดำเนินการเป็นประเภทที่ถูกเซ็นชื่อหรือไม่ได้ลงนาม

นี่เป็นวิธีการที่ใช้งานได้จริงในการตรวจสอบธงจาก C / C ++ รหัสต่อไปนี้จะใช้ได้กับVisual Studio 2005หรือใหม่กว่า (ทั้ง 32 และ 64 บิต) เช่นเดียวกับ GNU C / C ++ 64 บิต

#include <cstddef>
#if defined( _MSC_VER )
#include <intrin.h>
#endif

inline size_t query_intel_x86_eflags(const size_t query_bit_mask)
{
    #if defined( _MSC_VER )

        return __readeflags() & query_bit_mask;

    #elif defined( __GNUC__ )
        // This code will work only on 64-bit GNU-C machines.
        // Tested and does NOT work with Intel C++ 10.1!
        size_t eflags;
        __asm__ __volatile__(
            "pushfq \n\t"
            "pop %%rax\n\t"
            "movq %%rax, %0\n\t"
            :"=r"(eflags)
            :
            :"%rax"
            );
        return eflags & query_bit_mask;

    #else

        #pragma message("No inline assembly will work with this compiler!")
            return 0;
    #endif
}

int main(int argc, char **argv)
{
    int x = 1000000000;
    int y = 20000;
    int z = x * y;
    int f = query_intel_x86_eflags(0x801);
    printf("%X\n", f);
}

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


21

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

uint8_t x, y;    /* Give these values */
const uint16_t data16    = x + y;
const bool carry        = (data16 > 0xFF);
const bool overflow     = ((~(x ^ y)) & (x ^ data16) & 0x80);

มันขึ้นอยู่กับแนวคิดที่อธิบายไว้ในหน้านี้: http://www.cs.umd.edu/class/spring2003/cmsc311/Notes/Comb/overflow.html

สำหรับตัวอย่างที่ 32 บิต0xFFจะกลายเป็น0xFFFFFFFFและ0x80จะกลายเป็น0x80000000และในที่สุดก็จะกลายเป็นuint16_tuint64_t

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


ลิงก์ใช้งานไม่ได้: HTTP 403: ถูกห้าม
Peter Mortensen

18

วิธีที่ง่ายที่สุดคือการแปลงunsigned longs เป็นunsigned long longs ทำการคูณและเปรียบเทียบผลลัพธ์กับ 0x100000000LL

คุณอาจพบว่านี่มีประสิทธิภาพมากกว่าการแบ่งตามที่คุณทำในตัวอย่างของคุณ

โอ้และมันจะใช้ได้ทั้งใน C และ C ++ (เพราะคุณติดแท็กคำถามกับทั้งคู่)


รับเพียงแค่การดูที่คู่มือ glibc มีการกล่าวถึงกับดักจำนวนเต็มล้น (เป็นFPE_INTOVF_TRAP) SIGFPEเป็นส่วนหนึ่งของ นั่นจะเป็นอุดมคตินอกเหนือจากบิตที่น่ารังเกียจในคู่มือ:

FPE_INTOVF_TRAP จำนวนเต็มมากเกิน (เป็นไปไม่ได้ในโปรแกรม C ยกเว้นว่าคุณเปิดใช้งานการดักมากเกินไปในแบบเฉพาะฮาร์ดแวร์)

น่าละอายจริงๆ


4
อืม ... สิ่งที่ฉันไม่ได้พูดคือฉันกำลังถามคำถามนี้เพื่อเตรียมการเขียนโปรแกรมเพื่อแก้ปัญหาที่มีจำนวนมากขึ้นซึ่งฉันใช้ int มาเป็นเวลานานแล้ว เนื่องจาก int ที่มีความยาวไม่ได้อยู่ในมาตรฐาน C ++ ฉันติดอยู่กับรุ่น 32 บิตเพื่อหลีกเลี่ยงความสับสน
Chris Johnson

ผมแนะนำให้ใช้ซึ่งจะง่ายต่อการพกพาชนิดและมากกว่าการเข้ารหัสที่ยากULONG_MAX 0x100000000
jw013

24
สิ่งนี้ใช้ไม่ได้เมื่อlongและlong longมีขนาดเท่ากัน (เช่นในคอมไพเลอร์ 64 บิตจำนวนมาก)
ระหว่าง

การใช้สัญญาณเพื่อบอกคุณเกี่ยวกับการล้นนั้นจะช้ามาก ๆ
SamB

@SamB เฉพาะในกรณีที่คาดว่าจะมีการล้น
user253751

17

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

แนวคิดก็คือเพราะโปรเซสเซอร์เพียงแค่ให้ค่าตัดกลับเป็นศูนย์และ C / C ++ คือการแยกออกจากโปรเซสเซอร์เฉพาะคุณสามารถ:

uint32_t x, y;
uint32_t value = x + y;
bool overflow = value < (x | y);

ทั้งสองนี้ช่วยให้มั่นใจว่าหากตัวถูกดำเนินการตัวใดตัวหนึ่งเป็นศูนย์ แต่ไม่มีตัวใดตัวล้นจะไม่ถูกตรวจจับอย่างผิด ๆ และเร็วกว่าการดำเนินการ NOT / XOR / AND / ทดสอบจำนวนมากตามที่แนะนำไว้ก่อนหน้านี้

ตามที่ระบุไว้วิธีการนี้ถึงแม้จะดีกว่าวิธีอื่น ๆ ที่ซับซ้อนกว่า แต่ก็ยังคงเหมาะสม ต่อไปนี้คือการแก้ไขรหัสต้นฉบับที่มีการปรับให้เหมาะสม:

uint32_t x, y;
uint32_t value = x + y;
const bool overflow = value < x; // Alternatively "value < y" should also work

วิธีที่มีประสิทธิภาพและราคาถูกกว่าในการตรวจสอบการล้นการคูณคือ:

uint32_t x, y;
const bool overflow = (x >> 16U) * (y >> 16U);
uint32_t value = overflow ? UINT32_MAX : x * y;

ซึ่งส่งผลให้ทั้ง UINT32_MAX ในการล้นหรือผลของการคูณ มันเป็นพฤติกรรมที่ไม่ได้กำหนดอย่างเคร่งครัดเพื่อให้การคูณสามารถดำเนินต่อไปสำหรับจำนวนเต็มที่ลงนามในกรณีนี้


ฉันไม่เห็นด้วยเนื่องจากทฤษฎีการคำนวณ .. พิจารณาสิ่งต่อไปนี้: y> x, โอเวอร์โฟลว์ค่า, y มีค่ามากกว่า x เนื่องจากมีการตั้งค่าบิตเครื่องหมาย (1 + 255 ตัวอย่างสำหรับตัวอักษรที่ไม่ได้ลงชื่อ) และ x จะทำให้ ในล้น = false - เพราะฉะนั้นการใช้ตรรกะหรือเพื่อป้องกันพฤติกรรมเสียนี้ ..
DX-จันทร์

การทดสอบใช้งานได้กับตัวเลขที่คุณให้ (x: = 1, y: = 255, size = uint8_t): ค่าจะเป็น 0 (1 + 255) และ 0 <1 เป็นจริง มันทำงานได้จริงสำหรับทุกคู่ตัวเลข
กุนเธอร์ Piez

อืมคุณทำให้เป็นจุดที่ดี ฉันยังคงยึดติดกับด้านความปลอดภัยโดยใช้หรือหลอกแม้ว่าคอมไพเลอร์ที่ดีจะเพิ่มประสิทธิภาพให้กับผู้ให้บริการคุณอย่างถูกต้องสำหรับอินพุตทั้งหมดรวมถึงหมายเลขที่ไม่ล้นเช่น "0 + 4" ซึ่งผลลัพธ์จะไม่ล้น
DX-MON

4
หากมีน้ำล้นกว่าและx+y>=256 value=x+y-256เพราะy<256ถือเป็นจริงเสมอ (y-256) เป็นลบและvalue < xเป็นจริงเสมอ หลักฐานของคดีที่ไม่ล้นนั้นค่อนข้างคล้ายกัน
กุนเธอร์ Piez

2
@ DX-MON: วิธีการแรกของคุณเป็นสิ่งจำเป็นหากคุณมีบิตนำติดตัวจากการเพิ่มก่อนหน้านี้ uint32_t x[N], y[N], z[N], carry=0; for (int i = 0; i < N; i++) { z[i] = x[i] + y[i] + carry; carry = z[i] < (x[i] | y[i]); }หากคุณไม่มีorค่าคุณจะไม่สามารถแยกความแตกต่างระหว่างหนึ่งตัวถูกดำเนินการและตัวดำเนินการบิตเป็นศูนย์และตัวถูกดำเนินการตัวหนึ่ง0xffffffffและตัวดำเนินการบิตหนึ่ง
Matt

14

คุณไม่สามารถเข้าถึงการตั้งค่าสถานะโอเวอร์โฟลว์จาก C / C ++

คอมไพเลอร์บางตัวอนุญาตให้คุณแทรกคำสั่ง trap ลงในโค้ด เมื่อวันที่ GCC -ftrapvตัวเลือกคือ

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

อย่างไรก็ตาม-ftrapvดูเหมือนว่าจะไม่ทำอะไรเลยกับ x86 โดยใช้ GCC ล่าสุด ฉันเดาว่ามันเป็นของเหลือจากเวอร์ชั่นเก่าหรือเฉพาะกับสถาปัตยกรรมอื่น ฉันคาดว่าคอมไพเลอร์จะใส่ opcode INTO หลังจากแต่ละเพิ่มเติม น่าเสียดายที่มันไม่ได้ทำเช่นนี้


อาจแตกต่างกันไป: -ftrapv ทำงานได้ดีโดยใช้ GCC 4.3.4 บนกล่อง Cygwin มีตัวอย่างที่stackoverflow.com/questions/5005379/…
Nate Kohl

3
คุณทั้งคู่พูดถูก -ftrapv ทำงาน แต่เฉพาะกับจำนวนเต็มที่ลงนาม
ZAB

14

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

unsigned int r, a, b;
r = a + b;
if (r < a)
{
    // Overflow
}

สำหรับจำนวนเต็มที่ลงนามคุณสามารถตรวจสอบสัญญาณของข้อโต้แย้งและผลลัพธ์

จำนวนเต็มของสัญญาณต่างกันไม่สามารถล้นได้และจำนวนเต็มของสัญญาณล้นเดียวกันก็ต่อเมื่อผลลัพธ์นั้นเป็นสัญญาณต่างกัน:

signed int r, a, b, s;
r = a + b;
s = a>=0;
if (s == (b>=0) && s != (r>=0))
{
    // Overflow
}

วิธีแรกก็ใช้ได้กับจำนวนเต็มที่ลงนามด้วยใช่ไหม char result = (char)127 + (char)3;จะเป็น -126; เล็กกว่าตัวถูกดำเนินการทั้งสอง
primfaktor

1
โอ้ฉันเข้าใจแล้วปัญหาคือความจริงที่ว่ามันไม่ได้กำหนดไว้สำหรับประเภทที่ลงชื่อ
primfaktor

27
-1 การล้นของตัวเลขที่เซ็นชื่อส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนด (ดังนั้นการทดสอบช้าเกินไปที่จะเป็นประโยชน์จริง ๆ )
Voo

1
@primfaktor ไม่สามารถใช้งานได้กับ int ที่ลงนามแล้ว: char ((- 127) + (-17)) = 112 สำหรับ int ที่ลงนามคุณต้องตรวจสอบบิตของการขัดแย้งและผลลัพธ์
phuclv

3
ตามที่ระบุไว้แล้ววิธีแก้ปัญหาสำหรับจำนวนเต็มที่ลงนามไม่ทำงานเนื่องจากพฤติกรรมที่ไม่ได้กำหนดของ + b ในกรณีที่ล้น การตรวจสอบโอเวอร์โฟลว์ด้วยจำนวนเต็มที่ลงนามจะต้องทำก่อนการดำเนินการ
Marwan Burelle

11

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

การทดสอบการโอเวอร์โฟลว์ที่ลงชื่อ, การเพิ่มและการลบ:

  1. รับค่าคงที่ที่เป็นตัวแทนของค่าที่ใหญ่ที่สุดและน้อยที่สุดสำหรับประเภท MAXVALUE และ MINVALUE

  2. คำนวณและเปรียบเทียบสัญญาณของตัวถูกดำเนินการ

    หากค่าใดเป็นศูนย์จะไม่สามารถเพิ่มหรือลบได้ ข้ามการทดสอบที่เหลือ

    ข หากสัญญาณตรงข้ามแสดงว่าการเติมไม่สามารถผ่านได้ ข้ามการทดสอบที่เหลือ

    ค. หากเครื่องหมายเหมือนกันการลบจะไม่สามารถล้นได้ ข้ามการทดสอบที่เหลือ

  3. ทดสอบกระแสเกินค่าบวกของ MAXVALUE

    หากทั้งสองสัญญาณเป็นค่าบวกและ MAXVALUE - A <B ดังนั้นการเติมจะล้น

    ข หากเครื่องหมายของ B เป็นลบและ MAXVALUE - A <-B ดังนั้นการลบจะล้น

  4. ทดสอบการลบล้นของ MINVALUE

    หากสัญญาณทั้งสองเป็นลบและ MINVALUE - A> B ดังนั้นการเติมจะล้น

    ข หากเครื่องหมาย A เป็นลบและ MINVALUE - A> B ดังนั้นการลบจะล้น

  5. มิฉะนั้นจะไม่ล้น

การทดสอบการโอเวอร์โฟลว์ที่ลงนาม, การคูณและการหาร:

  1. รับค่าคงที่ที่เป็นตัวแทนของค่าที่ใหญ่ที่สุดและน้อยที่สุดสำหรับประเภท MAXVALUE และ MINVALUE

  2. คำนวณและเปรียบเทียบขนาด (ค่าสัมบูรณ์) ของตัวถูกดำเนินการกับหนึ่ง (ด้านล่างสมมติว่า A และ B เป็นขนาดเหล่านี้ไม่ใช่ต้นฉบับที่ลงนามแล้ว)

    หากค่าใดเป็นศูนย์การคูณจะไม่สามารถล้นและการหารจะให้ผลเป็นศูนย์หรืออินฟินิตี้

    ข หากค่าใดค่าหนึ่งคือการคูณและการหารจะไม่สามารถล้นได้

    ค. ถ้าขนาดของหนึ่งตัวถูกดำเนินการต่ำกว่าหนึ่งและอื่น ๆ มากกว่าหนึ่งตัวคูณไม่สามารถล้น

    d ถ้าขนาดทั้งสองมีค่าน้อยกว่าหนึ่งค่าหารจะไม่สามารถล้นได้

  3. ทดสอบกระแสเกินค่าบวกของ MAXVALUE

    หากตัวถูกดำเนินการทั้งสองมีค่ามากกว่าหนึ่งและ MAXVALUE / A <B การคูณจะล้น

    ข หาก B น้อยกว่าหนึ่งและ MAXVALUE * B <A การหารจะล้น

  4. มิฉะนั้นจะไม่ล้น

หมายเหตุ: การไหลล้นขั้นต่ำของ MINVALUE ถูกจัดการโดย 3 เพราะเราใช้ค่าสัมบูรณ์ อย่างไรก็ตามหาก ABS (ต่ำสุด)> สูงสุดแล้วเราจะมีผลบวกปลอมที่หายาก

การทดสอบอันเดอร์โฟล์คล้ายกัน แต่เกี่ยวข้องกับ EPSILON (จำนวนบวกที่เล็กที่สุดที่มากกว่าศูนย์)


1
อย่างน้อยในระบบ POSIX สัญญาณ SIGFPE สามารถเปิดใช้งานสำหรับจุดลอยตัวใต้ / ล้น
Chris Johnson

ในขณะที่แปลงเป็นทศนิยมและกลับมาทำงานมันเป็น (ตามการทดสอบของฉันในเครื่อง 32 บิต) ช้ากว่าโซลูชั่นอื่น ๆ
JanKanis

ผู้ตรวจสอบตรวจพบกรณีที่ขาดหายไปสำหรับการลบส่วนที่ 2 ฉันยอมรับว่า 0 - MINVALUE จะล้น ดังนั้นควรทำการทดสอบสำหรับกรณีนี้
Paul Chernoch

<pedantic> จำนวนเต็มไม่ underflow (= ใกล้เกินไปที่จะเป็นศูนย์ที่จะแสดงด้วยความแม่นยำใด ๆ ) 1.0e-200 / 1.0e200จะเป็นตัวอย่างของอันเดอร์โฟล์ที่เกิดขึ้นจริงโดยสมมติว่า IEEE เป็นสองเท่า คำที่ถูกต้องที่นี่แทนเป็นล้นมากเกินไป </pedantic>
Arne Vogel

เพื่อความแม่นยำเหตุผลที่จำนวนเต็มไม่ได้รับการพิจารณาว่าเป็นอันเดอร์อันเดอร์นั้นเป็นเพราะพฤติกรรมการตัดทอนที่กำหนดไว้เช่น1/INT_MAXอาจถูกพิจารณาว่าเป็นอันเดอร์อันเดอร์ได้น้อย แต่ภาษานั้นก็จะตัดทอนให้เป็นศูนย์
Arne Vogel

8

CERT ได้พัฒนาวิธีการใหม่ในการตรวจจับและการรายงานจำนวนเต็มล้นจำนวนเต็มที่ลงนามการตัดจำนวนเต็มไม่ได้ลงนามและการตัดทอนจำนวนเต็มโดยใช้รูปแบบจำนวนเต็ม "as-if" นับไม่ถ้วน (AIR) CERT ได้เผยแพร่รายงานทางเทคนิคที่อธิบายถึงโมเดลและสร้างต้นแบบการทำงานตาม GCC 4.4.0 และ GCC 4.5.0

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


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

8

อีกหนึ่งเครื่องมือที่น่าสนใจคือIOC: จำนวนเต็มล้นตรวจสอบสำหรับ C

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

คุณจะได้ผลลัพธ์เช่นนี้:

CLANG ARITHMETIC UNDEFINED at <add.c, (9:11)> :
Op: +, Reason : Signed Addition Overflow,
BINARY OPERATION: left (int32): 2147483647 right (int32): 1

1
ตอนนี้แพทช์นี้ถูกรวมเข้ากับ clang codebase ท่ามกลาง sanitizers อื่น ๆ ดูคำตอบของฉัน
ZAB

7

อีกหนึ่งโซลูชันที่ใช้ภาษาแอสเซมบลีคือโพรซีเดอร์ภายนอก ตัวอย่างนี้สำหรับการคูณจำนวนเต็มที่ไม่ได้ลงชื่อโดยใช้ g ++ และ fasm ภายใต้ Linux x64

โพรซีเดอร์นี้ทวีคูณอาร์กิวเมนต์จำนวนเต็มสองข้อที่ไม่ได้ลงชื่อ (32 บิต) (ตามข้อกำหนดสำหรับ amd64 (ส่วน3.2.3 การผ่านพารามิเตอร์ )

ถ้าคลาสนั้นเป็น INTEGER จะมีการลงทะเบียนลำดับถัดไปของลำดับ% rdi,% rsi,% rdx,% rcx,% r8 และ% r9

(edi และ esi ลงทะเบียนในรหัสของฉัน)) และส่งกลับผลลัพธ์หรือ 0 หากเกิดโอเวอร์โฟลว์

format ELF64

section '.text' executable

public u_mul

u_mul:
  MOV eax, edi
  mul esi
  jnc u_mul_ret
  xor eax, eax
u_mul_ret:
ret

ทดสอบ:

extern "C" unsigned int u_mul(const unsigned int a, const unsigned int b);

int main() {
    printf("%u\n", u_mul(4000000000,2)); // 0
    printf("%u\n", u_mul(UINT_MAX/2,2)); // OK
    return 0;
}

ลิงก์โปรแกรมกับไฟล์อ็อบเจ็กต์ asm ในกรณีของฉันในQt Creatorให้เพิ่มลงLIBSในไฟล์. pro


5

คำนวณผลลัพธ์ด้วย doubles พวกเขามี 15 หลักสำคัญ ความต้องการของคุณมีขอบเขตบนที่ยากในcของ 10 8  - มันสามารถมีได้สูงสุด 8 หลัก ดังนั้นผลลัพธ์จะแม่นยำถ้าอยู่ในระยะและจะไม่ล้นเป็นอย่างอื่น


5

ลองใช้แมโครนี้เพื่อทดสอบบิตล้นของเครื่อง 32- บิต (ปรับใช้โซลูชันของ Angel Sinigersky)

#define overflowflag(isOverflow){   \
size_t eflags;                      \
asm ("pushfl ;"                     \
     "pop %%eax"                    \
    : "=a" (eflags));               \
isOverflow = (eflags >> 11) & 1;}

ฉันนิยามมันเป็นแมโครเพราะมิฉะนั้นบิตล้นจะถูกเขียนทับ

ต่อมาเป็นแอปพลิเคชั่นเล็กน้อยที่มีการคัดรหัสด้านบน:

#include <cstddef>
#include <stdio.h>
#include <iostream>
#include <conio.h>
#if defined( _MSC_VER )
#include <intrin.h>
#include <oskit/x86>
#endif

using namespace std;

#define detectOverflow(isOverflow){     \
size_t eflags;                      \
asm ("pushfl ;"                     \
    "pop %%eax"                     \
    : "=a" (eflags));               \
isOverflow = (eflags >> 11) & 1;}

int main(int argc, char **argv) {

    bool endTest = false;
    bool isOverflow;

    do {
        cout << "Enter two intergers" << endl;
        int x = 0;
        int y = 0;
        cin.clear();
        cin >> x >> y;
        int z = x * y;
        detectOverflow(isOverflow)
        printf("\nThe result is: %d", z);
        if (!isOverflow) {
            std::cout << ": no overflow occured\n" << std::endl;
        } else {
            std::cout << ": overflow occured\n" << std::endl;
        }

        z = x * x * y;
        detectOverflow(isOverflow)
        printf("\nThe result is: %d", z);
        if (!isOverflow) {
            std::cout << ": no overflow ocurred\n" << std::endl;
        } else {
            std::cout << ": overflow occured\n" << std::endl;
        }

        cout << "Do you want to stop? (Enter \"y\" or \"Y)" << endl;

        char c = 0;

        do {
            c = getchar();
        } while ((c == '\n') && (c != EOF));

        if (c == 'y' || c == 'Y') {
            endTest = true;
        }

        do {
            c = getchar();
        } while ((c != '\n') && (c != EOF));

    } while (!endTest);
}

4
ไม่ใช่เครื่อง 32 บิตทั้งหมดที่รองรับ Intel x86 และคอมไพเลอร์บางตัวไม่รองรับไวยากรณ์แอสเซมบลี gnu (ฉันคิดว่ามันตลกที่คุณโพสต์รหัสที่ทดสอบ_MSC_VERแม้ว่า MS คอมไพล์จะปฏิเสธรหัสทั้งหมด)
Ben Voigt

2

การจับ Integer Overflows ใน Cชี้ให้เห็นวิธีแก้ปัญหาที่กว้างกว่าที่ CERT กล่าวถึง (เป็นเรื่องทั่วไปในแง่ของประเภทที่มีการจัดการ) แม้ว่าจะต้องใช้ส่วนขยาย GCC บางส่วน (ฉันไม่ทราบว่ามีส่วนสนับสนุนอย่างกว้างขวาง)


2

คุณไม่สามารถเข้าถึงการตั้งค่าสถานะโอเวอร์โฟลว์จาก C / C ++

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

ดูและinfo asinfo gcc


8
แอสเซมเบลอร์แอสเซมเบลอร์ไม่มีคุณลักษณะ C / C ++ และแพลตฟอร์มอิสระ ใน x86 คุณสามารถใช้คำสั่งในสาขาของ btw ได้
Nils Pipenbrinck

0

เพื่อขยายคำตอบของ Head Geek มีวิธีที่เร็วกว่าในการทำaddition_is_safe;

bool addition_is_safe(unsigned int a, unsigned int b)
{
    unsigned int L_Mask = std::numeric_limits<unsigned int>::max();
    L_Mask >>= 1;
    L_Mask = ~L_Mask;

    a &= L_Mask;
    b &= L_Mask;

    return ( a == 0 || b == 0 );
}

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

สิ่งนี้จะเร็วยิ่งขึ้นหากคุณเตรียมรูปแบบล่วงหน้าในตัวสร้างบางตัวเนื่องจากไม่เคยเปลี่ยนแปลง


5
สิ่งนี้ไม่ถูกต้อง Carry อาจนำบิตจากตำแหน่งต่ำกว่าที่จะทำให้เกิดการล้น UINT_MAX + 1พิจารณาการเพิ่ม หลังจากปิดบังaจะมีชุดบิตสูง แต่1จะกลายเป็นศูนย์ดังนั้นฟังก์ชั่นจะกลับมาtrueนอกจากนี้ยังปลอดภัย - แต่คุณมุ่งไปที่ล้นโดยตรง
สุกร

0

mozilla::CheckedInt<T>จัดเตรียมคณิตศาสตร์จำนวนเต็มแบบล้นที่ตรวจสอบสำหรับประเภทจำนวนเต็มT(ใช้คอมไพเลอร์อินทรินใน clang และ gcc เท่าที่มี) รหัสที่อยู่ภายใต้ MPL 2.0 และขึ้นอยู่กับสาม ( IntegerTypeTraits.h, Attributes.hและCompiler.h) ส่วนหัวเท่านั้นที่ไม่ได้มาตรฐานส่วนหัวอื่น ๆ ในห้องสมุดบวก Mozilla เฉพาะเครื่องจักรยืนยัน คุณอาจต้องการแทนที่เครื่องจักรยืนยันหากคุณนำเข้ารหัส


-1

คำตอบของ MSalterเป็นความคิดที่ดี

หากต้องการการคำนวณจำนวนเต็ม (เพื่อความแม่นยำ) แต่มีจุดลอยตัวคุณสามารถทำสิ่งต่อไปนี้

uint64_t foo(uint64_t a, uint64_t b) {
    double dc;

    dc = pow(a, b);

    if (dc < UINT_MAX) {
       return (powu64(a, b));
    }
    else {
      // Overflow
    }
}

โดยปกติฉันจะบอกว่าการคำนวณซ้ำในจุดลอยตัวเป็นความคิดที่ไม่ดี แต่สำหรับกรณีเฉพาะของการยกกำลัง a ^ c มันอาจมีประสิทธิภาพมากกว่า แต่การทดสอบควรจะ(c * log(a) < max_log)อยู่ที่ไหนconst double max_log = log(UINT_MAX)
Toby Speight

-1

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

unsigned long checked_imul(unsigned long a, unsigned long b) {
  unsigned __int128 res = (unsigned __int128)a * b;
  if ((unsigned long)(res >> 64))
    printf("overflow in integer multiply");
  return (unsigned long)res;
}

สำหรับโปรแกรม 32 บิตเราต้องสร้างผลลัพธ์ 64 บิตและพารามิเตอร์ 32 บิต

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


1
คุณควรใช้ประเภท 128- บิตที่ไม่ได้ลงชื่อ__uint128เพื่อหลีกเลี่ยงการโอเวอร์โฟลว์ที่ลงนามแล้วและเลื่อนค่าลบไปทางขวา
chqrlie

อะไรคือ"สัญชาตญาณของคอมไพเลอร์ขึ้นอยู่กับ"และ"สัญชาตญาณล้น" ? คุณหมายถึง" function intrinsic "หรือไม่ คุณมีข้อมูลอ้างอิงหรือไม่? (โปรดตอบกลับโดยแก้ไขคำตอบของคุณไม่ใช่ที่นี่ในความคิดเห็น (ตามความเหมาะสม))
Peter Mortensen

-3
#include <stdio.h>
#include <stdlib.h>

#define MAX 100 

int mltovf(int a, int b)
{
    if (a && b) return abs(a) > MAX/abs(b);
    else return 0;
}

main()
{
    int a, b;

    for (a = 0; a <= MAX; a++)
        for (b = 0; b < MAX; b++) {

        if (mltovf(a, b) != (a*b > MAX)) 
            printf("Bad calculation: a: %d b: %d\n", a, b);

    }
}

-3

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


6
ยกเว้นว่าคุณไม่สามารถแทนที่โอเปอเรเตอร์สำหรับชนิดบิลด์อินได้ คุณต้องเขียนคลาสเพื่อใช้และเขียนรหัสลูกค้าใหม่เพื่อใช้งาน
Blaisorblade

-3

มันขึ้นอยู่กับสิ่งที่คุณใช้สำหรับ ดำเนินการเพิ่มหรือคูณแบบยาวที่ไม่ได้ลงชื่อ (DWORD) ทางออกที่ดีที่สุดคือการใช้ ULARGE_INTEGER

ULARGE_INTEGER เป็นโครงสร้างของสอง DWORD มูลค่าเต็มสามารถเข้าถึงได้เป็น "QuadPart" ในขณะที่เข้าถึง DWORD สูงเป็น "HighPart" และเข้าถึง DWORD ต่ำเป็น "LowPart"

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

DWORD
My Addition(DWORD Value_A, DWORD Value_B)
{
    ULARGE_INTEGER a, b;

    b.LowPart = Value_A;  // A 32 bit value(up to 32 bit)
    b.HighPart = 0;
    a.LowPart = Value_B;  // A 32 bit value(up to 32 bit)
    a.HighPart = 0;

    a.QuadPart += b.QuadPart;

    // If  a.HighPart
    // Then a.HighPart contains the overflow (carry)

    return (a.LowPart + a.HighPart)

    // Any overflow is stored in a.HighPart (up to 32 bits)

6
น่าเสียดายที่นี่เป็นโซลูชันสำหรับ Windows เท่านั้น แพลตฟอร์มอื่น ๆ ULARGE_INTEGERไม่ได้
Mysticial

-3

เพื่อทำการคูณที่ไม่ได้ลงชื่อโดยไม่ล้นในแบบพกพาสามารถใช้ต่อไปนี้:

... /* begin multiplication */
unsigned multiplicand, multiplier, product, productHalf;
int zeroesMultiplicand, zeroesMultiplier;
zeroesMultiplicand = number_of_leading_zeroes( multiplicand );
zeroesMultiplier   = number_of_leading_zeroes( multiplier );
if( zeroesMultiplicand + zeroesMultiplier <= 30 ) goto overflow;
productHalf = multiplicand * ( c >> 1 );
if( (int)productHalf < 0 ) goto overflow;
product = productHalf * 2;
if( multiplier & 1 ){
   product += multiplicand;
   if( product < multiplicand ) goto overflow;
}
..../* continue code here where "product" is the correct product */
....
overflow: /* put overflow handling code here */

int number_of_leading_zeroes( unsigned value ){
   int ctZeroes;
   if( value == 0 ) return 32;
   ctZeroes = 1;
   if( ( value >> 16 ) == 0 ){ ctZeroes += 16; value = value << 16; }
   if( ( value >> 24 ) == 0 ){ ctZeroes +=  8; value = value <<  8; }
   if( ( value >> 28 ) == 0 ){ ctZeroes +=  4; value = value <<  4; }
   if( ( value >> 30 ) == 0 ){ ctZeroes +=  2; value = value <<  2; }
   ctZeroes -= x >> 31;
   return ctZeroes;
}

-4

วิธีง่ายๆในการทดสอบการโอเวอร์โฟลว์คือการตรวจสอบความถูกต้องโดยการตรวจสอบว่าค่าปัจจุบันน้อยกว่าค่าก่อนหน้าหรือไม่ ตัวอย่างเช่นสมมติว่าคุณมีลูปเพื่อพิมพ์พาวเวอร์ 2:

long lng;
int n;
for (n = 0; n < 34; ++n)
{
   lng = pow (2, n);
   printf ("%li\n", lng);
}

เพิ่มล้นตรวจสอบวิธีที่ฉันอธิบายผลลัพธ์ในนี้:

long signed lng, lng_prev = 0;
int n;
for (n = 0; n < 34; ++n)
{
    lng = pow (2, n);
    if (lng <= lng_prev)
    {
        printf ("Overflow: %i\n", n);
        /* Do whatever you do in the event of overflow.  */
    }
    printf ("%li\n", lng);
    lng_prev = lng;
}

มันใช้งานได้สำหรับค่าที่ไม่ได้ลงนามเช่นเดียวกับค่าลงนามในเชิงบวกและเชิงลบ

แน่นอนถ้าคุณต้องการทำสิ่งที่คล้ายกันสำหรับการลดค่าแทนที่จะเพิ่มค่าคุณจะพลิก<=เครื่องหมายเพื่อทำมัน>=สมมติว่าพฤติกรรมของอันเดอร์โฟลว์นั้นเหมือนกับพฤติกรรมของล้น ในความซื่อสัตย์ทั้งหมดนั้นเกี่ยวกับการพกพาที่คุณจะได้รับโดยไม่ต้องเข้าถึงการโอเวอร์โฟลว์ของซีพียู (และต้องใช้รหัสแอสเซมบลีแบบอินไลน์


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