Tại sao phép chia hai int không mang lại giá trị đúng khi được gán cho đôi?


110

Làm thế nào mà đến trong đoạn mã sau

int a = 7;
int b = 3;
double c = 0;
c = a / b;

ckết thúc bằng giá trị 2, thay vì 2,3333, như người ta mong đợi. Nếu ablà nhân đôi, câu trả lời chuyển thành 2.333. Nhưng chắc chắn vì c đã là một nhân đôi nên nó phải hoạt động với số nguyên?

Vì vậy, làm thế nào đến int/int=doublekhông hoạt động?


Có thể có sự trùng lặp của kết quả
Phép chia

Câu trả lời:


161

Điều này là do bạn đang sử dụng phiên bản chia số nguyên operator/, mất 2 ints và trả về một int. Để sử dụng doublephiên bản trả về a double, ít nhất một trong các ints phải được chuyển thành a double.

c = a/(double)b;

9
Tôi muốn chuyển đổi một cách rõ ràng cả hai abđể doubleđơn giản cho rõ ràng, nhưng nó thực sự không quan trọng.
John Dibling

31
Vì câu hỏi được gắn thẻ C ++ nên tôi muốn xem static_cast <> hơn là một kiểu truyền C.
Martin York

16
Cá nhân tôi cảm thấy rằng kiểu đúc C rõ ràng hơn (ép kiểu trong hầu hết các ngôn ngữ thông thường khác được thực hiện theo kiểu C). static_cast<>với tôi dường như luôn có cảm tình với tôi. Trong trường hợp nguyên thủy, không có thực sự bất kỳ nguy cơ nhận được static_cast<>reinterpret_cast<>hỗn hợp lên.
Chad La Guardia

6
@ Tux-D: Đối với phôi số học? Tôi muốn tránh static_casttrong trường hợp này và sử dụng kiểu C-style để thay thế. Không có lợi ích gì khi sử dụng phôi kiểu C ++ ở đây và chúng làm lộn xộn mã hơn rất nhiều so với phôi kiểu C. Diễn xuất số học chính xác là bối cảnh mà các kiểu cast kiểu C hoàn toàn phù hợp và thực sự là thích hợp hơn các kiểu cast khác.
AnT

19
Đôi khi bạn có thể đánh bại những người "không theo kiểu C" bằng cách viết double(b). Không phải lúc nào họ cũng nhận ra rằng đó là một chuyển đổi, vì nó trông giống như một lệnh gọi hàm tạo rõ ràng.
Steve Jessop,

12

Nó đây:

a) Phép chia hai ints thực hiện phép chia luôn số nguyên. Vì vậy, kết quả a/btrong trường hợp của bạn chỉ có thể là một int.

Nếu bạn muốn giữ abdưới dạng ints, nhưng vẫn chia chúng đầy đủ, bạn phải ép kiểu ít nhất một trong số chúng để nhân đôi: (double)a/bhoặc a/(double)bhoặc (double)a/(double)b.

b) clà a double, vì vậy nó có thể chấp nhận một intgiá trị khi chuyển nhượng: giá trị intđược tự động chuyển đổi thành doublevà gán cho c.

c) Hãy nhớ rằng khi gán, biểu thức ở bên phải của =được tính trước (theo quy tắc (a) ở trên, và không tính đến biến ở bên trái của =) và sau đó được gán cho biến ở bên trái của =(theo ( b) ở trên). Tôi tin rằng điều này hoàn thành bức tranh.


11

Với rất ít ngoại lệ (tôi chỉ có thể nghĩ ra một), C ++ xác định toàn bộ ý nghĩa của một biểu thức (hoặc biểu thức con) từ chính biểu thức đó. Bạn làm gì với kết quả của biểu thức không quan trọng. Trong trường hợp của bạn, trong biểu thức a / b, không có doubletrong tầm nhìn; tất cả mọi thứ là int. Vì vậy trình biên dịch sử dụng phép chia số nguyên. Chỉ khi nó có kết quả, nó mới xem xét phải làm gì với nó và chuyển nó thành double.


3
Một ngoại lệ mà tôi có thể nghĩ đến là chọn quá tải hàm khi lấy một con trỏ - giá trị của &funcnametùy thuộc vào kiểu mà bạn truyền nó sang.
Steve Jessop

2
@Steve Jessop Đó là ngoại lệ duy nhất mà tôi có thể nghĩ đến. (Nhưng với kích thước và độ phức tạp của tiêu chuẩn, tôi sẽ không như thề rằng tôi đã không bỏ lỡ bất kỳ.)
James Kanze

6

clà một doublebiến, nhưng giá trị được gán cho nó là một intgiá trị vì nó là kết quả của phép chia hai ints, điều này cho bạn "phép chia số nguyên" (bỏ phần còn lại). Vì vậy, những gì xảy ra trong dòng c=a/b

  1. a/b được đánh giá, tạo ra một loại tạm thời int
  2. giá trị của giá trị tạm thời được gán cho csau khi chuyển đổi sang loại double.

Giá trị của a/bđược xác định mà không cần tham chiếu đến ngữ cảnh của nó (gán cho double).


6

Khi bạn chia hai số nguyên, kết quả sẽ là một số nguyên, bất kể thực tế là bạn lưu trữ nó ở dạng kép.


5

Trong ngôn ngữ C ++, kết quả của subexpresison không bao giờ bị ảnh hưởng bởi bối cảnh xung quanh (với một số ngoại lệ hiếm hoi). Đây là một trong những nguyên tắc mà ngôn ngữ tuân theo cẩn thận. Biểu thức c = a / bchứa một biểu thức con độc lập a / b, được diễn giải độc lập với bất kỳ thứ gì bên ngoài biểu thức con đó. Ngôn ngữ không quan tâm rằng sau này bạn sẽ gán kết quả cho a double. a / blà một phép chia số nguyên. Bất cứ điều gì khác không quan trọng. Bạn sẽ thấy nguyên tắc này được tuân theo trong nhiều góc của đặc tả ngôn ngữ. Đó là cách hoạt động của C ++ (và C).

Một ví dụ về một ngoại lệ mà tôi đã đề cập ở trên là gán / khởi tạo con trỏ hàm trong các tình huống có quá tải hàm

void foo(int);
void foo(double);

void (*p)(double) = &foo; // automatically selects `foo(fouble)`

Đây là một ngữ cảnh trong đó phía bên trái của một phép gán / khởi tạo ảnh hưởng đến hành vi của phía bên phải. (Ngoài ra, việc khởi tạo tham chiếu đến mảng ngăn chặn sự phân rã kiểu mảng, đây là một ví dụ khác về hành vi tương tự.) Trong tất cả các trường hợp khác, phía bên phải hoàn toàn bỏ qua phía bên trái.


4

Các /nhà điều hành có thể được sử dụng để phân chia số nguyên hoặc phân dấu chấm động. Bạn đang cung cấp cho nó hai toán hạng số nguyên, vì vậy nó đang thực hiện phép chia số nguyên và sau đó kết quả sẽ được lưu trữ trong một nhân đôi.


2

Về mặt kỹ thuật, điều này phụ thuộc vào ngôn ngữ, nhưng hầu như tất cả các ngôn ngữ đều đối xử với chủ đề này như nhau. Khi có kiểu không khớp giữa hai kiểu dữ liệu trong một biểu thức, hầu hết các ngôn ngữ sẽ cố gắng truyền dữ liệu ở một bên của biểu thức =để khớp với dữ liệu ở phía bên kia theo một tập hợp các quy tắc được xác định trước.

Khi chia hai số cùng kiểu (số nguyên, số nhân đôi, v.v.), kết quả sẽ luôn cùng kiểu (vì vậy 'int / int' sẽ luôn dẫn đến kết quả là int).

Trong trường hợp này, bạn có double var = integer result phép tính kết quả số nguyên thành nhân đôi sau khi tính toán, trong trường hợp đó dữ liệu phân số đã bị mất. (hầu hết các ngôn ngữ sẽ thực hiện việc truyền kiểu này để ngăn chặn sự không chính xác của kiểu mà không đưa ra ngoại lệ hoặc lỗi).

Nếu bạn muốn giữ kết quả là một nhân đôi, bạn sẽ muốn tạo ra một tình huống mà bạn có double var = double result

Cách dễ nhất để làm điều đó là buộc biểu thức ở bên phải của một phương trình được ép thành nhân đôi:

c = a/(double)b

Phép chia giữa số nguyên và số kép sẽ dẫn đến việc ép kiểu số nguyên thành số kép (lưu ý rằng khi làm toán, trình biên dịch thường "upcast" lên kiểu dữ liệu cụ thể nhất để tránh mất dữ liệu).

Sau khi upcast, asẽ kết thúc như một đôi và bây giờ bạn có sự phân chia giữa hai đôi. Điều này sẽ tạo ra sự phân chia và phân công mong muốn.

LẠI, xin lưu ý rằng đây là ngôn ngữ cụ thể (và thậm chí có thể là cụ thể cho trình biên dịch), tuy nhiên hầu hết tất cả các ngôn ngữ (chắc chắn là tất cả những ngôn ngữ mà tôi có thể nghĩ ra) đều coi ví dụ này giống hệt nhau.


Câu hỏi này được gắn thẻ [C ++] và Tiêu chuẩn C ++ quy định chính xác cách hoạt động của câu hỏi này. Không chắc bạn muốn nói gì về "ngôn ngữ cụ thể" và nó chắc chắn không dành riêng cho trình biên dịch, giả sử không có phần mở rộng trình biên dịch nào được tham gia.
John Dibling

Ngoài ra, không chính xác khi nói rằng "double var = integer kết quả chuyển var kép thành int". Đôi không được chuyển thành một int. Kết quả int được chuyển đổi thành giá trị kép.
John Dibling

Tôi đã cho phép khả năng mở rộng trình biên dịch (tôi đã thực sự gặp sự cố này một lần khi môi trường của tôi "truyền sai" kết quả và tôi không thể tìm ra lý do). Và kết quả là ngôn ngữ cụ thể vì trong một số ngôn ngữ không tuân theo các quy tắc truyền giống nhau. Tôi đã không cho rằng đó là một thẻ C ++ cụ thể. Bạn nói đúng về nhận xét "double var = integer result". Đã chỉnh sửa để phản ánh điều đó. Cảm ơn bạn!
matthewdunnam 27/09

0

Điều quan trọng là một trong những yếu tố của phép tính phải là kiểu float-double. Sau đó, để nhận được kết quả kép, bạn cần ép kiểu phần tử này như hình dưới đây:

c = static_cast<double>(a) / b;

hoặc c = a / static_cast (b);

Hoặc bạn có thể tạo trực tiếp ::

c = 7.0 / 3;

Lưu ý rằng một trong các phần tử của phép tính phải có '.0' để chỉ ra phép chia kiểu float-double cho một số nguyên. Nếu không, mặc dù biến c là một biến kép, kết quả cũng sẽ là 0.


Câu trả lời của bạn mang lại điều gì mà không câu trả lời nào trong số 9 câu trả lời khác chưa có?
bolov
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.