2009-02-08 49 views
21

Hôm nay, tôi nhận thấy rằng khi tôi truyền một giá trị lớn hơn số nguyên tối đa có thể thành số nguyên, tôi nhận được -2147483648. Tương tự như vậy, khi tôi cast một double nhỏ hơn số nguyên tối thiểu có thể, tôi cũng nhận được -2147483648.Xử lý tràn khi đúc nhân đôi thành số nguyên trong C

Hành vi này có được xác định cho tất cả các nền tảng không?
Cách tốt nhất để phát hiện điều này dưới/tràn là gì? Được đặt nếu báo cáo cho min và max int trước khi đúc giải pháp tốt nhất?

+0

[Số nguyên tối thiểu 32 bit đúc (-2147483648) để nổi cho số dương (2147483648.0)] (http://stackoverflow.com/q/11536389/995714) –

Trả lời

0

Tôi không chắc chắn về điều này nhưng tôi nghĩ rằng có thể "bật" ngoại lệ dấu phẩy động cho dưới/tràn ... hãy xem Dealing with Floating-point Exceptions in MSVC7\8 này để bạn có thể thay thế nếu/else kiểm tra.

-1

Tôi không thể cho bạn biết chắc chắn liệu nó có được xác định cho tất cả các nền tảng hay không, nhưng đó là khá nhiều điều đã xảy ra trên mọi nền tảng tôi đã sử dụng. Ngoại trừ, theo kinh nghiệm của tôi, nó cuộn. Tức là, nếu giá trị của giá trị kép là INT_MAX + 2, thì khi kết quả của diễn viên kết thúc bằng INT_MIN + 2.

Để có cách tốt nhất để xử lý, tôi thực sự không chắc chắn. Tôi đã chạy lên chống lại vấn đề bản thân mình, và vẫn chưa tìm thấy một cách thanh lịch để đối phó với nó. Tôi chắc rằng ai đó sẽ trả lời có thể giúp chúng tôi ở đó.

12

limits.h có hằng số cho max và min giá trị có thể với nhiều loại nguyên liệu, bạn có thể kiểm tra biến đôi của bạn trước khi đúc, như

if (my_double > nextafter(INT_MAX, 0) || my_double < nextafter(INT_MIN, 0)) 
    printf("Overflow!"); 
else 
    my_int = (int)my_double; 

EDIT: nextafter() sẽ giải quyết vấn đề được đề cập bởi nwellnhof

+0

Điều này không có tác dụng đối với tôi. Tôi thử và làm điều này 'float f = INT_MAX; f ++; ConvertToInt (f) 'với kiểm tra giới hạn mà bạn có ở trên và nó không tràn. Khác biệt là gì? – Pittfall

+1

@Pittfall: 'float' có (tất cả ngoại trừ các nền tảng rất kỳ lạ sử dụng IEEE-754 nổi) có 24 chữ số nhị phân đáng kể. Vì vậy, khi bạn đặt nó thành INT_MAX, là 2³¹-1 (INT_MAX trên nền tảng 32 bit), chữ số cuối cùng là 128 giây. Vì vậy, nếu bạn thêm bất cứ điều gì nhỏ hơn 128, kết quả là số orignal, đó là '(float) INT_MAX + 1.f == (float) INT_MAX'. Với 'double', có chữ số có ý nghĩa hơn' int', nó sẽ hoạt động. –

+0

'INT_MAX' và' INT_MIN' là cách kiểm tra C. Cách C++ đang sử dụng ['std :: numeric_limits :: max()'] (http://en.cppreference.com/w/cpp/types/numeric_limits/max) và '… :: min()'. –

4

Một cách di động cho C++ là sử dụng lớp SafeInt:

http://www.codeplex.com/SafeInt

Việc thực hiện sẽ cho phép cộng/trừ/etc thông thường trên kiểu số C++ bao gồm các phôi. Nó sẽ ném một ngoại lệ bất cứ khi nào và tràn kịch bản được phát hiện.

SafeInt<int> s1 = INT_MAX; 
SafeInt<int> s2 = 42; 
SafeInt<int> s3 = s1 + s2; // throws 

Tôi khuyên bạn nên sử dụng lớp này ở bất kỳ nơi tràn là một tình huống quan trọng. Nó làm cho nó rất khó khăn để tránh âm thầm tràn. Trong trường hợp có một kịch bản phục hồi cho một tràn, chỉ cần nắm bắt SafeIntException và phục hồi khi thích hợp.

SafeInt hiện đang làm việc trên GCC cũng như Visual Studio

+1

khá hay. có nhiều chi phí không? – wprl

+0

Một thử nghiệm thô khiến tôi tin rằng nó có thể không thể phát hiện tràn trong C + + (không có chi phí nghiêm trọng hoặc thay đổi toàn bộ mô hình, chẳng hạn như gói mọi số nguyên làm đối tượng). Lớp _dedicated này không thể xử lý 'SafeInt x = std :: numeric_limits :: max() + 100' (nó không ném). – kizzx2

+2

bạn đang đùa ...? phía bên tay phải của bạn * đã * đã tràn trước khi nó thậm chí đến được hang động SafeInt. Bạn không thể đổ lỗi cho SafeInt cho rằng – Ichthyo

2

Một lựa chọn khác là sử dụng boost::numeric_cast mà cho phép chuyển đổi giữa các loại độc đoán số. Nó phát hiện mất phạm vi khi một loại số được chuyển đổi và ném ngoại lệ nếu phạm vi không thể được giữ nguyên.

Trang web được tham chiếu ở trên cũng cung cấp một ví dụ nhỏ cung cấp thông tin tổng quan nhanh về cách sử dụng mẫu này.

Tất nhiên, đây không phải là đồng bằng C nữa ;-)

+0

boost :: numeric_cast làm cho cùng một sai lầm mà tôi thảo luận trong câu trả lời của tôi. 'numeric_cast (pow (2.0, 63.0))' không ném. – nwellnhof

11

Để trả lời câu hỏi của bạn: Các hành vi khi bạn cast ra khỏi phạm vi nổi là undefined hoặc thực hiện cụ thể.

Nói từ kinh nghiệm: Tôi đã làm việc trên một hệ thống MIPS64 không triển khai các loại phôi này chút nào. Thay vì làm một cái gì đó xác định CPU đã ném một ngoại lệ CPU.Trình xử lý ngoại lệ cần phải mô phỏng diễn viên trả về mà không làm bất cứ điều gì cho kết quả.

Tôi đã kết thúc với số nguyên ngẫu nhiên. Hãy đoán xem phải mất bao lâu để tìm ra lỗi cho nguyên nhân này. :-)

Bạn nên tự kiểm tra phạm vi nếu bạn không chắc chắn rằng số đó không thể thoát khỏi phạm vi hợp lệ.

2

Chúng tôi đáp ứng cùng một câu hỏi. chẳng hạn như:

double d = 9223372036854775807L; 
int i = (int)d; 

trong Linux/window, i = -2147483648. nhưng Trong AIX 5.3 i = 2147483647.

Nếu đôi nằm ngoài phạm vi của bộ trộn.

  • Linux/cửa sổ luôn trả về INT_MIN.
  • AIX sẽ trả về INT_MAX nếu gấp đôi là postive, sẽ trả về INT_MIN trong số double là negetive.
11

Khi truyền nổi sang số nguyên, tràn gây ra hành vi không xác định. Từ spec C99, phần 6.3.1.4 Bất động nổi và nguyên:

Khi một giá trị hữu hạn các loại nổi thực được chuyển đổi sang một kiểu số nguyên khác hơn _Bool, phần phân đoạn bị loại bỏ (ví dụ, giá trị được cắt ngắn về 0). Nếu giá trị của phần tích phân không thể được biểu diễn bằng kiểu số nguyên, thì hành vi là không xác định.

Bạn phải kiểm tra phạm vi một cách thủ công, nhưng không sử dụng mã như:

// DON'T use code like this! 
if (my_double > INT_MAX || my_double < INT_MIN) 
    printf("Overflow!"); 

INT_MAX là một số nguyên không đổi mà có thể không có một đại diện dấu chấm động chính xác. Khi so sánh với một phao, nó có thể được làm tròn đến giá trị dấu phẩy động lớn hơn gần nhất hoặc cao hơn gần nhất (đây là giá trị thực hiện được xác định). Với số nguyên 64 bit, ví dụ: INT_MAX2^63 - 1, thường sẽ được làm tròn thành 2^63, do đó, bản gốc kiểm tra sẽ trở thành my_double > INT_MAX + 1. Điều này sẽ không phát hiện tình trạng tràn nếu my_double bằng 2^63.

Ví dụ với gcc 4.9.1 trên Linux, chương trình sau đây

#include <math.h> 
#include <stdint.h> 
#include <stdio.h> 

int main() { 
    double d = pow(2, 63); 
    int64_t i = INT64_MAX; 
    printf("%f > %lld is %s\n", d, i, d > i ? "true" : "false"); 
    return 0; 
} 

in

9223372036854775808.000000 > 9223372036854775807 is false 

Thật khó để có được quyền này nếu bạn không biết các giới hạn và đại diện bên trong của các số nguyên và kiểu kép trước. Nhưng nếu bạn chuyển đổi double-int64_t, ví dụ, bạn có thể sử dụng các hằng số thời điểm đó là đôi chính xác (giả định bổ sung và IEEE hai của đôi) nổi:

if (!(my_double >= -9223372036854775808.0 // -2^63 
    && my_double < 9223372036854775808.0) // 2^63 
) { 
    // Handle overflow. 
} 

Các cấu trúc !(A && B) cũng xử lý một cách chính xác Nans.Một phiên bản di động, an toàn, nhưng không chính xác cho slighty int s là:

if (!(my_double > INT_MIN && my_double < INT_MAX)) { 
    // Handle overflow. 
} 

này errs về phía thận trọng và sai sẽ từ chối giá trị tương đương hoặc INT_MININT_MAX. Nhưng đối với hầu hết các ứng dụng, điều này sẽ ổn.

+0

Tôi vừa thực hiện một thử nghiệm thực nghiệm nhỏ, và câu trả lời này có vẻ đúng (một lần nữa, giả sử số nguyên bổ sung của hai, nếu bạn không thể giả định rằng, có thể Boost hoặc SafeInt là cách hợp lý duy nhất để đi). Bạn nên upvote câu trả lời này và downvote câu trả lời không chính xác mà ủng hộ my_double> INT_MAX || my_double bhaller

+0

@ bhaller Tôi vừa kiểm tra, và cả Boost và SafeInt đều mắc lỗi tương tự mà tôi thảo luận trong câu trả lời của mình. – nwellnhof

+0

Yikes. Bạn có báo cáo vấn đề với họ không? – bhaller

3

Cách tốt nhất để phát hiện điều này dưới/tràn là gì?

So sánh cắt ngắn double-chính xác giới hạn gần INT_MIN,INT_MAX.

Các lừachính xác giới hạn chuyển đổi dựa trên INT_MIN,INT_MAX vào double giá trị. A double có thể không đại diện chính xác INT_MAX vì số lượng bit trong một số intcó thể vượt quá độ chính xác của dấu phẩy động đó. Trong trường hợp đó, việc chuyển đổi INT_MAX thành double bị làm tròn. Số sau INT_MAX là mức lũy thừa 2 và chắc chắn có thể đại diện là double. 2.0*(INT_MAX/2 + 1) tạo toàn bộ số một lớn hơn INT_MAX.

Điều tương tự cũng áp dụng cho INT_MIN trên máy không bổ sung 2.

INT_MAX luôn là một sức mạnh-of-2 - 1.
INT_MIN luôn là:
-INT_MAX (không 2 của bổ sung) hoặc
-INT_MAX-1 (2 của bổ sung)

int double_to_int(double x) { 
    x = trunc(x); 
    if (x >= 2.0*(INT_MAX/2 + 1)) Handle_Overflow(); 
    #if -INT_MAX == INT_MIN 
    if (x <= 2.0*(INT_MIN/2 - 1)) Handle_Underflow(); 
    #else 
    if (x < INT_MIN) Handle_Underflow(); 
    #endif 
    return (int) x; 
} 

Để phát hiện NaN và không sử dụng trunc()

#define DBL_INT_MAXP1 (2.0*(INT_MAX/2+1)) 
#define DBL_INT_MINM1 (2.0*(INT_MIN/2-1)) 

int double_to_int(double x) { 
    if (x < DBL_INT_MAXP1) { 
    #if -INT_MAX == INT_MIN 
    if (x > DBL_INT_MINM1) { 
     return (int) x; 
    } 
    #else 
    if (ceil(x) >= INT_MIN) { 
     return (int) x; 
    } 
    #endif 
    Handle_Underflow(); 
    } else if (x > 0) { 
    Handle_Overflow(); 
    } else { 
    Handle_NaN(); 
    } 
} 
+0

Bạn nói rằng sức mạnh của 2, kích thước của 'MAX_INT + 1', chắc chắn có thể biểu diễn dưới dạng' double'. Bạn có thể giải thích lý do tại sao? Giả định của bạn là gì? Giả sử IEEE đủ? –

+1

Ok, tôi có thể thấy bây giờ bạn giả định rằng 'FLT_RADIX' là 2 và' INT_MAX' nằm dưới 'DBL_MAX', và không có gì khác ngoài những gì được đảm bảo bởi tiêu chuẩn ngôn ngữ. Đó là một giải pháp tuyệt vời và sáng tạo. Tôi thích nó. –

+0

Một bình luận: Những gì bạn gọi * tràn *, tôi tin là chính thức được gọi là * tràn âm *. Phần dưới là khi kết quả chụp thành 0. Overflow là khi cường độ bị ảnh hưởng, gần như nói. –

Các vấn đề liên quan