2016-12-08 13 views
10

Chúng tôi biết rằng -2 * 4^31 + 1 = -9.223.372.036.854.775.807, giá trị thấp nhất bạn có thể lưu trữ trong thời gian dài, như được nói ở đây: What range of values can integer types store in C++. Vì vậy, tôi có hoạt động này:dài trong Visual Studio

#include <iostream> 

unsigned long long pow(unsigned a, unsigned b) { 
    unsigned long long p = 1; 
    for (unsigned i = 0; i < b; i++) 
     p *= a; 
    return p; 
} 

int main() 
{ 
    long long nr = -pow(4, 31) + 5 -pow(4,31); 
    std::cout << nr << std::endl; 
} 

Tại sao nó hiển thị -9.223.372.036.854.775.808 thay vì -9.223.372.036.854.775.803? Tôi đang sử dụng Visual Studio 2015.

+0

Nhận xét không dành cho thảo luận mở rộng; cuộc hội thoại này đã được [chuyển sang trò chuyện] (http://chat.stackoverflow.com/rooms/130291/discussion-on-question-by-peter-long-long-value-in-visual-studio). –

Trả lời

14

Đây là một vấn đề nhỏ thực sự khó chịu trong đó có ba (!) Nguyên nhân.

Thứ nhất có vấn đề là số học dấu chấm động là gần đúng. Nếu trình biên dịch chọn một hàm pow trả về float hoặc double, thì 4 ** 31 lớn đến mức 5 nhỏ hơn 1ULP (đơn vị ít chính xác nhất), vì vậy việc thêm nó sẽ không làm gì (nói cách khác, 4.0 ** 31 + 5 == 4.0 ** 31). Nhân với -2 có thể được thực hiện mà không bị mất, và kết quả có thể được lưu trữ trong một long long mà không bị mất như là câu trả lời sai: -9.223.372.036.854.775.808.

Thứ hai, tiêu đề chuẩn có thể bao gồm các tiêu đề chuẩn khác, nhưng không bắt buộc. Rõ ràng, phiên bản của Visual Studio là <iostream> bao gồm <math.h> (tuyên bố pow trong không gian tên chung), nhưng phiên bản Code :: Blocks thì không.

Thứ ba, chức năng của OP pow không được chọn vì anh đi lập luận 4, và 31, mà đều là kiểu int, và các chức năng công bố có các tham số kiểu unsigned. Kể từ C++ 11, có rất nhiều tình trạng quá tải (hoặc một mẫu chức năng) của std::pow. Tất cả đều trả lại float hoặc double (trừ khi một trong các đối số thuộc loại long double - không áp dụng ở đây).

Do đó, tình trạng quá tải của std::pow sẽ là kết hợp tốt hơn ... với giá trị trả về kép và chúng tôi nhận được làm tròn điểm nổi.

Đạo đức của câu chuyện: Không viết chức năng có cùng tên với chức năng thư viện chuẩn, trừ khi bạn thực sự biết bạn đang làm gì!

+0

"Visual Studio bao gồm" nên đọc "phiên bản của '' được gửi kèm với Visual Studio bao gồm" –

+0

@LThode: Cảm ơn bạn đã chỉnh sửa - được cải thiện nhiều! –

2

Giá trị dài dài nhất có thể đạt được thông qua numeric_limits. Lâu dài đó là:

auto lowest_ll = std::numeric_limits<long long>::lowest(); 

mà kết quả trong:

-9223372036854775808

Các pow() chức năng đó được gọi là không phải của bạn vì thế các kết quả quan sát. Thay đổi tên của hàm.

+0

Vui lòng xem các nhận xét trong câu hỏi. Ông cũng đã cố gắng tự thực hiện chức năng lâu dài unsigned. –

3

Visual Studio đã xác định pow(double, int), chỉ yêu cầu chuyển đổi một đối số, trong khi pow(unsigned, unsigned) của bạn yêu cầu chuyển đổi cả hai đối số trừ khi bạn sử dụng pow(4U, 31U). Quá tải độ phân giải trong C++ dựa trên các đầu vào - không phải là loại kết quả.

+0

Ngoài ra, bao gồm * có thể * (nhưng không bắt buộc phải) khai báo phiên bản tiêu chuẩn của 'pow'. Tôi nghi ngờ vấn đề là nó trên VS, và không trên Code: Blocks. –

1

Cuộc gọi đầu tiên của bạn tới pow đang sử dụng chức năng của thư viện chuẩn C, hoạt động trên các điểm nổi. Hãy thử đưa ra chức năng pow của bạn một tên duy nhất:

unsigned long long my_pow(unsigned a, unsigned b) { 
    unsigned long long p = 1; 
    for (unsigned i = 0; i < b; i++) 
     p *= a; 
    return p; 
} 

int main() 
{ 
    long long nr = -my_pow(4, 31) + 5 - my_pow(4, 31); 
    std::cout << nr << std::endl; 
} 

Mã này báo cáo một lỗi: "điều hành trừ unary áp dụng cho loại unsigned, kết quả vẫn unsigned". Vì vậy, về cơ bản, mã ban đầu của bạn được gọi là một hàm dấu chấm động, đã phủ nhận giá trị, áp dụng một số số nguyên cho nó, mà nó không có đủ độ chính xác để trả lời bạn đang tìm kiếm (ở 19 chữ số)! Để có được câu trả lời bạn đang tìm kiếm, thay đổi chữ ký để:

long long my_pow(unsigned a, unsigned b); 

này đã làm việc cho tôi trong MSVC++ 2013. Như đã nêu trong câu trả lời khác, bạn đang nhận được dấu chấm động pow vì chức năng của bạn hy vọng unsigned và nhận các hằng số nguyên được ký. Thêm U vào các số nguyên của bạn sẽ gọi phiên bản pow của bạn.

+0

"toán tử trừ thống nhất được áp dụng cho loại chưa ký, kết quả vẫn chưa được ký" là NOT. –

+0

Mang theo với MSVC. :) Tôi không có bất kỳ cờ biên dịch đặc biệt nào được đặt. – cyberbisson

2

Giải thích duy nhất có thể cho kết quả -9.223.372.036.854.775.808 là việc sử dụng hàm pow từ thư viện chuẩn trả lại giá trị kép. Trong trường hợp đó, 5 sẽ thấp hơn độ chính xác của phép tính kép và kết quả sẽ chính xác là -2 và được chuyển đổi thành một khoảng thời gian dài sẽ cho 0x8000000000000000 hoặc -9.223.372.036.854.775.808.

Nếu bạn sử dụng chức năng của bạn trả về một unsigned dài lâu, bạn sẽ nhận được một cảnh báo nói rằng bạn áp dụng unary trừ một loại unsigned và vẫn nhận được một ULL. Vì vậy, toàn bộ hoạt động nên được thực hiện như unsigned long long và nên cung cấp mà không tràn 0x8000000000000005 như giá trị unsigned. Khi bạn đưa nó vào một giá trị đã ký, kết quả là không xác định, nhưng tất cả các trình biên dịch tôi biết chỉ cần sử dụng số nguyên đã ký với cùng một biểu diễn là -9.223.372.036.854.775.803.

Nhưng nó sẽ là đơn giản để làm cho việc tính toán như đã ký kết lâu dài mà không có bất kỳ cảnh báo bằng cách chỉ sử dụng:

long long nr = -1 * pow(4, 31) + 5 - pow(4,31); 

Là một Ngoài ra, bạn có và đừng quăng xác định cũng không tràn ở đây nên kết quả là hoàn toàn được xác định cho mỗi tiêu chuẩn cung cấp unsigned dài dài ít nhất 64 bit.