เหตุใดการหารจำนวนเต็มใน C # จึงส่งคืนจำนวนเต็มไม่ใช่จำนวนทศนิยม


131

มีใครรู้บ้างว่าทำไมการหารจำนวนเต็มใน C # ส่งกลับจำนวนเต็มไม่ใช่ทศนิยม? ความคิดเบื้องหลังคืออะไร? (เป็นเพียงมรดกของ C / C ++ หรือไม่?)

ใน C #:

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

ผลลัพธ์ของรหัสนี้จะเป็น:

'Hello world'

พูดอย่างเคร่งครัดไม่มีสิ่งที่เรียกว่าการหารจำนวนเต็ม (การหารตามนิยามคือการดำเนินการที่สร้างจำนวนที่มีเหตุผลจำนวนเต็มเป็นส่วนย่อยที่เล็กมาก)


46
เพราะมันintegerไม่ใช่การfloating pointหาร
Hunter McMillen

มันต้อง (ใน VB.Net) มันถูกนำไปใช้ที่แตกต่างกันในวิธีทางคณิตศาสตร์ตามธรรมชาติโดยที่ผลลัพธ์ทั้งหมดของการดำเนินการหารเป็นจำนวนอตรรกยะ
BanditoBunny

3
ฉันคิดว่าคุณหมายถึงตัวเลขเหตุผล ดูวิกิพีเดีย : การหารจำนวนเต็มสองจำนวนอาจทำให้เหลือเศษ ในการแบ่งส่วนที่เหลือให้เสร็จสมบูรณ์ระบบจำนวนจะขยายเพื่อรวมเศษส่วนหรือจำนวนตรรกยะตามที่เรียกกันโดยทั่วไป
crashmstr

6
นี่คือเหตุผลที่ฉันไม่ชอบ "คัดลอกไวยากรณ์" ในภาษา ฉันมาจาก VB โดยคิดว่า "C # คือ. NET" ไม่ใช่ "C # ก็เหมือนกับ C" ฉันเดาผิด แต่ในกรณีนี้ฉันชอบวิธี VB มากกว่า หากพวกเขาประสบปัญหาในการสร้างข้อผิดพลาดของคอมไพเลอร์เมื่อใช้ประเภทง่าย ๆ ที่ไม่ได้กำหนดค่าเริ่มต้น (คุณไม่ได้รับคำเตือนใน C) ทำไมไม่เตือนคุณเมื่อคุณกำหนดการหารจำนวนเต็มให้เป็นจำนวนทศนิยม
darda

คำตอบ:


96

แม้ว่าโปรแกรมเมอร์มือใหม่จะทำผิดพลาดในการหารจำนวนเต็มเมื่อพวกเขาตั้งใจจะใช้การหารทศนิยม แต่ในทางปฏิบัติจริงการหารจำนวนเต็มถือเป็นการดำเนินการทั่วไป หากคุณสมมติว่าไม่ค่อยมีคนใช้และทุกครั้งที่คุณหารคุณจะต้องจำไว้เสมอว่าให้โยนไปที่จุดลอยตัวคุณจะเข้าใจผิด

ก่อนอื่นการหารจำนวนเต็มค่อนข้างเร็วกว่าเล็กน้อยดังนั้นหากคุณต้องการเพียงแค่ผลลัพธ์จำนวนเต็มเราก็ต้องการใช้อัลกอริทึมที่มีประสิทธิภาพมากขึ้น

ประการที่สองมีอัลกอริทึมจำนวนหนึ่งที่ใช้การหารจำนวนเต็มและหากผลลัพธ์ของการหารเป็นเลขทศนิยมเสมอคุณจะถูกบังคับให้ปัดเศษผลลัพธ์ทุกครั้ง ตัวอย่างหนึ่งจากด้านบนของหัวของฉันคือการเปลี่ยนฐานของตัวเลข การคำนวณแต่ละหลักเกี่ยวข้องกับการหารจำนวนเต็มของตัวเลขพร้อมกับเศษที่เหลือแทนที่จะเป็นการหารทศนิยมของจำนวน

เนื่องจากเหตุผลเหล่านี้ (และอื่น ๆ ที่เกี่ยวข้อง) การหารจำนวนเต็มจึงทำให้เกิดจำนวนเต็ม หากคุณต้องการได้การหารทศนิยมของจำนวนเต็มสองจำนวนคุณจะต้องจำไว้ว่าให้แคสต์หนึ่งเป็น a double/ float/decimal /


5
ในสถาปนิก VB.Net .Net ได้ตัดสินใจอีกครั้ง: / - เสมอการหารลอย, \ - การหารจำนวนเต็มดังนั้นจึงไม่สอดคล้องกันยกเว้นว่าคุณพิจารณามรดกของ C ++
BanditoBunny

4
คุณสามารถกำหนดได้ในขณะคอมไพล์ว่าตัว/ดำเนินการจะทำการหารจำนวนเต็มหรือทศนิยมหรือไม่ (เว้นแต่คุณจะใช้ไดนามิก) หากคุณคิดออกได้ยากเพราะคุณทำในบรรทัดเดียวมากฉันขอแนะนำให้แบ่งบรรทัดนั้นออกเป็นหลาย ๆ บรรทัดเพื่อให้ง่ายต่อการพิจารณาว่าตัวถูกดำเนินการเป็นจำนวนเต็มหรือทศนิยม ผู้อ่านโค้ดของคุณในอนาคตน่าจะชื่นชอบ
Servy

5
โดยส่วนตัวแล้วฉันพบว่ามันเป็นปัญหาที่ฉันต้องคิดเสมอว่าอะไรคือตัวแปรที่ฉันกำลังหารฉันถือว่าเป็นการใช้ความสนใจอย่างสิ้นเปลือง
BanditoBunny

8
@pelesl เนื่องจากจะเป็นการเปลี่ยนแปลงครั้งใหญ่ในการทำเช่นนั้นซึ่งจำนวนโปรแกรมทางดาราศาสตร์จะเสียฉันสามารถพูดด้วยความมั่นใจอย่างเต็มที่ว่าจะไม่เกิดขึ้นใน C # นั่นเป็นสิ่งที่ต้องทำตั้งแต่วันที่ 1 ในภาษาหรือไม่ทำเลย
Servy

2
@Servy: มีหลายสิ่งเช่นนั้นใน C, C ++ และ C # โดยส่วนตัวแล้วฉันคิดว่า C # จะเป็นภาษาที่ดีกว่าหากมีตัวดำเนินการที่แตกต่างกันสำหรับการหารจำนวนเต็มและเพื่อหลีกเลี่ยงการมีรหัสที่ถูกต้องซึ่งก่อให้เกิดพฤติกรรมที่น่าประหลาดใจตัวint/intดำเนินการนั้นผิดกฎหมายเพียงอย่างเดียว [ด้วยการวินิจฉัยที่ระบุว่ารหัสต้องส่งตัวถูกดำเนินการหรือใช้ ตัวดำเนินการอื่น ๆ ขึ้นอยู่กับพฤติกรรมที่ต้องการ] มีลำดับโทเค็นอื่น ๆ ที่ดีสำหรับการหารจำนวนเต็มอาจเป็นไปได้ที่จะเลิกใช้/เพื่อจุดประสงค์นั้น แต่ฉันไม่รู้ว่าอะไรจะใช้ได้จริง
supercat

77

ดู C # ข้อกำหนด ตัวดำเนินการแบ่งมีสามประเภท

  • การหารจำนวนเต็ม
  • การแบ่งจุดลอยตัว
  • การหารทศนิยม

ในกรณีของคุณเรามีการหารจำนวนเต็มโดยใช้กฎต่อไปนี้:

การหารจะปัดเศษผลลัพธ์ให้เป็นศูนย์และค่าสัมบูรณ์ของผลลัพธ์คือจำนวนเต็มมากที่สุดที่เป็นไปได้ซึ่งน้อยกว่าค่าสัมบูรณ์ของผลหารของตัวถูกดำเนินการสองตัว ผลลัพธ์จะเป็นศูนย์หรือเป็นบวกเมื่อตัวถูกดำเนินการทั้งสองมีเครื่องหมายเดียวกันและเป็นศูนย์หรือเป็นลบเมื่อตัวถูกดำเนินการทั้งสองมีเครื่องหมายตรงข้ามกัน

ฉันคิดว่าเหตุผลที่ C # ใช้การหารประเภทนี้สำหรับจำนวนเต็ม (บางภาษาส่งคืนผลลัพธ์แบบลอยตัว) คือฮาร์ดแวร์ - การหารจำนวนเต็มเร็วและง่ายกว่า


ภาษาใดที่ส่งคืนผลลัพธ์แบบลอย @SergeyBerezovskiy
Ilaria

40

ข้อมูลแต่ละประเภทสามารถโอเวอร์โหลดโอเปอเรเตอร์แต่ละตัวได้ ถ้าทั้งตัวเศษและตัวส่วนเป็นจำนวนเต็มประเภทจำนวนเต็มจะดำเนินการหารและจะส่งกลับประเภทจำนวนเต็ม หากคุณต้องการแบ่งจุดลอยตัวคุณต้องโยนตัวเลขอย่างน้อยหนึ่งจำนวนเป็นประเภทจุดลอยตัวก่อนที่จะหาร ตัวอย่างเช่น:

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

หรือถ้าคุณใช้ตัวอักษร:

float x = 13f / 4f;

โปรดทราบว่าจุดลอยไม่แม่นยำ หากคุณสนใจเกี่ยวกับความแม่นยำให้ใช้ประเภททศนิยมแทน


1
+1 สำหรับการกล่าวถึงว่าต้องลอยเพียงหนึ่งเทอมเพื่อทำการหารทศนิยม
Xynariz

เห็นได้ชัดว่าคำพูดของคุณเกี่ยวกับความแม่นยำเป็นสิ่งที่ถูกต้องในบริบทของการเรียนรู้และทำให้ไม่ซับซ้อนในการทำความเข้าใจ เนื่องจากเราต้องมีความแม่นยำมากที่สุดในงานของเราฉันยังคงต้องการชี้แจงเกี่ยวกับความแม่นยำ: ตาม IEE 754-1985 คุณจะได้รับผลลัพธ์ที่แน่นอน (แม้ว่าส่วนใหญ่จะไม่ใช่กรณีนี้ก็ตาม) คุณจะได้ผลลัพธ์ที่แม่นยำเมื่อมีการนำเสนอค่าการคำนวณที่แน่นอนมาก่อนและผลลัพธ์ก็คือ - พูดง่ายๆ - ผลรวมของ 2 แม้ว่าจะไม่สามารถใช้แนวทางปฏิบัติที่ดีที่สุดในการพึ่งพาความแม่นยำนั้นในกรณีพิเศษเหล่านั้นได้
L. Monty

การเพิ่มความแม่นยำ: ความเป็นไปได้ที่จะได้ผลลัพธ์ที่แน่นอนดีขึ้นอย่างมากเนื่องจากผลลัพธ์ใกล้เคียงกับ 1 หรือ -1 อาจจะสับสนเล็กน้อยที่ความเป็นไปได้นี้ยังคงเป็น 0 เนื่องจากมีจำนวนอนันต์และผลลัพธ์จำนวน จำกัด ซึ่งสามารถนำเสนอได้อย่างแน่นอน :)
L. Monty

1
@ L.Monty ขอบคุณที่นำเรื่องนี้ขึ้นมา ฉันได้เรียนรู้เพิ่มเติมเกี่ยวกับจุดลอยตัวตั้งแต่เขียนคำตอบนี้และประเด็นที่คุณทำนั้นยุติธรรม ในทางเทคนิคฉันยังคงบอกว่าคำพูดของฉัน "จุดลอยตัวไม่แม่นยำ" เป็นที่ยอมรับในแง่ที่ว่าบางครั้งอาจมีความถูกต้องแม่นยำไม่ได้หมายความว่าโดยรวมแล้วแม่นยำ อย่างที่พวกเขาพูดกันว่านาฬิกาที่พังนั้นถูกต้องวันละสองครั้ง แต่ฉันจะไม่เรียกว่าเครื่องวัดความเที่ยงตรง ฉันค่อนข้างแปลกใจที่เป็นส่วนหนึ่งที่รบกวนคุณมากกว่าคำแนะนำของฉันที่ว่าประเภททศนิยมนั้นแม่นยำ
Steven Doggart

ทศนิยมไม่ชัดเจนด้วยเหตุผลเดียวกันทั้งหมดที่ทำให้เกิดการลอย เพียงแค่ว่าลอยเป็นฐาน -2 และทศนิยมเป็นฐาน -10 ตัวอย่างเช่นประเภททศนิยมไม่สามารถเก็บค่าที่แม่นยำของ 1/3 ได้อย่างแม่นยำ
Steven Doggart

11

เนื่องจากคุณไม่ได้ใช้คำต่อท้ายตัวอักษร13และ4ถูกตีความเป็นจำนวนเต็ม:

คู่มือ :

intถ้าตัวอักษรไม่ได้มีคำต่อท้ายก็มีครั้งแรกของประเภทนี้ที่คุ้มค่าสามารถแสดง: uint, long, ulong,

ดังนั้นเนื่องจากคุณประกาศ13เป็นจำนวนเต็มการหารจำนวนเต็มจะดำเนินการ:

คู่มือ :

สำหรับการดำเนินการของรูปแบบ x / y ความละเอียดเกินพิกัดของตัวดำเนินการไบนารีจะถูกนำไปใช้เพื่อเลือกการใช้งานตัวดำเนินการเฉพาะ ตัวถูกดำเนินการจะถูกแปลงเป็นประเภทพารามิเตอร์ของตัวดำเนินการที่เลือกและประเภทของผลลัพธ์คือประเภทการส่งคืนของตัวดำเนินการ

ตัวดำเนินการแบ่งที่กำหนดไว้ล่วงหน้าแสดงอยู่ด้านล่าง ตัวดำเนินการทั้งหมดคำนวณผลหารของ x และ y

การหารจำนวนเต็ม:

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

และการปัดเศษเกิดขึ้น:

การหารจะปัดเศษผลลัพธ์ให้เป็นศูนย์และค่าสัมบูรณ์ของผลลัพธ์คือจำนวนเต็มมากที่สุดที่เป็นไปได้ซึ่งน้อยกว่าค่าสัมบูรณ์ของผลหารของตัวถูกดำเนินการสองตัว ผลลัพธ์จะเป็นศูนย์หรือเป็นบวกเมื่อตัวถูกดำเนินการทั้งสองมีเครื่องหมายเดียวกันและเป็นศูนย์หรือเป็นลบเมื่อตัวถูกดำเนินการทั้งสองมีเครื่องหมายตรงกันข้าม

หากคุณทำสิ่งต่อไปนี้:

int x = 13f / 4f;

คุณจะได้รับข้อผิดพลาดของคอมไพเลอร์เนื่องจากการหารทศนิยม (ตัว/ดำเนินการของ13f ) ส่งผลให้เกิดการลอยซึ่งไม่สามารถส่งเป็น int โดยปริยายได้

หากคุณต้องการให้การหารเป็นทศนิยมคุณจะต้องทำให้ผลลัพธ์เป็นแบบลอย:

float x = 13 / 4;

สังเกตว่าคุณจะยังคงหารจำนวนเต็มซึ่งโดยปริยายจะถูกเหวี่ยงให้ลอย: ผลลัพธ์จะเป็น 3.0ผลจะเป็น ในการประกาศตัวถูกดำเนินการเป็น float อย่างชัดเจนโดยใช้fคำต่อท้าย ( 13f, 4f)


+1 เพื่ออธิบายว่าคุณสามารถมีคำตอบเป็นทศนิยมได้ แต่ยังคงหารจำนวนเต็ม 1.0นอกจากนี้อีกหนึ่งวิธีการทั่วไปที่ผมเคยเห็นการแบ่งแรงลอยจุดคือการคูณในระยะแรกของการแบ่งโดย
Xynariz

8

เพียงใช้ดำเนินงานขั้นพื้นฐาน

จำไว้ว่าเมื่อคุณเรียนรู้ที่จะแบ่งแยก 9/6 = 1 with remainder 3ในการเริ่มต้นที่เราแก้ไขได้

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

/ -operator ร่วมกับ% -operator ถูกใช้เพื่อดึงค่าเหล่านั้น


6

อาจมีประโยชน์:

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

โปรดลองเพิ่มคำอธิบายสำหรับคำตอบของคุณ
NetStarter

การแสดงออกที่ผ่านมาจะผลิตไม่ได้2.5 2
Lasse V.Karlsen

ใช่การสะกดผิด ขอบคุณ
eozten

4

ผลลัพธ์จะเป็นประเภทที่มีช่วงของตัวเศษและตัวส่วนมากกว่าเสมอ ข้อยกเว้นคือไบต์และแบบสั้นซึ่งทำให้เกิด 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

ไม่มีการแปลงโดยนัยระหว่างประเภททศนิยมและประเภททศนิยมดังนั้นจึงไม่อนุญาตให้แบ่งระหว่างประเภทเหล่านี้ คุณต้องแคสต์อย่างชัดเจนและตัดสินใจว่าคุณต้องการอันไหน (ทศนิยมมีความแม่นยำมากกว่าและมีช่วงที่เล็กกว่าเมื่อเทียบกับประเภททศนิยม)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.