ฉันต้องการให้แน่ใจว่าการหารจำนวนเต็มจะถูกปัดเศษขึ้นเสมอหากจำเป็น มีวิธีที่ดีกว่านี้ไหม มีการหล่อที่เกิดขึ้นมากมาย :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
ฉันต้องการให้แน่ใจว่าการหารจำนวนเต็มจะถูกปัดเศษขึ้นเสมอหากจำเป็น มีวิธีที่ดีกว่านี้ไหม มีการหล่อที่เกิดขึ้นมากมาย :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
คำตอบ:
UPDATE: คำถามนี้เป็นเรื่องของบล็อกของฉันในเดือนมกราคม 2013 ขอบคุณสำหรับคำถามที่ยอดเยี่ยม!
การคำนวณเลขจำนวนเต็มถูกต้องยาก ดังที่ได้รับการพิสูจน์อย่างพอเพียงแล้วในขณะที่คุณพยายามทำเคล็ดลับ "ฉลาด" อัตราต่อรองเป็นสิ่งที่ดีที่คุณทำผิดพลาด และเมื่อพบข้อบกพร่องการเปลี่ยนรหัสเพื่อแก้ไขข้อบกพร่องโดยไม่พิจารณาว่าการแก้ไขแบ่งอย่างอื่นไม่ใช่เทคนิคการแก้ปัญหาที่ดีหรือไม่ จนถึงตอนนี้เราได้คิดแล้วว่าวิธีแก้ปัญหาเลขจำนวนเต็มไม่ถูกต้องห้าแบบสำหรับปัญหานี้โพสต์ไม่ยากโดยเฉพาะอย่างยิ่ง
วิธีที่ถูกต้องในการเข้าหาปัญหาเลขคณิตจำนวนเต็มนั่นคือวิธีที่เพิ่มโอกาสในการได้คำตอบที่ถูกต้องตั้งแต่ครั้งแรกคือการเข้าหาปัญหาอย่างรอบคอบแก้ปัญหาทีละขั้นตอนและใช้หลักการทางวิศวกรรมที่ดีในการทำ ดังนั้น.
เริ่มต้นด้วยการอ่านข้อกำหนดสำหรับสิ่งที่คุณพยายามจะเปลี่ยน ข้อกำหนดสำหรับการแบ่งจำนวนเต็มอย่างชัดเจนระบุ:
การหารจะปัดผลลัพธ์เป็นศูนย์
ผลลัพธ์เป็นศูนย์หรือบวกเมื่อตัวถูกดำเนินการทั้งสองมีเครื่องหมายเหมือนกันและศูนย์หรือลบเมื่อตัวถูกดำเนินการทั้งสองมีเครื่องหมายตรงข้าม
หากตัวถูกดำเนินการด้านซ้ายเป็น int ที่สามารถแทนค่าได้น้อยที่สุดและตัวถูกดำเนินการด้านขวาคือ –1 จะเกิดการล้น [... ] มันคือการดำเนินการที่กำหนดว่าเป็น [ArithmeticException] ถูกโยนหรือล้นไปไม่ได้รายงานกับค่าผลลัพธ์เป็นที่ของตัวถูกดำเนินการด้านซ้าย
ถ้าค่าของตัวถูกดำเนินการด้านขวาเป็นศูนย์ System.DivideByZeroException จะถูกส่งออกไป
สิ่งที่เราต้องการคือฟังก์ชั่นแบ่งจำนวนเต็มซึ่งคำนวณฉลาด แต่รอบผลเสมอขึ้นไม่เสมอไปทางศูนย์
ดังนั้นเขียนสเปคสำหรับฟังก์ชันนั้น ฟังก์ชั่นของเราint DivRoundUp(int dividend, int divisor)
จะต้องมีพฤติกรรมที่กำหนดไว้สำหรับทุกการป้อนข้อมูลที่เป็นไปได้ พฤติกรรมที่ไม่ได้กำหนดนั้นเป็นสิ่งที่น่ากังวลอย่างมากดังนั้นให้กำจัดมันออกไป เราจะบอกว่าการดำเนินการของเรามีข้อกำหนดนี้:
การดำเนินการพ่นถ้าตัวหารเป็นศูนย์
การดำเนินการโยนถ้าเงินปันผลเป็น int.minval และตัวหารคือ -1
หากไม่มีส่วนที่เหลือ - การหารคือ 'คู่' - ดังนั้นค่าส่งคืนคือความฉลาดทางอินทิเกรต
มิฉะนั้นก็จะส่งกลับมีขนาดเล็กที่สุดจำนวนเต็มที่มีมากขึ้นกว่าความฉลาดที่เป็นก็มักจะปัดเศษขึ้น
ตอนนี้เรามีสเปคเพื่อให้เรารู้ว่าเราสามารถเกิดขึ้นกับการออกแบบทดสอบ สมมติว่าเราเพิ่มเกณฑ์การออกแบบเพิ่มเติมว่าปัญหาจะได้รับการแก้ไขด้วยเลขคณิตเลขจำนวนเต็มแทนที่จะคำนวณความฉลาดเป็นสองเท่าเนื่องจากการแก้ปัญหา "double" ถูกปฏิเสธอย่างชัดเจนในคำชี้แจงปัญหา
ดังนั้นเราต้องคำนวณอะไร เห็นได้ชัดว่าเพื่อให้เป็นไปตามข้อกำหนดของเราในขณะที่เหลืออยู่ แต่เพียงผู้เดียวในการคำนวณจำนวนเต็มเราจำเป็นต้องรู้ข้อเท็จจริงสามประการ ก่อนอื่นความฉลาดทางเลขจำนวนเต็มคืออะไร? ประการที่สองการแบ่งเป็นอิสระจากที่เหลือ? และที่สามถ้าไม่หารด้วยผลหารหารด้วยการปัดเศษขึ้นหรือลง
ตอนนี้เรามีสเปคและการออกแบบเราสามารถเริ่มเขียนโค้ดได้
public static int DivRoundUp(int dividend, int divisor)
{
if (divisor == 0 ) throw ...
if (divisor == -1 && dividend == Int32.MinValue) throw ...
int roundedTowardsZeroQuotient = dividend / divisor;
bool dividedEvenly = (dividend % divisor) == 0;
if (dividedEvenly)
return roundedTowardsZeroQuotient;
// At this point we know that divisor was not zero
// (because we would have thrown) and we know that
// dividend was not zero (because there would have been no remainder)
// Therefore both are non-zero. Either they are of the same sign,
// or opposite signs. If they're of opposite sign then we rounded
// UP towards zero so we're done. If they're of the same sign then
// we rounded DOWN towards zero, so we need to add one.
bool wasRoundedDown = ((divisor > 0) == (dividend > 0));
if (wasRoundedDown)
return roundedTowardsZeroQuotient + 1;
else
return roundedTowardsZeroQuotient;
}
มันฉลาดไหม ไม่สวยหรอ ไม่สั้น ไม่ถูกต้องตามข้อกำหนด? ฉันเชื่ออย่างนั้น แต่ฉันยังไม่ได้ทดสอบอย่างเต็มที่ มันดูดีทีเดียว
เราเป็นมืออาชีพที่นี่; ใช้วิธีปฏิบัติทางวิศวกรรมที่ดี วิจัยเครื่องมือของคุณระบุพฤติกรรมที่ต้องการพิจารณากรณีข้อผิดพลาดก่อนและเขียนรหัสเพื่อเน้นความถูกต้องชัดเจน และเมื่อคุณพบข้อผิดพลาดให้พิจารณาว่าอัลกอริทึมของคุณมีข้อบกพร่องอย่างลึกล้ำที่จะเริ่มต้นด้วยก่อนที่คุณจะเริ่มสุ่มสลับทิศทางของการเปรียบเทียบรอบ ๆ และแบ่งสิ่งที่ใช้งานได้แล้ว
คำตอบทั้งหมดที่นี่ดูเหมือนจะซับซ้อนเกินไป
ใน C # และ Java สำหรับการปันผลและตัวหารที่เป็นบวกคุณต้องทำ:
( dividend + divisor - 1 ) / divisor
((13-1)%3)+1)
ให้ผลลัพธ์ 1 รายการ การแบ่งประเภทที่เหมาะสม1+(dividend - 1)/divisor
ให้ผลลัพธ์เช่นเดียวกับคำตอบของเงินปันผลและตัวหารที่เป็นบวก นอกจากนี้ยังไม่มีปัญหาล้น แต่เทียมพวกเขาอาจจะ
สำหรับจำนวนเต็มที่ลงนาม:
int div = a / b;
if (((a ^ b) >= 0) && (a % b != 0))
div++;
สำหรับจำนวนเต็มที่ไม่ได้ลงนาม:
int div = a / b;
if (a % b != 0)
div++;
การแบ่งจำนวนเต็ม ' /
' ถูกกำหนดให้ปัดไปทางศูนย์ (7.7.2 ของข้อมูลจำเพาะ) แต่เราต้องการปัดเศษขึ้น ซึ่งหมายความว่าคำตอบเชิงลบจะถูกปัดเศษอย่างถูกต้องแล้ว แต่จำเป็นต้องปรับคำตอบเชิงบวก
คำตอบเชิงบวกที่ไม่เป็นศูนย์นั้นง่ายต่อการตรวจสอบ แต่คำตอบที่เป็นศูนย์นั้นมีเล่ห์เหลี่ยมเล็กน้อยเนื่องจากนั่นอาจเป็นการปัดเศษของค่าลบหรือการปัดเศษของค่าบวก
เดิมพันที่ปลอดภัยที่สุดคือการตรวจสอบเมื่อคำตอบควรเป็นบวกโดยตรวจสอบว่าสัญญาณของจำนวนเต็มทั้งสองเหมือนกัน ตัวดำเนินการ xor จำนวนเต็ม ' ^
' บนค่าสองค่าจะทำให้ 0 sign-bit เมื่อเป็นกรณีนี้หมายถึงผลลัพธ์ที่ไม่เป็นลบดังนั้นการตรวจสอบ(a ^ b) >= 0
จะพิจารณาว่าผลลัพธ์ควรเป็นค่าบวกก่อนที่จะปัดเศษ นอกจากนี้โปรดทราบว่าสำหรับจำนวนเต็มที่ไม่ได้ลงชื่อทุกคำตอบเป็นบวกอย่างเห็นได้ชัดดังนั้นการตรวจสอบนี้สามารถละเว้น
การตรวจสอบที่เหลืออยู่เพียงอย่างเดียวคือว่ามีการปัดเศษเกิดขึ้นหรือไม่ซึ่งa % b != 0
จะทำงาน
เลขคณิต (จำนวนเต็มหรืออย่างอื่น) นั้นไม่ง่ายอย่างที่คิด คิดอย่างถี่ถ้วนทุกครั้ง
ถึงแม้ว่าคำตอบสุดท้ายของฉันอาจจะไม่ง่ายหรือชัดเจนหรือเร็วกว่าคำตอบสำหรับจุดลอยตัว แต่ก็มีคุณภาพการไถ่ที่แข็งแกร่งมากสำหรับฉัน ตอนนี้ฉันมีเหตุผลผ่านคำตอบดังนั้นฉันจึงแน่ใจว่าถูกต้อง (จนกว่าจะมีคนฉลาดแจ้งให้ฉันทราบเป็นอย่างอื่น -แอบดูทิศทางของเอริค -)
เพื่อให้ได้ความรู้สึกที่แน่นอนเกี่ยวกับคำตอบของจุดลอยผมต้องทำเพิ่มเติม (และอาจจะซับซ้อนกว่า) โดยคิดว่ามีเงื่อนไขใด ๆ ที่ความแม่นยำของจุดลอยตัวอาจเข้าทางและMath.Ceiling
อาจเป็นไปได้หรือไม่ สิ่งที่ไม่พึงประสงค์ในอินพุต 'เหมาะสม'
แทนที่ (โปรดทราบฉันเปลี่ยนวินาทีmyInt1
ด้วยmyInt2
สมมติว่าเป็นสิ่งที่คุณหมายถึง):
(int)Math.Ceiling((double)myInt1 / myInt2)
ด้วย:
(myInt1 - 1 + myInt2) / myInt2
ข้อแม้เพียงอย่างเดียวว่าถ้าmyInt1 - 1 + myInt2
ล้นชนิดจำนวนเต็มที่คุณกำลังใช้คุณอาจจะไม่ได้รับสิ่งที่คุณคาดหวัง
เหตุผลนี้ผิด : -1000000 และ 3999 ควรให้ -250 นี่ให้ -249
แก้ไข:
พิจารณานี้มีข้อผิดพลาดเช่นเดียวกับการแก้ปัญหาอื่น ๆ สำหรับจำนวนเต็มเชิงลบmyInt1
ค่ามันอาจจะง่ายที่จะทำสิ่งที่ชอบ:
int rem;
int div = Math.DivRem(myInt1, myInt2, out rem);
if (rem > 0)
div++;
ที่ควรให้ผลลัพธ์ที่ถูกต้องในการdiv
ใช้การดำเนินการจำนวนเต็มเท่านั้น
เหตุผลนี้ผิด : -1 และ -5 ควรให้ 1, นี่ให้ 0
แก้ไข (อีกครั้งด้วยความรู้สึก):
ผู้ดำเนินการแผนกปัดเศษเป็นศูนย์ สำหรับผลลัพธ์เชิงลบสิ่งนี้ถูกต้องดังนั้นเฉพาะผลลัพธ์ที่ไม่ใช่เชิงลบเท่านั้นที่ต้องมีการปรับ นอกจากนี้ยังพิจารณาว่าDivRem
เพียงแค่ทำ/
และ%
ต่อไปลองข้ามสาย (และเริ่มต้นด้วยการเปรียบเทียบง่าย ๆ เพื่อหลีกเลี่ยงการคำนวณแบบโมดูโลเมื่อไม่จำเป็น):
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
เหตุผลนี้ผิด : -1 และ 5 ควรให้ 0 นี่ให้ 1
(ในการป้องกันของตัวเองของความพยายามครั้งสุดท้ายของฉันฉันไม่ควรพยายามตอบเหตุผลในขณะที่ใจของฉันบอกฉันว่าฉันนอนดึก 2 ชั่วโมง)
โอกาสที่สมบูรณ์แบบในการใช้ส่วนขยาย:
public static class Int32Methods
{
public static int DivideByAndRoundUp(this int number, int divideBy)
{
return (int)Math.Ceiling((float)number / (float)divideBy);
}
}
ทำให้โค้ดของคุณสามารถอ่านได้ด้วย:
int result = myInt.DivideByAndRoundUp(4);
คุณสามารถเขียนผู้ช่วย
static int DivideRoundUp(int p1, int p2) {
return (int)Math.Ceiling((double)p1 / p2);
}
คุณสามารถใช้สิ่งต่อไปนี้
a / b + ((Math.Sign(a) * Math.Sign(b) > 0) && (a % b != 0)) ? 1 : 0)
คำตอบข้างต้นบางข้อใช้ลอยตัวซึ่งไม่มีประสิทธิภาพและไม่จำเป็นจริงๆ สำหรับ ints ที่ไม่ได้ลงชื่อนี่คือคำตอบที่มีประสิทธิภาพสำหรับ int1 / int2:
(int1 == 0) ? 0 : (int1 - 1) / int2 + 1;
สำหรับ ints ที่ลงนามแล้วจะไม่ถูกต้อง
ปัญหาของการแก้ปัญหาทั้งหมดที่นี่คือพวกเขาต้องการนักแสดงหรือพวกเขามีปัญหาเชิงตัวเลข การหล่อเพื่อลอยหรือเป็นสองเท่าเป็นทางเลือกเสมอ แต่เราทำได้ดีกว่า
เมื่อคุณใช้รหัสของคำตอบจาก @jerryjvl
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
มีข้อผิดพลาดในการปัดเศษ 1/5 จะปัดเศษขึ้นเนื่องจาก 1% 5! = 0 แต่นี่เป็นความผิดเพราะการปัดเศษจะเกิดขึ้นก็ต่อเมื่อคุณแทนที่ 1 ด้วย 3 ดังนั้นผลลัพธ์คือ 0.6 เราต้องหาวิธีที่จะปัดเศษขึ้นเมื่อการคำนวณให้ค่าที่มากกว่าหรือเท่ากับ 0.5 ผลลัพธ์ของโอเปอเรเตอร์โมดูโลในตัวอย่างด้านบนมีช่วงตั้งแต่ 0 ถึง myInt2-1 การปัดเศษจะเกิดขึ้นก็ต่อเมื่อส่วนที่เหลือมากกว่า 50% ของตัวหาร ดังนั้นรหัสที่ปรับจะมีลักษณะดังนี้:
int div = myInt1 / myInt2;
if (myInt1 % myInt2 >= myInt2 / 2)
div++;
แน่นอนว่าเรามีปัญหาการปัดเศษที่ myInt2 / 2 เช่นกัน แต่ผลลัพธ์นี้จะช่วยให้คุณมีวิธีการปัดเศษที่ดีกว่าอีกวิธีหนึ่งในเว็บไซต์นี้