Làm thế nào để tôi phát hiện tràn số nguyên không dấu?


618

Tôi đang viết một chương trình trong C ++ để tìm tất cả các giải pháp của một b = c , nơi một , bc cùng nhau sử dụng tất cả các chữ số 0-9 đúng một lần. Chương trình looped trên giá trị của mộtb , và nó chạy một thói quen chữ số đếm mỗi lần trên một , bmột b để kiểm tra xem tình trạng chữ số đã được thỏa mãn.

Tuy nhiên, các giải pháp giả mạo có thể được tạo ra khi một b tràn giới hạn số nguyên. Tôi đã kết thúc việc kiểm tra điều này bằng cách sử dụng mã như:

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ách nào tốt hơn để kiểm tra tràn? Tôi biết rằng một số chip có cờ nội bộ được đặt khi tràn xảy ra, nhưng tôi chưa bao giờ thấy nó được truy cập thông qua C hoặc C ++.


Coi chừng rằng tràn tràn đã ký int là hành vi không xác định trong C và C ++ , và do đó bạn phải phát hiện ra nó mà không thực sự gây ra nó. Đối với tràn int đã ký trước khi thêm, hãy xem Phát hiện tràn tràn đã ký trong C / C ++ .


21
Thông tin có thể hữu ích về chủ đề này: Chương 5 của "Mã hóa an toàn trong C và C ++" của Seacord - http://www.informit.com/content/images/0321335724/samplech CHƯƠNG / khử_ch05.pdf Các lớp SafeInt cho C ++ - http : //bloss.msdn.com/david_leblanc/archive/2008/09/30/safeint-3-on-codeplex.aspx - http://www.codeplex.com/SafeInt Thư viện IntSafe cho C: - [ blog.msdn .com / michael_howard / archiv
Michael Burr

3
Mã hóa bảo mật của Seacord là một tài nguyên tuyệt vời, nhưng không sử dụng IntegerLib. Xem blog.regehr.org/archives/593 .
jww

32
Tùy chọn trình biên dịch gcc -ftrapvsẽ khiến nó tạo ra một tràn số nguyên SIGABRT trên (đã ký). Xem ở đây .
nibot

1
Nó không trả lời câu hỏi tràn, nhưng một cách khác để giải quyết vấn đề là sử dụng thư viện BigNum như GMP để đảm bảo bạn luôn có đủ độ chính xác. Bạn sẽ không phải lo lắng về việc tràn nếu bạn phân bổ đủ chữ số lên phía trước.
văn

1
Thông tin được cung cấp bởi @HeadGeek trong câu trả lời của anh ấy là khá nhiều những gì tôi sẽ nói là tốt. Tuy nhiên, với một bổ sung. Cách bạn đang phát hiện tràn ra để nhân lên bây giờ có lẽ là nhanh nhất. Trên ARM như tôi đã nhận xét trong câu trả lời của HeadGeek, bạn có thể sử dụng clzhướng dẫn hoặc __clz(unsigned)hàm để xác định thứ hạng của số (nơi có bit cao nhất của nó). Vì tôi không chắc chắn liệu điều này có sẵn trên x86 hay x64 hay không, tôi sẽ cho rằng nó không phải và nói rằng việc tìm kiếm bit quan trọng nhất sẽ thực hiện theo các log(sizeof(int)*8)hướng dẫn tồi tệ nhất .
vô nghĩa

Câu trả lời:


229

Tôi thấy bạn đang sử dụng số nguyên không dấu. Theo định nghĩa, trong C (tôi không biết về C ++), số học không dấu không tràn ... vì vậy, ít nhất là đối với C, quan điểm của bạn là moot :)

Với các số nguyên đã ký, một khi đã có tràn, hành vi không xác định (UB) đã xảy ra và chương trình của bạn có thể làm bất cứ điều gì (ví dụ: kết xuất các bài kiểm tra không kết luận). 

#include <limits.h>

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

Để tạo một chương trình phù hợp, bạn cần kiểm tra tràn trước khi tạo tràn. Phương thức này cũng có thể được sử dụng với các số nguyên không dấu:

// 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 */;

Đối với bộ phận (trừ trường hợp INT_MIN-1trường hợp đặc biệt), không có bất kỳ khả năng đi qua INT_MINhay INT_MAX.


97
Các số nguyên không được ký không tràn hoàn toàn trong C ++ (ISO / IEC 14882: 2003 3.9.1.4). Việc tôi sử dụng 'tràn' trong câu hỏi là ý nghĩa thông tục hơn, dự định bao gồm các kiểu không dấu được xác định rõ, vì tôi quan tâm đến số nguyên không dấu đại diện cho số nguyên dương toán học, không phải số nguyên dương mod 2 ^ 32 (hoặc 2 ^ 64). Sự khác biệt giữa tràn như một sai lệch so với hành vi số nguyên có kích thước vô hạn toán học và tràn như một hành vi không xác định trong ngôn ngữ dường như hiếm khi được làm rõ ràng.
Chris Johnson

15
Thử nghiệm đó không cần phải x >= 0- x > 0sẽ đủ (nếu x == 0, sau đó x + akhông thể tràn vì những lý do rõ ràng).
phê

2
@pmg, có trích dẫn hỗ trợ từ tiêu chuẩn không?
Pacerier

5
Tôi thích cách tiếp cận này ... Tuy nhiên, hãy cẩn thận: phát hiện tràn nhân với giả định x dương. Đối với x == 0, nó dẫn đến chia cho phát hiện 0 và đối với x âm, nó luôn phát hiện lỗi tràn.
Franz D.

4
if ((a < INT_MIN / x))kiểm tra là quá muộn. Một if (x == -1) bài kiểm tra là cần thiết đầu tiên.
chux - Phục hồi Monica

164

một cách để xác định liệu một hoạt động có khả năng tràn, sử dụng vị trí của một bit nhiều nhất có ý nghĩa trong toán hạng và một chút kiến thức cơ bản nhị phân toán học.

Ngoài ra, bất kỳ hai toán hạng nào cũng sẽ dẫn đến (nhiều nhất) một bit nhiều hơn một bit cao nhất của toán hạng lớn nhất. Ví dụ:

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);
}

Để nhân, bất kỳ hai toán hạng nào cũng sẽ dẫn đến (nhiều nhất) tổng số bit của toán hạng. Ví dụ:

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);
}

Tương tự, bạn có thể ước tính kích thước tối đa của kết quả acho sức mạnh bnhư thế này:

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

(Tất nhiên thay thế số bit cho số nguyên mục tiêu của bạn.)

Tôi không chắc chắn về cách nhanh nhất để xác định vị trí của một bit cao nhất trong một số, đây là phương pháp vũ phu:

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

Nó không hoàn hảo, nhưng điều đó sẽ cho bạn ý tưởng tốt cho dù hai số nào có thể tràn ra trước khi bạn thực hiện thao tác. Tôi không biết liệu nó có nhanh hơn việc kiểm tra kết quả theo cách bạn đề xuất hay không, bởi vì vòng lặp trong highestOneBitPositionhàm, nhưng nó có thể (đặc biệt là nếu bạn biết trước đó có bao nhiêu bit trong toán hạng).


98
và tất nhiên bạn có thể đổi tên cao nhấtOneBitP vị trí để đăng nhập :)
Oliver Hallam

37
Vâng, đó là hoạt động tương tự log2, nhưng điều đó không nhất thiết phải rõ ràng đối với người không có nền tảng toán học.
Head Geek

48
Không thuật toán này đánh giá thấp các câu trả lời an toàn? 2 ^ 31 + 0 sẽ phát hiện là không an toàn vì highOneBitP vị trí (2 ^ 31) = 32. (2 ^ 32 - 1) * 1 sẽ phát hiện là không an toàn vì 32 + 1> 32. 1 ^ 100 sẽ phát hiện là không an toàn vì 1 * 100 > 32.
clahey

19
theo mức multiplication_is_safe 0x8000 * 0x10000tràn của bạn (vị trí bit là 16 + 17 = 33 là > 32 ), mặc dù điều đó không phải vì 0x8000 * 0x10000 = 0x80000000điều đó rõ ràng vẫn phù hợp với int 32 bit không dấu. Đây chỉ là một trong những ví dụ có thể mà mã này không hoạt động. 0x8000 * 0x10001, ...
Michi

13
@GT_mh: Quan điểm của bạn? Như tôi đã nói, nó không hoàn hảo; đó là một quy tắc-of-thumb rằng dứt khoát sẽ nói khi có điều gì an toàn, nhưng không có cách nào để xác định xem mỗi tính sẽ ổn mà không làm đầy đủ các tính toán. 0x8000 * 0x10000không phải là "an toàn", theo định nghĩa này, mặc dù hóa ra là ổn.
Head Geek

147

Clang 3,4+GCC 5+ cung cấp các nội dung số học được kiểm tra. Họ cung cấp một giải pháp rất nhanh cho vấn đề này, đặc biệt khi so sánh với kiểm tra an toàn kiểm tra bit.

Đối với ví dụ trong câu hỏi của OP, nó sẽ hoạt động như thế này:

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
}

Tài liệu Clang không chỉ định liệu c_testcó chứa kết quả tràn nếu xảy ra tràn hay không, nhưng tài liệu GCC nói rằng có. Cho rằng hai cái này muốn __builtintương thích với nhau, có lẽ an toàn khi cho rằng đây cũng là cách Clang hoạt động.

Có một __builtinhoạt động cho mỗi phép tính số học có thể tràn (cộng, trừ, nhân), với các biến thể được ký và không dấu, cho kích thước int, kích thước dài và kích thước dài. Cú pháp của tên là __builtin_[us](operation)(l?l?)_overflow:

  • ucho unsigned hoặc scho ;
  • hoạt động là một trong add, subhoặc mul;
  • không có lhậu tố có nghĩa là các toán hạng là ints; một lphương tiện long; hai ls có nghĩa long long.

Vì vậy, đối với một bổ sung số nguyên dài đã ký kiểm tra, nó sẽ được __builtin_saddl_overflow. Danh sách đầy đủ có thể được tìm thấy trên trang tài liệu Clang .

GCC 5+ và Clang 3.8+ bổ sung cung cấp builtins generic rằng công việc mà không chỉ định các loại giá trị: __builtin_add_overflow, __builtin_sub_overflow__builtin_mul_overflow. Chúng cũng hoạt động trên các loại nhỏ hơn int.

Các nội trang thấp hơn những gì tốt nhất cho nền tảng. Trên x86, họ kiểm tra cờ mang, tràn và ký cờ.

Cl.exe của Visual Studio không có tương đương trực tiếp. Đối với các phép cộng và phép trừ không dấu, bao gồm <intrin.h>sẽ cho phép bạn sử dụng addcarry_uNNsubborrow_uNN(trong đó NN là số bit, như addcarry_u8hoặc subborrow_u64). Chữ ký của họ là một chút khó hiểu:

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_inlà cờ mang / mượn trên đầu vào và giá trị trả về là giá trị mang / mượn trên đầu ra. Nó dường như không có tương đương cho các hoạt động đã ký hoặc nhân.

Mặt khác, Clang cho Windows hiện đã sẵn sàng sản xuất (đủ tốt cho Chrome), do đó, đó cũng có thể là một tùy chọn.


__builtin_sub_overflowchắc chắn không có trong Clang 3.4.
Richard Cook

2
@RichardCook, phải mất một thời gian nhưng Clang có các nội dung chung như phiên bản 3.9.
zneak

@tambre, tôi không nghĩ là có.
zneak

4
Theo các tài liệu , __builtin_add_overflowvà bạn bè đã có sẵn trên Clang 3.8.
Lekensteyn

2
Cảm ơn. Điều này làm việc tuyệt vời. Bất cứ ý tưởng chức năng tương ứng cho trực quan c ++ là gì? Dường như không thể tìm thấy chúng.
Mudit Jain

53

Một số trình biên dịch cung cấp quyền truy cập vào cờ tràn số nguyên trong CPU mà sau đó bạn có thể kiểm tra nhưng điều này không chuẩn.

Bạn cũng có thể kiểm tra khả năng tràn trước khi bạn thực hiện phép nhân:

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

11
... hoặc sử dụng num_limits <TYPE> :: max ()
Jonas Gulle

20
Đừng quên xử lý a = 0 - chia nhỏ sau đó.
Thelema

16
@Thelema: "Đừng quên xử lý a = 0" - và INT_MIN / -1.
jww

1
Nếu như b == ULONG_MAX / a? Sau đó, nó vẫn có thể phù hợp, cho rằng aphân chia ULONG_MAXmà không dư.
con lợn

Thật buồn cười, hiệu suất khôn ngoan, phép nhân khá nhanh so với phép chia và bạn đang thêm phép chia cho mỗi phép nhân. Điều này không có âm thanh giống như các giải pháp.
DrumM

40

Cảnh báo: GCC có thể tối ưu hóa kiểm tra tràn khi biên dịch -O2. Tùy chọn -Wallnày sẽ đưa ra cảnh báo trong một số trường hợp như

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

nhưng không phải trong ví dụ này:

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

Cách an toàn duy nhất là kiểm tra tràn trước khi nó xảy ra, như được mô tả trong bài báo CERT , và điều này sẽ vô cùng tẻ nhạt khi sử dụng một cách có hệ thống.

Biên dịch với -fwrapvgiải quyết vấn đề, nhưng vô hiệu hóa một số tối ưu hóa.

Chúng tôi rất cần một giải pháp tốt hơn. Tôi nghĩ rằng trình biên dịch nên đưa ra cảnh báo theo mặc định khi thực hiện tối ưu hóa dựa trên tràn không xảy ra. Tình huống hiện tại cho phép trình biên dịch tối ưu hóa kiểm tra tràn, theo ý kiến ​​của tôi là không thể chấp nhận được.


8
Lưu ý rằng trình biên dịch chỉ có thể làm điều này với các kiểu số nguyên đã ký ; tràn được xác định hoàn toàn cho các loại số nguyên không dấu. Tuy nhiên, vâng, đó là một cái bẫy khá nguy hiểm!
SamB

1
"Tôi nghĩ rằng trình biên dịch nên đưa ra cảnh báo theo mặc định khi thực hiện tối ưu hóa dựa trên tràn không xảy ra." - vậy for(int k = 0; k < 5; k++) {...}có nên đưa ra cảnh báo?
dùng253751

2
@immibis: Tại sao nên? Các giá trị kcó thể dễ dàng được xác định tại thời điểm biên dịch. Trình biên dịch không phải thực hiện bất kỳ giả định nào.
MikeMB

2
@immibis: Để trích dẫn ở trên: "Tôi nghĩ trình biên dịch nên đưa ra cảnh báo theo mặc định khi thực hiện tối ưu hóa dựa trên tràn không xảy ra."
MikeMB

1
@MikeMB Việc tối ưu hóa trong đó trình biên dịch không bận tâm kiểm tra mức nnhỏ hơn 32, trước khi phát ra lệnh dịch chuyển chỉ sử dụng 5 bit thấp hơn n?
user253751

30

Clang hiện hỗ trợ kiểm tra tràn động cho cả số nguyên đã ký và không dấu. Xem công tắc -fsanitize = số nguyên . Hiện tại, nó là trình biên dịch C ++ duy nhất có kiểm tra tràn động được hỗ trợ đầy đủ cho mục đích gỡ lỗi.


25

Tôi thấy rằng rất nhiều người đã trả lời câu hỏi về tràn, nhưng tôi muốn giải quyết vấn đề ban đầu của anh ấy. Ông nói rằng vấn đề là tìm một b = c sao cho tất cả các chữ số được sử dụng mà không lặp lại. Ok, đó không phải là những gì anh ấy đã hỏi trong bài viết này, nhưng tôi vẫn nghĩ rằng cần phải nghiên cứu giới hạn trên của vấn đề và kết luận rằng anh ấy sẽ không bao giờ cần phải tính toán hoặc phát hiện một lỗi tràn (lưu ý: Tôi không thành thạo trong toán học nên tôi đã làm điều này từng bước một, nhưng kết quả cuối cùng đơn giản đến mức điều này có thể có một công thức đơn giản).

Điểm chính là giới hạn trên mà vấn đề yêu cầu đối với a, b hoặc c là 98.765.432. Dù sao, bắt đầu bằng cách phân chia vấn đề trong các phần tầm thường và không tầm thường:

  • x 0 == 1 (tất cả các hoán vị của 9, 8, 7, 6, 5, 4, 3, 2 là các giải pháp)
  • x 1 == x (không có giải pháp nào khả thi)
  • 0 b == 0 (không có giải pháp nào khả thi)
  • 1 b == 1 (không có giải pháp nào khả thi)
  • a b , a> 1, b> 1 (không tầm thường)

Bây giờ chúng ta chỉ cần chỉ ra rằng không có giải pháp nào khả thi và chỉ có hoán vị là hợp lệ (và sau đó mã để in chúng là tầm thường). Chúng tôi quay trở lại giới hạn trên. Trên thực tế giới hạn trên là c ≤ 98.765.432. Đó là giới hạn trên bởi vì đó là số lớn nhất có 8 chữ số (tổng cộng 10 chữ số trừ 1 cho mỗi a và b). Giới hạn trên này chỉ dành cho c vì giới hạn của a và b phải thấp hơn nhiều vì sự tăng trưởng theo cấp số nhân, như chúng ta có thể tính toán, thay đổi b từ 2 đến giới hạn trên:

    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

Lưu ý, ví dụ dòng cuối cùng: nó nói rằng 1,97 ^ 27 ~ 98M. Vì vậy, ví dụ: 1 ^ 27 == 1 và 2 ^ 27 == 134.217.728 và đó không phải là giải pháp vì nó có 9 chữ số (2> 1.97 nên thực sự lớn hơn những gì cần kiểm tra). Như có thể thấy, các kết hợp có sẵn để thử nghiệm a và b thực sự rất nhỏ. Đối với b == 14, chúng ta cần thử 2 và 3. Với b == 3, chúng ta bắt đầu từ 2 và dừng ở 462. Tất cả các kết quả được cấp dưới ~ 98M.

Bây giờ chỉ cần kiểm tra tất cả các kết hợp ở trên và tìm kiếm những kết hợp không lặp lại bất kỳ chữ số nào:

    ['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)

Không ai trong số họ phù hợp với vấn đề (điều này cũng có thể được nhìn thấy khi không có '0', '1', ..., '9').

Mã ví dụ giải quyết nó sau. Cũng lưu ý rằng được viết bằng Python, không phải vì nó cần các số nguyên chính xác tùy ý (mã không tính toán bất cứ thứ gì lớn hơn 98 triệu), nhưng vì chúng tôi phát hiện ra rằng số lượng thử nghiệm quá nhỏ nên chúng tôi nên sử dụng ngôn ngữ cấp cao để sử dụng các thùng chứa và thư viện tích hợp của nó (cũng lưu ý: mã có 28 dòng).

    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
Tại sao bạn không sử dụng 9.876.543.210 làm giới hạn trên?
Tom Roggero

3
Bởi vì 2 chữ số phải được sử dụng cho phía bên trái của phương trình.
hdante

2
Không phải là nó tạo ra sự khác biệt, nhưng giới hạn trên thực tế có thể được lấy là 98765410 như bạn đã nêu các giá trị trên LHS là> 1
Paul Childs

24

Đây là một giải pháp "không di động" cho câu hỏi. Các CPU Intel x86 và x64 có cái gọi là thanh ghi EFLAGS , được bộ xử lý điền vào sau mỗi hoạt động số học số nguyên. Tôi sẽ bỏ qua một mô tả chi tiết ở đây. Các cờ có liên quan là Cờ "Tràn" (mặt nạ 0x800) và Cờ "Mang theo" (mặt nạ 0x1). Để giải thích chúng một cách chính xác, người ta nên xem xét nếu các toán hạng thuộc loại đã ký hoặc không dấu.

Đây là một cách thực tế để kiểm tra các cờ từ C / C ++. Đoạn mã sau sẽ hoạt động trên Visual Studio 2005 hoặc mới hơn (cả 32 và 64 bit), cũng như trên GNU C / C ++ 64 bit.

#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);
}

Nếu các toán hạng được nhân lên mà không bị tràn, bạn sẽ nhận được giá trị trả về là 0 từ query_intel_eflags(0x801), tức là cả cờ mang và cờ tràn đều không được đặt. Trong mã ví dụ được cung cấp của hàm main (), xảy ra tràn và cả hai cờ được đặt thành 1. Kiểm tra này không ngụ ý bất kỳ phép tính nào nữa, vì vậy nó sẽ khá nhanh.


21

Nếu bạn có một kiểu dữ liệu lớn hơn kiểu dữ liệu bạn muốn kiểm tra (giả sử bạn thực hiện thêm 32 bit và bạn có loại 64 bit), thì điều này sẽ phát hiện nếu xảy ra tràn. Ví dụ của tôi là cho một add 8 bit. Nhưng nó có thể được thu nhỏ lại.

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);

Nó dựa trên các khái niệm được giải thích trên trang này: http://www.cs.umd.edu/group/spring2003/cmsc311/Notes/Comb/overflow.html

Đối với một ví dụ 32 bit, 0xFFtrở thành 0xFFFFFFFF0x80trở thành 0x80000000và cuối cùng uint16_ttrở thành a uint64_t.

LƯU Ý : điều này nắm bắt được phép cộng / trừ số nguyên và tôi nhận ra rằng câu hỏi của bạn liên quan đến phép nhân. Trong trường hợp đó, phân chia có khả năng là cách tiếp cận tốt nhất. Đây thường là cách mà callocviệc triển khai đảm bảo rằng các tham số không tràn khi chúng được nhân lên để có kích thước cuối cùng.


Liên kết bị hỏng: HTTP 403: Bị cấm
Peter Mortensen

18

Cách đơn giản nhất là chuyển đổi unsigned longs của bạn thành unsigned long longs, thực hiện phép nhân của bạn và so sánh kết quả với 0x100000000LL.

Bạn có thể sẽ thấy rằng điều này hiệu quả hơn so với việc thực hiện phép chia như bạn đã làm trong ví dụ của mình.

Ồ, và nó sẽ hoạt động ở cả C và C ++ (vì bạn đã gắn thẻ câu hỏi với cả hai).


Chỉ cần nhìn vào hướng dẫn sử dụng glibc . Có một đề cập đến một bẫy tràn số nguyên ( FPE_INTOVF_TRAP) như là một phần của SIGFPE. Đó sẽ là lý tưởng, ngoài các bit khó chịu trong hướng dẫn:

FPE_INTOVF_TRAP Tràn số nguyên (không thể trong chương trình C trừ khi bạn bật bẫy tràn theo kiểu dành riêng cho phần cứng).

Một chút xấu hổ thực sự.


4
Heh ... điều tôi không nói là tôi đang hỏi câu hỏi này để chuẩn bị viết một chương trình để giải quyết vấn đề với số lượng lớn hơn, trong đó tôi đã sử dụng int dài. Vì int dài dài không (được cho là) ​​trong tiêu chuẩn C ++, tôi đã sử dụng phiên bản 32 bit để tránh nhầm lẫn.
Chris Johnson

Tôi khuyên bạn nên sử dụng ULONG_MAXloại dễ nhập và dễ di chuyển hơn mã hóa cứng 0x100000000.
jw013

24
Điều này không hoạt động khi longlong longcó cùng kích thước (ví dụ: trên nhiều trình biên dịch 64 bit).
interjay

Dựa vào các tín hiệu để cho bạn biết về tràn tràn dù sao cũng sẽ rất chậm.
SamB

@SamB Chỉ khi tràn ra được dự kiến ​​là thường xuyên.
dùng253751

17

Đây là một cách thực sự nhanh chóng để phát hiện tràn cho ít nhất các bổ sung, có thể dẫn đến sự nhân lên, chia và tăng sức mạnh.

Ý tưởng là chính xác bởi vì bộ xử lý sẽ chỉ để giá trị trở về 0 và C / C ++ sẽ được trừu tượng hóa từ bất kỳ bộ xử lý cụ thể nào, bạn có thể:

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

Cả hai điều này đảm bảo rằng nếu một toán hạng bằng 0 và một không, thì tràn sẽ không bị phát hiện sai và nhanh hơn đáng kể so với nhiều hoạt động kiểm tra KHÔNG / XOR / AND / như đề xuất trước đây.

Như đã chỉ ra, cách tiếp cận này, mặc dù tốt hơn so với các cách khác phức tạp hơn, nhưng vẫn tối ưu. Sau đây là bản sửa đổi của mã gốc có chứa tối ưu hóa:

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

Một cách hiệu quả và rẻ hơn để phát hiện tràn nhân là:

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

Điều này dẫn đến UINT32_MAX khi tràn hoặc kết quả của phép nhân. Đây là hành vi không được xác định nghiêm ngặt để cho phép phép nhân tiến hành cho số nguyên đã ký trong trường hợp này.


Tôi không đồng ý do lý thuyết tính toán .. hãy xem xét các yếu tố sau: y> x, giá trị tràn, y chỉ lớn hơn x do bit dấu được đặt (ví dụ 1 + 255, đối với ký tự không dấu) giá trị thử nghiệm và x sẽ cho kết quả trong tràn = false - do đó sử dụng logic hoặc để ngăn chặn hành vi bị hỏng này ..
DX-MON

Kiểm tra hoạt động cho các số bạn đưa ra (x: = 1, y: = 255, size = uint8_t): giá trị sẽ là 0 (1 + 255) và 0 <1 là đúng. Nó thực sự hoạt động cho mỗi cặp số.
Gunther Piez

Hmm, bạn làm cho một điểm tốt. Tôi vẫn giữ khía cạnh an toàn bằng cách sử dụng hoặc mẹo mặc dù bất kỳ trình biên dịch tốt nào cũng sẽ tối ưu hóa nhà cung cấp, bạn thực sự đúng cho tất cả các đầu vào, bao gồm cả các số không tràn như "0 + 4" trong đó kết quả sẽ không bị tràn.
DX-MON

4
Nếu có tràn, hơn x+y>=256value=x+y-256. Bởi vì y<256luôn luôn đúng, (y-256) là âm và vì vậy value < xluôn luôn đúng. Bằng chứng cho trường hợp không tràn là khá giống nhau.
Gunther Piez

2
@ DX-MON: Phương pháp đầu tiên của bạn là cần thiết nếu bạn cũng có một bit carry từ một add trước đó. 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]); }Nếu bạn không có orcác giá trị, bạn sẽ không thể phân biệt giữa một toán hạng và bit mang là 0 và một toán hạng 0xffffffffvà bit mang là một.
Matt

14

Bạn không thể truy cập cờ tràn từ C / C ++.

Một số trình biên dịch cho phép bạn chèn các hướng dẫn bẫy vào mã. Trên GCC tùy chọn là -ftrapv.

Điều độc lập di động và trình biên dịch duy nhất bạn có thể làm là tự mình kiểm tra các lỗi tràn. Giống như bạn đã làm trong ví dụ của bạn.

Tuy nhiên, -ftrapvdường như không làm gì trên x86 bằng GCC mới nhất. Tôi đoán đó là một phần còn lại từ một phiên bản cũ hoặc cụ thể cho một số kiến ​​trúc khác. Tôi đã mong đợi trình biên dịch sẽ chèn một opcode INTO sau mỗi lần thêm. Thật không may, nó không làm điều này.


Có thể nó khác nhau: -ftrapv dường như hoạt động tốt khi sử dụng GCC 4.3.4 trên hộp Cygwin. Có một ví dụ tại stackoverflow.com/questions/5005379/ trên
Nate Kohl

3
Cả hai bạn đều đúng. -ftrapv thực hiện công việc nhưng chỉ dành cho số nguyên đã ký
ZAB

14

Đối với các số nguyên không dấu, chỉ cần kiểm tra xem kết quả nhỏ hơn một trong các đối số:

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

Đối với số nguyên đã ký, bạn có thể kiểm tra các dấu hiệu của các đối số và kết quả.

Số nguyên của các dấu hiệu khác nhau không thể tràn và các số nguyên của cùng một dấu hiệu tràn chỉ khi kết quả là một dấu hiệu khác nhau:

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

Vâng, phương pháp đầu tiên cũng sẽ hoạt động đối với các số nguyên đã ký, phải không? char result = (char)127 + (char)3;sẽ là -126; nhỏ hơn cả hai toán hạng.
primfaktor

1
Ồ tôi hiểu rồi, vấn đề là thực tế là nó không được xác định cho các loại đã ký.
primfaktor

27
-1 tràn số đã ký dẫn đến hành vi không xác định (do đó kiểm tra quá muộn để thực sự hữu ích).
Voo

1
@primfaktor không hoạt động đối với int int: char ((- 127) + (-17)) = 112. Đối với int đã ký, bạn phải kiểm tra bit dấu của các đối số và kết quả
phuclv

3
Như đã nêu, giải pháp cho số nguyên đã ký không hoạt động do hành vi không xác định của a + b trong trường hợp tràn. Kiểm tra tràn với số nguyên đã ký phải được thực hiện trước khi hoạt động.
Marwan Burelle

11

Tôi cần phải trả lời câu hỏi tương tự cho các số dấu phẩy động, trong đó mặt nạ bit và dịch chuyển không có vẻ hứa hẹn. Cách tiếp cận tôi giải quyết trên các công trình cho các số có dấu và số nguyên, dấu và số nguyên. Nó hoạt động ngay cả khi không có loại dữ liệu lớn hơn để quảng bá cho các tính toán trung gian. Nó không phải là hiệu quả nhất cho tất cả các loại này, nhưng vì nó hoạt động cho tất cả chúng, nên nó đáng để sử dụng.

Đã ký kiểm tra tràn, phép cộng và phép trừ:

  1. Lấy các hằng số đại diện cho các giá trị lớn nhất và nhỏ nhất có thể cho loại, MAXVALUE và MINVALUE.

  2. Tính toán và so sánh các dấu của toán hạng.

    a. Nếu một trong hai giá trị bằng 0, thì cả phép cộng và phép trừ đều không thể tràn. Bỏ qua các bài kiểm tra còn lại.

    b. Nếu các dấu hiệu ngược lại, thì bổ sung không thể tràn. Bỏ qua các bài kiểm tra còn lại.

    c. Nếu các dấu hiệu giống nhau, thì phép trừ không thể tràn. Bỏ qua các bài kiểm tra còn lại.

  3. Kiểm tra mức tràn dương của MAXVALUE.

    a. Nếu cả hai dấu hiệu đều dương và MAXVALUE - A <B, thì phép cộng sẽ tràn.

    b. Nếu dấu của B là âm và MAXVALUE - A <-B, thì phép trừ sẽ tràn.

  4. Kiểm tra độ tràn âm của MINVALUE.

    a. Nếu cả hai dấu âm và MINVALUE - A> B, thì phép cộng sẽ tràn.

    b. Nếu dấu của A là âm và MINVALUE - A> B, thì phép trừ sẽ tràn.

  5. Nếu không, không tràn.

Đã ký kiểm tra tràn, nhân và chia:

  1. Lấy các hằng số đại diện cho các giá trị lớn nhất và nhỏ nhất có thể cho loại, MAXVALUE và MINVALUE.

  2. Tính toán và so sánh độ lớn (giá trị tuyệt đối) của các toán hạng với một. (Dưới đây, giả sử A và B là các cường độ này, không phải là bản gốc đã ký.)

    a. Nếu một trong hai giá trị bằng 0, phép nhân không thể tràn và phép chia sẽ mang lại 0 hoặc vô cùng.

    b. Nếu một trong hai giá trị là một, phép nhân và phép chia không thể tràn.

    c. Nếu cường độ của một toán hạng dưới một và toán tử kia lớn hơn một, thì phép nhân không thể tràn.

    d. Nếu cường độ đều nhỏ hơn một, phép chia không thể tràn.

  3. Kiểm tra mức tràn dương của MAXVALUE.

    a. Nếu cả hai toán hạng đều lớn hơn một và MAXVALUE / A <B, thì phép nhân sẽ tràn.

    b. Nếu B nhỏ hơn một và MAXVALUE * B <A, thì phép chia sẽ tràn.

  4. Nếu không, không tràn.

Lưu ý: Tràn tối thiểu MINVALUE được xử lý bởi 3, vì chúng tôi đã lấy các giá trị tuyệt đối. Tuy nhiên, nếu ABS (MINVALUE)> MAXVALUE, thì chúng ta sẽ có một số dương tính giả hiếm gặp.

Các thử nghiệm cho dòng chảy tương tự, nhưng liên quan đến EPSILON (số dương nhỏ nhất lớn hơn 0).


1
Trên các hệ thống POSIX ít nhất, tín hiệu SIGFPE có thể được bật cho điểm nổi dưới / tràn.
Chris Johnson

Trong khi chuyển đổi sang dấu phẩy động và quay lại hoạt động, nó (theo thử nghiệm của tôi trên máy 32 bit) chậm hơn nhiều so với các giải pháp khác.
JanKanis

Một nhà phê bình đã phát hiện một trường hợp mất tích để trừ phần 2. Tôi đồng ý rằng 0 - MINVALUE sẽ tràn. Vì vậy, thử nghiệm cho trường hợp này nên được thêm vào.
Paul Chernoch

<pedantic> Số nguyên không chảy xuống (= trở nên quá gần với 0 để được biểu diễn với bất kỳ độ chính xác nào). 1.0e-200 / 1.0e200sẽ là một ví dụ về một dòng chảy thực tế, giả sử IEEE tăng gấp đôi. Thay vào đó, thuật ngữ chính xác ở đây là tràn âm. </ Pedantic>
Arne Vogel

Nói chính xác, lý do tại sao các số nguyên không được coi là dưới mức là do hành vi cắt cụ thể đã xác định, ví dụ, 1/INT_MAXcó thể được coi là tràn, nhưng ngôn ngữ chỉ đơn giản là bắt buộc cắt ngắn về 0.
Arne Vogel

8

CERT đã phát triển một cách tiếp cận mới để phát hiện và báo cáo tràn số nguyên đã ký, gói số nguyên không dấu và cắt ngắn số nguyên bằng cách sử dụng mô hình số nguyên vô hạn "as-if" (AIR). CERT đã xuất bản một báo cáo kỹ thuật mô tả mô hình và tạo ra một nguyên mẫu hoạt động dựa trên GCC 4.4.0 và GCC 4.5.0.

Mô hình số nguyên AIR hoặc tạo ra một giá trị tương đương với giá trị có thể thu được bằng cách sử dụng các số nguyên có phạm vi vô hạn hoặc dẫn đến vi phạm ràng buộc thời gian chạy. Không giống như các mô hình số nguyên trước đó, số nguyên AIR không yêu cầu bẫy chính xác và do đó không phá vỡ hoặc ức chế hầu hết các tối ưu hóa hiện có.


Tôi không thấy bất cứ điều gì hữu ích tại liên kết, nhưng âm thanh đó giống như một mô hình mà tôi đã ủng hộ từ lâu. Nó hỗ trợ phần lớn các tối ưu hóa hữu ích, đồng thời hỗ trợ các đảm bảo ngữ nghĩa hữu ích mà hầu hết các triển khai có thể cung cấp miễn phí. Nếu mã biết rằng đầu vào của hàm sẽ hợp lệ trong mọi trường hợp đầu ra có vấn đề , nhưng không biết trước liệu đầu ra có quan trọng hay không, có thể để tràn xảy ra trong trường hợp chúng không ảnh hưởng đến bất cứ điều gì có thể dễ dàng và hiệu quả hơn là phải ngăn chặn chúng bằng mọi giá.
supercat

8

Một công cụ thú vị khác là IOC: Trình kiểm tra tràn số nguyên cho C / C ++ .

Đây là trình biên dịch Clang đã vá , bổ sung kiểm tra mã vào thời gian biên dịch.

Bạn nhận được đầu ra trông như thế này:

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

1
Bản vá này hiện đã được hợp nhất để clang codebase giữa các chất khử trùng khác, xem câu trả lời của tôi.
ZAB

7

Một biến thể khác của một giải pháp, sử dụng ngôn ngữ lắp ráp, là một thủ tục bên ngoài. Ví dụ này cho phép nhân số nguyên không dấu bằng g ++ và fasm trong Linux x64.

Quy trình này nhân hai đối số nguyên không dấu (32 bit) (theo đặc tả cho amd64 (phần 3.2.3 Truyền tham số ).

Nếu lớp là INTEGER, thì thanh ghi có sẵn tiếp theo của chuỗi% rdi,% rsi,% rdx,% rcx,% r8 và% r9 được sử dụng

(đăng ký edi và esi trong mã của tôi)) và trả về kết quả hoặc 0 nếu xảy ra tràn.

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

Kiểm tra:

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;
}

Liên kết chương trình với tệp đối tượng asm. Trong trường hợp của tôi, trong Qt Creator , hãy thêm nó vào LIBStrong tệp .pro.


5

Tính kết quả với số nhân đôi. Họ có 15 chữ số có nghĩa. Yêu cầu của bạn có giới hạn trên cứng trên c là 10 8  - nó có thể có tối đa 8 chữ số. Do đó, kết quả sẽ chính xác nếu nó nằm trong phạm vi và nó sẽ không tràn ra ngoài.


5

Hãy thử macro này để kiểm tra bit tràn của máy 32 bit (điều chỉnh giải pháp của Angel Sinigersky)

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

Tôi đã định nghĩa nó là một macro vì nếu không thì bit tràn sẽ bị ghi đè.

Tiếp theo là một ứng dụng nhỏ với phân tách mã ở trên:

#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
Không phải tất cả các máy 32 bit đều tương thích với Intel x86 và không phải tất cả các trình biên dịch đều hỗ trợ cú pháp lắp ráp gnu (tôi thấy buồn cười khi bạn đăng mã kiểm tra _MSC_VERmặc dù các trình biên dịch MS sẽ từ chối mã).
Ben Voigt

2

Việc nắm bắt số nguyên tràn trong C chỉ ra một giải pháp tổng quát hơn so với giải pháp được thảo luận bởi CERT (nói chung là về các loại được xử lý), ngay cả khi nó yêu cầu một số tiện ích mở rộng GCC (tôi không biết chúng được hỗ trợ rộng rãi như thế nào).


2

Bạn không thể truy cập cờ tràn từ C / C ++.

Tôi không đồng ý với điều này. Bạn có thể viết một số ngôn ngữ lắp ráp nội tuyến và sử dụng mộtjo (nhảy tràn) giả sử bạn đang ở trên x86 để bẫy tràn. Tất nhiên, mã của bạn sẽ không còn có thể được chuyển sang các kiến ​​trúc khác.

Nhìn vào info asinfo gcc.


8
trình biên dịch nội tuyến không có tính năng C / C ++ và nền tảng độc lập. Trên x86, bạn có thể sử dụng lệnh istead của nhánh btw.
Nils Pipenbrinck

0

Để mở rộng câu trả lời của Head Geek, có một cách nhanh hơn để làm điều đó 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 );
}

Điều này sử dụng kiến ​​trúc máy an toàn, trong đó các số nguyên không dấu 64 bit và 32 bit sẽ vẫn hoạt động tốt. Về cơ bản, tôi tạo ra một mặt nạ sẽ che giấu tất cả nhưng bit đáng kể nhất. Sau đó, tôi che dấu cả hai số nguyên và nếu một trong hai số đó không được thiết lập bit đó, thì bổ sung là an toàn.

Điều này sẽ còn nhanh hơn nếu bạn khởi tạo trước mặt nạ trong một số hàm tạo, vì nó không bao giờ thay đổi.


5
Điều này LAF không đúng. Carry có thể mang bit từ các vị trí thấp hơn sẽ gây ra tràn. Xem xét thêm UINT_MAX + 1. Sau khi mặt nạ, asẽ có tập bit cao, nhưng 1sẽ trở thành số không và do đó chức năng sẽ trở lại true, bổ sung là an toàn - tuy nhiên bạn đang trực tiếp bị tràn.
con lợn

0

mozilla::CheckedInt<T>cung cấp toán học số nguyên được kiểm tra tràn cho loại số nguyên T(sử dụng nội tại trình biên dịch trên clang và gcc nếu có). Mã này đang được MPL 2.0 và phụ thuộc vào ba ( IntegerTypeTraits.h, Attributes.hCompiler.h) khác header-chỉ phi tiêu chuẩn thư viện tiêu đề cộng Mozilla cụ máy móc khẳng định . Bạn có thể muốn thay thế máy móc xác nhận nếu bạn nhập mã.


-1

Câu trả lời của MSalter là một ý tưởng tốt.

Nếu tính toán số nguyên là bắt buộc (cho độ chính xác), nhưng điểm nổi có sẵn, bạn có thể làm một cái gì đó như:

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
    }
}

Thông thường, tôi muốn nói rằng việc lặp lại phép tính theo dấu phẩy động là một ý tưởng tồi, nhưng đối với trường hợp cụ thể của phép lũy thừa này a ^ c, nó có thể hiệu quả hơn. Nhưng bài kiểm tra nên (c * log(a) < max_log)ở đâuconst double max_log = log(UINT_MAX)
Toby Speight

-1

Tập lệnh x86 bao gồm một lệnh nhân không dấu lưu trữ kết quả vào hai thanh ghi. Để sử dụng hướng dẫn đó từ C, người ta có thể viết đoạn mã sau trong chương trình 64 bit (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;
}

Đối với chương trình 32 bit, người ta cần tạo kết quả 64 bit và tham số 32 bit.

Một cách khác là sử dụng nội tại phụ thuộc vào trình biên dịch để kiểm tra thanh ghi cờ. Tài liệu GCC cho nội tại tràn có thể được tìm thấy từ 6.56 Hàm tích hợp để thực hiện Số học với Kiểm tra tràn .


1
Bạn nên sử dụng loại 128 bit không dấu __uint128để tránh tràn tràn đã ký và dịch chuyển sang phải một giá trị âm.
chqrlie

Là gì "bản năng biên dịch phụ thuộc vào""bản năng tràn" ? Bạn có nghĩa là " chức năng nội tại " ? Bạn đã có một tài liệu tham khảo? (Vui lòng trả lời bằng cách chỉnh sửa câu trả lời của bạn , không phải ở đây trong các bình luận (nếu thích hợp).)
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

Một cách rõ ràng để làm điều đó là ghi đè tất cả các toán tử (đặc biệt là + và *) và kiểm tra mức tràn trước khi thực hiện các thao tác.


6
Ngoại trừ việc bạn không thể ghi đè toán tử cho các loại dựng sẵn. Bạn cần phải viết một lớp cho điều đó và viết lại mã máy khách để sử dụng nó.
Blaisorblade

-3

Nó phụ thuộc vào những gì bạn sử dụng nó cho. Thực hiện phép cộng hoặc phép nhân dài (DWORD) không dấu, giải pháp tốt nhất là sử dụng ULARGE_INTEGER.

ULARGE_INTEGER là cấu trúc của hai DWORD. Giá trị đầy đủ có thể được truy cập là "QuadPart" trong khi DWORD cao được truy cập là "HighPart" và DWORD thấp được truy cập là "LowPart".

Ví dụ:

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
Thật không may, đây là một giải pháp chỉ dành cho Windows. Các nền tảng khác không có ULARGE_INTEGER.
Bí ẩn

-3

Để thực hiện phép nhân không dấu mà không tràn theo cách di động, có thể sử dụng các cách sau:

... /* 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

Cách đơn giản để kiểm tra tràn là thực hiện xác nhận bằng cách kiểm tra xem giá trị hiện tại có nhỏ hơn giá trị trước đó hay không. Ví dụ: giả sử bạn có một vòng lặp để in các quyền hạn của 2:

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

Thêm kiểm tra tràn theo cách mà tôi đã mô tả kết quả trong này:

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;
}

Nó hoạt động cho các giá trị không dấu cũng như các giá trị được ký dương và âm.

Tất nhiên, nếu bạn muốn làm một cái gì đó tương tự để giảm giá trị thay vì tăng giá trị, bạn sẽ lật <=dấu hiệu để thực hiện >=, giả sử hành vi của dòng chảy giống như hành vi của tràn. Thành thật mà nói, đó là về tính di động như bạn sẽ nhận được mà không cần truy cập vào cờ tràn của CPU (và điều đó sẽ yêu cầu mã lắp ráp nội tuyến, làm cho mã của bạn không thể di chuyển qua các triển khai).


9
Nếu một giá trị đã ký tràn, hành vi của chương trình của bạn không được xác định. Nó không được đảm bảo để bọc xung quanh.
David Stone
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.