Tôi muốn xác định một hàm nhận một unsigned int
đối số làm đối số và trả về một int
mô-đun đồng dư UINT_MAX + 1 cho đối số.
Lần thử đầu tiên có thể trông như thế này:
int unsigned_to_signed(unsigned n)
{
return static_cast<int>(n);
}
Nhưng như bất kỳ luật sư ngôn ngữ nào cũng biết, việc truyền từ chưa ký sang có ký cho các giá trị lớn hơn INT_MAX được xác định bởi việc triển khai.
Tôi muốn thực hiện điều này sao cho (a) nó chỉ dựa trên hành vi được yêu cầu bởi spec; và (b) nó biên dịch thành no-op trên bất kỳ máy hiện đại nào và tối ưu hóa trình biên dịch.
Đối với các máy kỳ lạ ... Nếu không có int congruent modulo UINT_MAX + 1 có dấu cho int chưa được ký, giả sử tôi muốn đưa ra một ngoại lệ. Nếu có nhiều hơn một (tôi không chắc điều này có thể xảy ra), giả sử tôi muốn có cái lớn nhất.
OK, lần thử thứ hai:
int unsigned_to_signed(unsigned n)
{
int int_n = static_cast<int>(n);
if (n == static_cast<unsigned>(int_n))
return int_n;
// else do something long and complicated
}
Tôi không quan tâm lắm đến hiệu quả khi tôi không sử dụng hệ thống bổ sung hai mặt điển hình, vì theo ý kiến khiêm tốn của tôi thì điều đó khó xảy ra. Và nếu mã của tôi trở thành một nút thắt cổ chai trên các hệ thống cường độ ký hiệu ở khắp nơi của năm 2050, thì tôi cá rằng ai đó có thể tìm ra điều đó và tối ưu hóa nó sau đó.
Bây giờ, nỗ lực thứ hai này đã khá gần với những gì tôi muốn. Mặc dù quá trình ép kiểu tới int
được xác định thực thi đối với một số đầu vào, việc ép kiểu trở lại unsigned
được đảm bảo theo tiêu chuẩn để bảo toàn mô-đun giá trị UINT_MAX + 1. Vì vậy, điều kiện không kiểm tra chính xác những gì tôi muốn và nó sẽ biên dịch thành không có gì trên bất kỳ hệ thống nào mà tôi có thể gặp phải.
Tuy nhiên ... tôi vẫn đang truyền tới int
mà không cần kiểm tra trước xem nó có gọi hành vi do triển khai xác định hay không. Trên một hệ thống giả thuyết nào đó vào năm 2050, nó có thể làm ai-biết-gì. Vì vậy, hãy nói rằng tôi muốn tránh điều đó.
Câu hỏi: "Nỗ lực thứ ba" của tôi trông như thế nào?
Tóm lại, tôi muốn:
- Truyền từ int chưa ký sang int đã ký
- Giữ nguyên giá trị mod UINT_MAX + 1
- Chỉ gọi hành vi được ủy quyền tiêu chuẩn
- Biên dịch thành no-op trên một máy bổ sung hai chức năng điển hình với trình biên dịch tối ưu hóa
[Cập nhật]
Hãy để tôi đưa ra một ví dụ cho thấy tại sao đây không phải là một câu hỏi tầm thường.
Hãy xem xét một triển khai C ++ giả định với các thuộc tính sau:
sizeof(int)
bằng 4sizeof(unsigned)
bằng 4INT_MAX
bằng 32767INT_MIN
bằng -2 32 + 32768UINT_MAX
bằng 2 32 - 1- Arithmetic on
int
là modulo 2 32 (trong phạm viINT_MIN
thông quaINT_MAX
) std::numeric_limits<int>::is_modulo
là đúng- Truyền không dấu
n
đến int sẽ giữ nguyên giá trị cho 0 <= n <= 32767 và nếu không sẽ cho kết quả bằng 0
Trong quá trình triển khai giả định này, có chính xác một int
giá trị đồng dư (mod UINT_MAX + 1) cho mỗi unsigned
giá trị. Vì vậy, câu hỏi của tôi sẽ được xác định rõ.
Tôi khẳng định rằng việc triển khai C ++ giả định này hoàn toàn tuân thủ các đặc tả C ++ 98, C ++ 03 và C ++ 11. Tôi thừa nhận rằng tôi đã không ghi nhớ từng từ của tất cả chúng ... Nhưng tôi tin rằng tôi đã đọc kỹ các phần liên quan. Vì vậy, nếu bạn muốn tôi chấp nhận câu trả lời của bạn, bạn phải (a) trích dẫn một thông số kỹ thuật quy định việc triển khai giả định này hoặc (b) xử lý nó một cách chính xác.
Thật vậy, một câu trả lời đúng phải xử lý mọi việc triển khai giả định được tiêu chuẩn cho phép. Theo định nghĩa, "chỉ gọi hành vi bắt buộc theo tiêu chuẩn" nghĩa là gì.
Ngẫu nhiên, lưu ý rằng std::numeric_limits<int>::is_modulo
hoàn toàn vô dụng ở đây vì nhiều lý do. Đối với một điều, nó có thể xảy ra true
ngay cả khi các phôi chưa được ký để ký không hoạt động đối với các giá trị chưa được ký lớn. Đối với người khác, nó true
thậm chí có thể nằm trên hệ thống phần bù hoặc dấu hiệu của một người, nếu số học chỉ đơn giản là mô đun của toàn bộ phạm vi số nguyên. Và như thế. Nếu câu trả lời của bạn phụ thuộc vào is_modulo
, nó sai.
[Cập nhật 2]
Câu trả lời của hvd đã dạy tôi điều gì đó: Việc triển khai C ++ giả định của tôi cho các số nguyên không được phép bởi C. Các tiêu chuẩn C99 và C11 rất cụ thể về việc biểu diễn các số nguyên có dấu; thực sự, chúng chỉ cho phép hai phần bù, phần bổ sung một và độ lớn dấu hiệu (mục 6.2.6.2 đoạn (2);).
Nhưng C ++ không phải là C. Hóa ra, sự thật này nằm ở trọng tâm của câu hỏi của tôi.
Chuẩn C ++ 98 ban đầu dựa trên C89 cũ hơn nhiều, cho biết (phần 3.1.2.5):
Đối với mỗi kiểu số nguyên có dấu, có một kiểu số nguyên không dấu tương ứng (nhưng khác nhau) (được chỉ định bằng từ khóa không dấu) sử dụng cùng một lượng lưu trữ (bao gồm thông tin dấu) và có cùng yêu cầu về căn chỉnh. Phạm vi các giá trị không âm của kiểu số nguyên có dấu là một dải con của kiểu số nguyên không dấu tương ứng và cách biểu diễn cùng một giá trị trong mỗi kiểu là như nhau.
C89 không nói gì về việc chỉ có một bit dấu hoặc chỉ cho phép hai phần bổ sung / một phần bổ sung / dấu hiệu-độ lớn.
Tiêu chuẩn C ++ 98 đã áp dụng ngôn ngữ này gần như nguyên văn (phần 3.9.1 đoạn (3)):
Đối với mỗi kiểu số nguyên có dấu, tồn tại một kiểu số nguyên không dấu tương ứng (nhưng khác nhau) : "
unsigned char
", "unsigned short int
", "unsigned int
" và "unsigned long int
", mỗi kiểu chiếm cùng một lượng bộ nhớ và có cùng yêu cầu căn chỉnh (3.9 ) là kiểu số nguyên có dấu tương ứng; nghĩa là mỗi kiểu số nguyên có dấu có cùng một biểu diễn đối tượng như kiểu số nguyên không dấu tương ứng của nó . Dải các giá trị không âm của kiểu số nguyên có dấu là một dải con của kiểu số nguyên không dấu tương ứng và cách biểu diễn giá trị của mỗi kiểu có dấu / không dấu tương ứng sẽ giống nhau.
Chuẩn C ++ 03 sử dụng ngôn ngữ cơ bản giống hệt nhau, cũng như C ++ 11.
Không có thông số C ++ chuẩn nào hạn chế các biểu diễn số nguyên có dấu của nó với bất kỳ thông số C nào, theo như tôi có thể nói. Và không có gì bắt buộc một bit ký hiệu hay bất cứ thứ gì thuộc loại này. Tất cả những gì nó nói là các số nguyên có dấu không âm phải là một dãy con của các số nguyên không dấu tương ứng.
Vì vậy, một lần nữa tôi khẳng định rằng INT_MAX = 32767 với INT_MIN = -2 32 +32768 được cho phép. Nếu câu trả lời của bạn giả định khác, nó không chính xác trừ khi bạn trích dẫn một tiêu chuẩn C ++ chứng minh tôi sai.