Tại sao phép chia số nguyên trong C # trả về một số nguyên mà không phải là số float?


131

Có ai biết tại sao phép chia số nguyên trong C # trả về một số nguyên mà không phải là số float không? Ý tưởng đằng sau nó là gì? (Có phải nó chỉ là một di sản của C / C ++?)

Trong C #:

float x = 13 / 4;   
//== operator is overridden here to use epsilon compare
if (x == 3.0)
   print 'Hello world';

Kết quả của mã này sẽ là:

'Hello world'

Nói một cách chính xác, không có thứ gọi là phép chia số nguyên (chia theo định nghĩa là một phép toán tạo ra một số hữu tỷ, số nguyên là một tập hợp con rất nhỏ trong đó.)


46
bởi vì nó là integerphân chia không floating pointphân chia.
Thợ săn McMillen

nó phải (trong VB.Net) nó được thực hiện theo cách toán học tự nhiên trong đó tất cả kết quả của phép toán chia là một số vô tỷ.
BanditoBunny

3
Tôi nghĩ bạn có nghĩa là số hữu tỷ . Xem wikipedia : Chia hai số nguyên có thể dẫn đến phần còn lại. Để hoàn thành việc phân chia phần còn lại, hệ thống số được mở rộng để bao gồm các phân số hoặc số hữu tỷ vì chúng thường được gọi nhiều hơn.
crashmstr

6
Đây là lý do tôi không phải là người thích "sao chép cú pháp" trong các ngôn ngữ. Tôi đến từ VB nghĩ rằng "C # là .NET" chứ không phải "C # giống như C". Tôi đoán sai lầm của tôi, nhưng trong trường hợp này tôi thích cách VB. Nếu họ gặp phải sự cố tạo lỗi trình biên dịch khi sử dụng các loại đơn giản chưa được khởi tạo (thậm chí bạn không nhận được cảnh báo trong C) thì tại sao bạn không cảnh báo bạn khi bạn gán phân chia số nguyên cho float?
darda

Câu trả lời:


96

Mặc dù thông thường, lập trình viên mới mắc lỗi này khi thực hiện phép chia số nguyên khi họ thực sự có ý định sử dụng phép chia dấu phẩy động, trong thực tế, phép chia số nguyên là một thao tác rất phổ biến. Nếu bạn cho rằng mọi người hiếm khi sử dụng nó, và mỗi khi bạn thực hiện phép chia, bạn sẽ luôn cần nhớ chuyển sang các điểm nổi, thì bạn đã nhầm.

Trước hết, phép chia số nguyên nhanh hơn một chút, vì vậy nếu bạn chỉ cần một kết quả số nguyên, người ta sẽ muốn sử dụng thuật toán hiệu quả hơn.

Thứ hai, có một số thuật toán sử dụng phép chia số nguyên và nếu kết quả của phép chia luôn là số dấu phẩy động, bạn sẽ bị buộc phải làm tròn kết quả mỗi lần. Một ví dụ ngoài đỉnh đầu của tôi là thay đổi cơ sở của một số. Tính toán mỗi chữ số liên quan đến phép chia số nguyên của một số cùng với phần còn lại, chứ không phải là phép chia dấu phẩy động của số.

Vì những lý do này (và các lý do liên quan khác), phân chia số nguyên dẫn đến một số nguyên. Nếu bạn muốn có được phép chia dấu phẩy động của hai số nguyên, bạn chỉ cần nhớ chuyển một thành một double/ float/ decimal.


5
Trong VB.Net, các kiến ​​trúc sư của Net đã đưa ra một quyết định khác: / - luôn luôn là một phép chia float, \ - phép chia số nguyên, vì vậy nó là loại không nhất quán, trừ khi bạn xem xét di sản C ++;
BanditoBunny

4
Bạn có thể xác định, tại thời điểm biên dịch, liệu /toán tử sẽ thực hiện phép chia số nguyên hay dấu phẩy động (trừ khi bạn đang sử dụng động). Nếu bạn khó thể tìm ra vì bạn đang làm quá nhiều trên một dòng đó, thì tôi khuyên bạn nên chia dòng đó thành nhiều dòng để dễ dàng tìm ra liệu toán hạng là số nguyên hay kiểu dấu phẩy động. Độc giả tương lai của mã của bạn sẽ có thể đánh giá cao nó.
Phục vụ

5
Cá nhân tôi thấy có vấn đề là tôi luôn phải nghĩ các biến tôi đang chia là gì, tôi coi đó là một sự lãng phí sử dụng sự chú ý của tôi.
BanditoBunny

8
@pelel Vì đó sẽ là một thay đổi lớn để thực hiện điều đó mà một số chương trình thiên văn sẽ bị phá vỡ, tôi có thể hoàn toàn tự tin rằng nó sẽ không bao giờ xảy ra trong C #. Đó là điều cần phải được thực hiện từ ngày 1 bằng ngôn ngữ hoặc hoàn toàn không.
Phục vụ

2
@Servy: Có rất nhiều thứ như thế trong C, C ++ và C #. Cá nhân, tôi nghĩ C # sẽ là ngôn ngữ tốt hơn nếu có một toán tử khác cho phép chia số nguyên và để tránh việc mã hợp pháp mang lại hành vi đáng kinh ngạc, int/inttoán tử chỉ đơn giản là bất hợp pháp [với chẩn đoán xác định rằng mã phải truyền toán hạng hoặc sử dụng toán tử khác, tùy thuộc vào hành vi nào được mong muốn]. Đã có một số chuỗi mã thông báo tốt khác có sẵn để phân chia số nguyên, có thể không chấp nhận việc sử dụng /cho mục đích đó, nhưng tôi không biết điều gì sẽ thực tế.
supercat

77

Xem thông số kỹ thuật C # . Có ba loại toán tử phân chia

  • Bộ phận nguyên
  • Phân chia điểm nổi
  • Phân chia thập phân

Trong trường hợp của bạn, chúng tôi có phân chia Integer, với các quy tắc sau được áp dụng:

Phép chia làm tròn kết quả về 0 và giá trị tuyệt đối của kết quả là số nguyên lớn nhất có thể nhỏ hơn giá trị tuyệt đối của thương số của hai toán hạng. Kết quả bằng 0 hoặc dương khi hai toán hạng có cùng dấu và 0 hoặc âm khi hai toán hạng có dấu trái ngược nhau.

Tôi nghĩ lý do tại sao C # sử dụng loại phân chia này cho số nguyên (một số ngôn ngữ trả về kết quả nổi) là phần cứng - phân chia số nguyên nhanh hơn và đơn giản hơn.


Những ngôn ngữ trả về kết quả nổi? @SergeyBerezovskiy
Ilaria

40

Mỗi loại dữ liệu có khả năng quá tải mỗi toán tử. Nếu cả tử số và mẫu số đều là số nguyên, kiểu số nguyên sẽ thực hiện thao tác chia và nó sẽ trả về một kiểu số nguyên. Nếu bạn muốn phân chia điểm nổi, bạn phải bỏ một hoặc nhiều số cho các loại dấu phẩy động trước khi chia chúng. Ví dụ:

int x = 13;
int y = 4;
float x = (float)y / (float)z;

hoặc, nếu bạn đang sử dụng chữ:

float x = 13f / 4f;

Hãy ghi nhớ, các điểm nổi không chính xác. Thay vào đó, nếu bạn quan tâm đến độ chính xác, hãy sử dụng một cái gì đó như kiểu thập phân.


1
+1 để đề cập rằng chỉ có một thuật ngữ cần được thả nổi để phân chia điểm nổi.
Xynariz

Rõ ràng tuyên bố của bạn về độ chính xác là quyền trong bối cảnh học tập và làm cho nó không phức tạp để hiểu. Vì chúng tôi cần phải chính xác nhất có thể trong công việc của mình, tôi vẫn muốn làm rõ về độ chính xác: Theo IEE 754-1985, bạn CÓ THỂ nhận được một kết quả chính xác (mặc dù điều này chủ yếu không phải là trường hợp). Bạn có thể nhận được một kết quả chính xác, khi các giá trị tính toán đã được trình bày chính xác trước đó và kết quả là - nói một cách đơn giản - tổng lũy ​​thừa của 2. Mặc dù không thể thực hành tốt nhất để dựa vào độ chính xác đó trong các trường hợp đặc biệt đó.
L. Monty

Bổ sung về độ chính xác: Khả năng có được kết quả chính xác được cải thiện đáng kể vì kết quả gần bằng 1 hoặc -1. Có thể có một chút nhầm lẫn rằng khả năng này vẫn là 0 vì có số lượng vô hạn và số lượng kết quả hữu hạn, có thể được trình bày chính xác. :)
L. Monty

1
@ L.Monty cảm ơn vì đã đưa nó lên. Tôi đã học được nhiều hơn về các điểm nổi kể từ khi viết câu trả lời này và điểm bạn đang thực hiện là công bằng. Về mặt kỹ thuật, tôi vẫn sẽ nói rằng tuyên bố điểm nổi của tôi không chính xác là có thể chấp nhận được, theo nghĩa là chỉ vì một cái gì đó có thể chính xác đôi khi không có nghĩa là nó nói chung là chính xác. Như họ nói, một chiếc đồng hồ bị hỏng là đúng hai lần một ngày, nhưng tôi không bao giờ gọi nó là một công cụ chính xác. Tôi thực sự khá ngạc nhiên khi đó là phần khiến bạn bận tâm hơn đề xuất của tôi rằng loại thập phân chính xác.
Steven Doggart

Số thập phân là không chính xác, vì tất cả các lý do tương tự mà phao là; chỉ là số float là cơ sở 2 và số thập phân là cơ sở 10. Chẳng hạn, một loại thập phân không thể giữ chính xác giá trị chính xác là 1/3.
Steven Doggart

11

Vì bạn không sử dụng bất kỳ hậu tố nào, các chữ 134được hiểu là số nguyên:

Hướng dẫn sử dụng :

Nếu theo nghĩa đen không có hậu tố, nó có là người đầu tiên của các loại, trong đó giá trị của nó có thể được biểu: int, uint, long, ulong.

Do đó, vì bạn khai báo 13là số nguyên, phép chia số nguyên sẽ được thực hiện:

Hướng dẫn sử dụng :

Đối với một hoạt động có dạng x / y, độ phân giải quá tải toán tử nhị phân được áp dụng để chọn một triển khai toán tử cụ thể. Các toán hạng được chuyển đổi thành các loại tham số của toán tử được chọn và loại kết quả là kiểu trả về của toán tử.

Các toán tử phân chia được xác định trước được liệt kê dưới đây. Các toán tử đều tính thương số của x và y.

Bộ phận nguyên:

int operator /(int x, int y);
uint operator /(uint x, uint y);
long operator /(long x, long y);
ulong operator /(ulong x, ulong y);

Và vì vậy, làm tròn xuống xảy ra:

Phép chia làm tròn kết quả về 0 và giá trị tuyệt đối của kết quả là số nguyên lớn nhất có thể nhỏ hơn giá trị tuyệt đối của thương số của hai toán hạng. Kết quả bằng 0 hoặc dương khi hai toán hạng có cùng dấu và 0 hoặc âm khi hai toán hạng có dấu trái ngược nhau.

Nếu bạn làm như sau:

int x = 13f / 4f;

Bạn sẽ nhận được một lỗi biên dịch, vì một phép chia dấu phẩy động ( /toán tử của 13f) dẫn đến một số float, không thể chuyển thành int ngầm.

Nếu bạn muốn phân chia là phân chia dấu phẩy động, bạn sẽ phải làm cho kết quả nổi:

float x = 13 / 4;

Lưu ý rằng bạn vẫn sẽ phân chia số nguyên, sẽ hoàn toàn được bỏ qua để nổi: kết quả sẽ là 3.0. Để khai báo rõ ràng các toán hạng là float, sử dụng fhậu tố ( 13f, 4f).


+1 để giải thích rằng bạn có thể có câu trả lời là dấu phẩy nhưng vẫn thực hiện phép chia số nguyên. Ngoài ra, một cách phổ biến khác mà tôi đã thấy để buộc phân chia dấu phẩy động là nhân số hạng đầu tiên của phép chia với 1.0.
Xynariz

8

Nó chỉ là một hoạt động cơ bản .

Hãy nhớ khi bạn học cách chia. Ban đầu chúng tôi đã giải quyết 9/6 = 1 with remainder 3.

9 / 6 == 1  //true
9 % 6 == 3 // true

/ -Operator kết hợp với% -operator được sử dụng để truy xuất các giá trị đó.


6

Có thể hữu ích:

double a = 5.0/2.0;   
Console.WriteLine (a);      // 2.5

double b = 5/2;   
Console.WriteLine (b);      // 2

int c = 5/2;   
Console.WriteLine (c);      // 2

double d = 5f/2f;   
Console.WriteLine (d);      // 2.5

Vui lòng thử thêm một số giải thích cho câu trả lời của bạn
NetStarter 8/11/2016

Biểu thức cuối cùng sẽ tạo ra 2.5, không 2.
Lasse V. Karlsen

Đúng, sai chính tả. Cảm ơn.
eozten

4

Kết quả sẽ luôn thuộc loại có phạm vi tử số và mẫu số lớn hơn. Các ngoại lệ là byte và ngắn, tạo ra int (Int32).

var a = (byte)5 / (byte)2;  // 2 (Int32)
var b = (short)5 / (byte)2; // 2 (Int32)
var c = 5 / 2;              // 2 (Int32)
var d = 5 / 2U;             // 2 (UInt32)
var e = 5L / 2U;            // 2 (Int64)
var f = 5L / 2UL;           // 2 (UInt64)
var g = 5F / 2UL;           // 2.5 (Single/float)
var h = 5F / 2D;            // 2.5 (Double)
var i = 5.0 / 2F;           // 2.5 (Double)
var j = 5M / 2;             // 2.5 (Decimal)
var k = 5M / 2F;            // Not allowed

Không có chuyển đổi ngầm giữa các loại dấu phẩy động và loại thập phân, vì vậy không được phép phân chia giữa chúng. Bạn phải phân vai rõ ràng và quyết định cái nào bạn muốn (Decimal có độ chính xác cao hơn và phạm vi nhỏ hơn so với các loại dấu phẩy động).

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.