round () สำหรับ float ใน C ++


232

ฉันต้องการฟังก์ชั่นการปัดเศษทศนิยมง่ายๆเช่น:

double round(double);

round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1

ฉันสามารถหาceil()และfloor()ใน math.h - round()แต่ไม่ได้

มีอยู่ในไลบรารี C ++ มาตรฐานภายใต้ชื่ออื่นหรือไม่หรือไม่?


1
หากคุณเพียงแค่ต้องการส่งออกตัวเลขเป็นตัวเลขที่ปัดเศษดูเหมือนว่าคุณสามารถทำได้std::cout << std::fixed << std::setprecision(0) << -0.9เช่น
แฟรงค์

44
การปกป้องสิ่งนี้ ... ผู้ใช้ใหม่ที่มีโครงร่างการปัดเศษใหม่ที่ยอดเยี่ยมควรอ่านคำตอบที่มีอยู่ก่อน
Shog9

12
roundสามารถใช้ได้ตั้งแต่ C ++ <cmath>11 น่าเสียดายถ้าคุณอยู่ใน Microsoft Visual Studio มันยังขาดอยู่: connect.microsoft.com/VisualStudio/feedback/details/775474/…
Alessandro Jacopson

3
ตามที่ฉันบันทึกไว้ในคำตอบการกลิ้งของคุณroundมีข้อแม้มากมาย ก่อน C ++ 11 มาตรฐานที่พึ่ง C90 roundซึ่งไม่ได้รวม C ++ 11 ขึ้นอยู่กับ C99 ซึ่งมีroundแต่ตามที่ระบุไว้truncซึ่งรวมถึงคุณสมบัติที่แตกต่างกันและอาจเหมาะสมกว่าขึ้นอยู่กับแอปพลิเคชัน คำตอบส่วนใหญ่ดูเหมือนจะไม่สนใจว่าผู้ใช้อาจต้องการคืนประเภทที่มีปัญหามากยิ่งขึ้น
Shafik Yaghmour

2
@uvts_cvs ดูเหมือนว่านี่จะไม่เป็นปัญหาสำหรับ visual Studio เวอร์ชันล่าสุดให้ดูได้ทันที
Shafik Yaghmour

คำตอบ:


144

ไม่มีรอบ () ในไลบรารีมาตรฐาน C ++ 98 คุณสามารถเขียนเองได้ ต่อไปนี้คือการใช้งานของรอบครึ่ง :

double round(double d)
{
  return floor(d + 0.5);
}

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


53
วิธีนี้ไม่สามารถจัดการกับจำนวนลบได้อย่างถูกต้อง คำตอบโดย litb ถูกต้อง
ผู้ใช้ที่ลงทะเบียน

39
@InnerJoin: ใช่มันจัดการกับจำนวนลบต่างไปจากคำตอบของ litb แต่นั่นไม่ได้ทำให้ "ไม่ถูกต้อง"
Roddy

39
การเพิ่ม 0.5 ก่อนตัดทอนไม่สามารถปัดเศษเป็นจำนวนเต็มที่ใกล้เคียงที่สุดสำหรับอินพุตหลาย ๆ อันรวมถึง 0.49999999999999994 ดูblog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Pascal Cuoq

10
@ Sergi0: ไม่มี "ถูกต้อง" และ "ไม่ถูกต้อง" เพราะมีคำจำกัดความของการปัดเศษมากกว่าหนึ่งคำเพื่อตัดสินว่าจะเกิดอะไรขึ้นที่จุดกึ่งกลาง ตรวจสอบข้อเท็จจริงของคุณก่อนที่จะผ่านการตัดสิน
Jon

16
@MuhammadAnnaqeeb: ถูกต้องทุกอย่างได้รับการพัฒนาอย่างมากนับตั้งแต่เปิดตัว C ++ 11 คำถามนี้ถูกถามและตอบในเวลาอื่นเมื่อชีวิตยากและความสุขมีน้อย มันยังคงอยู่ที่นี่เป็นบทกวีสำหรับวีรบุรุษที่อาศัยอยู่และต่อสู้ย้อนกลับไปและสำหรับจิตวิญญาณที่ยากจนที่ยังไม่สามารถใช้เครื่องมือที่ทันสมัย
Andreas Magnusson

96

Boost เสนอฟังก์ชั่นการปัดเศษอย่างง่าย

#include <boost/math/special_functions/round.hpp>

double a = boost::math::round(1.5); // Yields 2.0
int b = boost::math::iround(1.5); // Yields 2 as an integer

สำหรับข้อมูลเพิ่มเติมโปรดดูที่เอกสารเพิ่ม

แก้ไข : ตั้งแต่ C ++ 11 มีstd::round,std::lroundstd::llroundและ


2
ฉันใช้การเพิ่มในโครงการของฉัน +1 สำหรับสิ่งนี้ดีกว่าการใช้floor(value + 0.5)วิธีการไร้เดียงสา!
Gustavo Maciel

@GustavoMaciel ฉันรู้ว่าฉันบิตปลายที่จะเล่นเกม floor(value + 0.5)แต่การดำเนินงานเพิ่มขึ้นเป็น
n คำสรรพนาม 'm

ที่จริงแล้วมันไม่ได้: github.com/boostorg/math/blob/develop/include/boost/math/… 4 ปีต่อมาฉันอยากจะบอกว่าfloor(value + 0.5)ไม่ไร้เดียงสาเลย แต่ขึ้นอยู่กับบริบทและธรรมชาติ ของค่าที่คุณต้องการปัดเศษ!
Gustavo Maciel

84

มาตรฐาน C ++ 03 อาศัยมาตรฐาน C90 สำหรับสิ่งที่มาตรฐานเรียกว่าไลบรารี C มาตรฐานซึ่งครอบคลุมในร่างมาตรฐาน C ++ 03 ( มาตรฐานร่างสาธารณะที่ใกล้เคียงที่สุดกับ C ++ 03 คือ N1804 ) ส่วน1.2 อ้างอิงเชิงบรรทัด :

ไลบรารีที่อธิบายไว้ในข้อ 7 ของ ISO / IEC 9899: 1990 และข้อ 7 ของ ISO / IEC 9899 / Amd.1: 1995 ต่อไปนี้จะเรียกว่า Standard C Library 1)

ถ้าเราไปที่เอกสาร C สำหรับรอบ lround, llround บน cppreferenceเราจะเห็นว่ารอบและฟังก์ชันที่เกี่ยวข้องนั้นเป็นส่วนหนึ่งของC99และจะไม่สามารถใช้ได้ใน C ++ 03 หรือก่อนหน้า

ใน C ++ 11 การเปลี่ยนแปลงนี้นับตั้งแต่ C ++ 11 อาศัยมาตรฐาน C99 ฉบับร่างสำหรับไลบรารีมาตรฐาน Cดังนั้นจึงจัดเตรียมstd :: round และสำหรับประเภทคืนที่ครบถ้วน std :: lround, std :: llround :

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::round( 0.4 ) << " " << std::lround( 0.4 ) << " " << std::llround( 0.4 ) << std::endl ;
    std::cout << std::round( 0.5 ) << " " << std::lround( 0.5 ) << " " << std::llround( 0.5 ) << std::endl ;
    std::cout << std::round( 0.6 ) << " " << std::lround( 0.6 ) << " " << std::llround( 0.6 ) << std::endl ;
}

ตัวเลือกอื่นจาก C99 จะเป็นstd :: truncซึ่ง:

คำนวณจำนวนเต็มที่ใกล้ที่สุดไม่มากกว่าขนาด ARG

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::trunc( 0.4 ) << std::endl ;
    std::cout << std::trunc( 0.9 ) << std::endl ;
    std::cout << std::trunc( 1.1 ) << std::endl ;

}

หากคุณต้องการที่จะสนับสนุนไม่ใช่ C ++ 11 การใช้งานทางออกที่ดีที่สุดของคุณจะใช้รอบส่งเสริม iRound, lround, llroundหรือเพิ่ม TRUNC

การกลิ้งเวอร์ชั่นรอบของคุณเองนั้นยาก

กลิ้งของคุณเองอาจจะไม่คุ้มค่าความพยายามเป็นหนักกว่ามันก็ดู: ปัดเศษลอยตัวเต็มที่ใกล้เคียงที่สุดส่วนที่ 1 , ปัดเศษลอยตัวเต็มที่ใกล้เคียงที่สุดส่วนที่ 2และปัดเศษลอยตัวเต็มที่ใกล้เคียงที่สุด, ส่วน 3อธิบาย:

ตัวอย่างเช่นการใช้งานทั่วไปของคุณโดยใช้std::floorและการเพิ่ม0.5ไม่ทำงานสำหรับอินพุตทั้งหมด:

double myround(double d)
{
  return std::floor(d + 0.5);
}

หนึ่งอินพุตที่จะล้มเหลวสำหรับคือ0.49999999999999994( ดูสด )

การใช้งานทั่วไปอื่น ๆ เกี่ยวข้องกับการคัดเลือกประเภทจุดลอยตัวเป็นประเภทอินทิกรัลซึ่งสามารถเรียกใช้พฤติกรรมที่ไม่ได้กำหนดในกรณีที่ส่วนอินทิกรัลไม่สามารถแสดงในประเภทปลายทางได้ เราสามารถเห็นสิ่งนี้ได้จากร่างมาตรฐาน C ++ ส่วน4.9 การแปลงแบบอินทิกรัลแบบลอยตัวซึ่งกล่าวว่า (การเน้นที่เหมือง ):

prvalue ของชนิด floating point สามารถแปลงเป็น prvalue ของชนิดจำนวนเต็ม การแปลงตัดทอน นั่นคือส่วนที่เป็นเศษส่วนจะถูกยกเลิก พฤติกรรมจะไม่ได้กำหนดถ้าค่าที่ถูกตัดทอนไม่สามารถแสดงในประเภทปลายทาง [ ... ]

ตัวอย่างเช่น:

float myround(float f)
{
  return static_cast<float>( static_cast<unsigned int>( f ) ) ;
}

ป.ร. ให้ไว้std::numeric_limits<unsigned int>::max()เป็น4294967295แล้วโทรต่อไปนี้:

myround( 4294967296.5f ) 

จะทำให้เกิดการล้น ( ดูสด )

เราสามารถเห็นความยากลำบากนี้จริงๆโดยดูที่คำตอบนี้เพื่อกระชับวิธีการใช้รอบ () ใน C? ซึ่งอ้างอิงรุ่นnewlibsของความแม่นยำรอบลอยเดี่ยว มันเป็นฟังก์ชั่นที่ยาวมาก ๆ สำหรับบางสิ่งที่ดูเรียบง่าย ดูเหมือนว่าไม่น่าเป็นไปได้ที่ทุกคนที่ไม่มีความรู้อย่างลึกซึ้งเกี่ยวกับการใช้งานจุดลอยตัวสามารถใช้ฟังก์ชันนี้ได้อย่างถูกต้อง:

float roundf(x)
{
  int signbit;
  __uint32_t w;
  /* Most significant word, least significant word. */
  int exponent_less_127;

  GET_FLOAT_WORD(w, x);

  /* Extract sign bit. */
  signbit = w & 0x80000000;

  /* Extract exponent field. */
  exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;

  if (exponent_less_127 < 23)
    {
      if (exponent_less_127 < 0)
        {
          w &= 0x80000000;
          if (exponent_less_127 == -1)
            /* Result is +1.0 or -1.0. */
            w |= ((__uint32_t)127 << 23);
        }
      else
        {
          unsigned int exponent_mask = 0x007fffff >> exponent_less_127;
          if ((w & exponent_mask) == 0)
            /* x has an integral value. */
            return x;

          w += 0x00400000 >> exponent_less_127;
          w &= ~exponent_mask;
        }
    }
  else
    {
      if (exponent_less_127 == 128)
        /* x is NaN or infinite. */
        return x + x;
      else
        return x;
    }
  SET_FLOAT_WORD(x, w);
  return x;
}

ในทางกลับกันหากไม่มีวิธีการแก้ปัญหาอื่นใดnewlib ที่ใช้งานได้อาจเป็นตัวเลือกเนื่องจากเป็นการใช้งานที่ผ่านการทดสอบอย่างดี


5
@downvoter โปรดอธิบายสิ่งที่สามารถปรับปรุงได้? คำตอบส่วนใหญ่ที่นี่เป็นเพียงผิดเพราะพวกเขาพยายามที่จะหมุนรอบของตัวเองซึ่งทั้งหมดล้มเหลวในรูปแบบเดียวหรืออื่น หากคำอธิบายของฉันหายไปโปรดแจ้งให้เราทราบ
Shafik Yaghmour

1
คำตอบที่สมบูรณ์ - โดยเฉพาะส่วนที่ต่ำกว่า 0.5 ช่องอื่น: round(-0.0). C spec ไม่ปรากฏขึ้นเพื่อระบุ ฉันคาดหวัง-0.0ผล
chux - Reinstate Monica

3
@chux ที่น่าสนใจและมาตรฐาน IEEE 754-2008 ระบุว่าการปัดเศษเก็บรักษาสัญญาณของศูนย์และอินฟินิตี้ (ดู 5.9)
Ruslan

1
@Shafik นี่คือคำตอบที่ดี ฉันไม่เคยคิดมาก่อนเลยว่าการปัดเศษเป็นเรื่องที่ไม่สำคัญ
Ruslan

1
บางทีก็ควรค่าแก่การกล่าวถึงซึ่งstd::rint()มักนิยมใช้std::round()เมื่อ C ++ 11 พร้อมใช้งานด้วยเหตุผลด้านตัวเลขและประสิทธิภาพ มันใช้โหมดการปัดเศษปัจจุบันซึ่งแตกต่างจากround()โหมดพิเศษของ มันมีประสิทธิภาพมากขึ้นใน x86 ซึ่งrintสามารถอยู่ในคำสั่งเดียวได้ (gcc และเสียงดังกราวทำอย่างนั้นแม้ไม่มี-ffast-math godbolt.org/g/5UsL2eในขณะที่เสียงดังกราวเข้าใกล้เกือบเทียบเท่าnearbyint()) ARM มีคำสั่งสนับสนุนเพียงคำสั่งเดียวround()แต่ใน x86 มันสามารถอินไลน์ได้หลายคำสั่งและเฉพาะกับ-ffast-math
Peter Cordes

71

อาจเป็นเรื่องน่าสังเกตว่าถ้าคุณต้องการผลลัพธ์จำนวนเต็มจากการปัดเศษคุณไม่จำเป็นต้องผ่านมันผ่านเพดานหรือพื้น กล่าวคือ

int round_int( double r ) {
    return (r > 0.0) ? (r + 0.5) : (r - 0.5); 
}

3
ไม่ได้ให้ผลลัพธ์ที่คาดหวังสำหรับ 0.49999999999999994 แต่ (ดีขึ้นอยู่กับสิ่งที่คุณคาดหวังแน่นอน แต่ 0 ดูเหมือนสมเหตุสมผลกับฉันมากกว่า 1)
stijn

@stijn จับได้ดี ฉันพบว่าการเพิ่มส่วนต่อท้ายตัวอักษรสองตัวแบบยาวในค่าคงที่ของฉันแก้ไขปัญหาตัวอย่างของคุณ แต่ฉันไม่ทราบว่ามีตัวอย่างความแม่นยำอื่น ๆ ที่ไม่สามารถจับได้หรือไม่
kalaxy

1
btw ถ้าคุณเพิ่ม 0.49999999999999994 แทนที่จะเป็น 0.5 มันก็ใช้ได้ทั้ง 0.4999999999999999994 และ 5000000000000001.0 เป็นทั้งอินพุต ไม่แน่ใจว่ามันใช้ได้กับค่าทั้งหมดหรือไม่และฉันไม่พบข้อมูลอ้างอิงใด ๆ ที่ระบุว่านี่เป็นการแก้ไขขั้นสุดท้าย
stijn

1
@stijn มันก็โอเคสำหรับค่าทั้งหมดถ้าคุณไม่สนใจว่าทิศทางใดค่าที่อยู่ในระหว่างจำนวนเต็มสองจำนวนจะถูกปัดเศษ โดยไม่ต้องคิดฉันจะพิสูจน์ด้วยการวิเคราะห์กรณีด้วยกรณีต่อไปนี้: 0 <= d <0.5, 0.5 <= d <1.5, 1.5 <= d <2 ^ 52, d> = 2 ^ 52 ฉันยังทดสอบเคสที่มีความแม่นยำเดียวอย่างละเอียด
Pascal Cuoq

3
ต่อ 4.9 [conv.fpint], "พฤติกรรมไม่ได้กำหนดไว้หากค่าที่ถูกตัดทอนไม่สามารถแสดงในประเภทปลายทางได้" ดังนั้นนี่เป็นอันตรายเล็กน้อย คำตอบ SO อื่น ๆ อธิบายวิธีการทำสิ่งนี้อย่างสมบูรณ์
Tony Delroy

41

มีให้บริการตั้งแต่ C ++ 11 ในหน่วย cmath (อ้างอิงจากhttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf )

#include <cmath>
#include <iostream>

int main(int argc, char** argv) {
  std::cout << "round(0.5):\t" << round(0.5) << std::endl;
  std::cout << "round(-0.5):\t" << round(-0.5) << std::endl;
  std::cout << "round(1.4):\t" << round(1.4) << std::endl;
  std::cout << "round(-1.4):\t" << round(-1.4) << std::endl;
  std::cout << "round(1.6):\t" << round(1.6) << std::endl;
  std::cout << "round(-1.6):\t" << round(-1.6) << std::endl;
  return 0;
}

เอาท์พุท:

round(0.5):  1
round(-0.5): -1
round(1.4):  1
round(-1.4): -1
round(1.6):  2
round(-1.6): -2

1
นอกจากนี้ยังมีlroundและllroundสำหรับผลลัพธ์รวม
sp2danny

@ sp2danny: หรือดีกว่าlrintเพื่อใช้โหมดการปัดเศษในปัจจุบันแทนไทม์เบรคแบบไม่ต้องroundอยู่ห่างจากศูนย์
Peter Cordes

27

floor(value + 0.5)ก็มักจะนำมาใช้เป็น

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


1
มันเป็นการดีที่จะสร้างความแตกต่างระหว่าง 'round' รุ่นต่างๆ เป็นการดีที่จะรู้ว่าเมื่อใดควรเลือกเช่นกัน
xtofl

5
มีอัลกอริทึมการปัดเศษที่แตกต่างกันอย่างแน่นอนซึ่งทุกคนสามารถอ้างเหตุผลว่า "ถูกต้อง" อย่างไรก็ตามชั้น (ค่า + 0.5) ไม่ใช่หนึ่งในสิ่งเหล่านี้ สำหรับค่าบางค่าเช่น 0.49999997f หรือ double double คำตอบนั้นผิด - มันจะถูกปัดเป็น 1.0 เมื่อทุกคนยอมรับว่าควรเป็นศูนย์ ดูโพสต์นี้สำหรับรายละเอียด: blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Bruce Dawson

14

มี 2 ​​ปัญหาที่เรากำลังดู:

  1. การปัดเศษการแปลง
  2. การแปลงประเภท

การแปลงการปัดเศษหมายถึงการปัดเศษ±การลอย / สองเท่าเป็นพื้นที่ใกล้ที่สุด / เพดานลอย / สองเท่า อาจเป็นปัญหาของคุณสิ้นสุดที่นี่ แต่ถ้าคุณคาดว่าจะส่งคืน Int / Long คุณจะต้องทำการแปลงประเภทและทำให้เกิดปัญหา "ล้น" อาจเข้าสู่โซลูชันของคุณ ดังนั้นตรวจสอบข้อผิดพลาดในฟังก์ชั่นของคุณ

long round(double x) {
   assert(x >= LONG_MIN-0.5);
   assert(x <= LONG_MAX+0.5);
   if (x >= 0)
      return (long) (x+0.5);
   return (long) (x-0.5);
}

#define round(x) ((x) < LONG_MIN-0.5 || (x) > LONG_MAX+0.5 ?\
      error() : ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))

จาก: http://www.cs.tut.fi/~jkorpela/round.html


การใช้LONG_MIN-0.5และLONG_MAX+0.5 แนะนำภาวะแทรกซ้อนเนื่องจากคณิตศาสตร์อาจไม่ถูกต้อง LONG_MAXอาจเกินdoubleความแม่นยำสำหรับการแปลงที่แน่นอน มีแนวโน้มที่ต้องการเพิ่มเติมassert(x < LONG_MAX+0.5); (<vs <=) ตามที่LONG_MAX+0.5อาจจะเป็นตัวแทนและ(x)+0.5อาจมีผลลัพธ์ที่แน่นอนLONG_MAX+1ซึ่งล้มเหลวในการlongส่ง ปัญหามุมอื่น ๆ เช่นกัน
chux - Reinstate Monica

อย่าเรียกใช้ฟังก์ชันของคุณround(double)มีฟังก์ชันไลบรารีคณิตศาสตร์มาตรฐานของชื่อนั้น (ใน C ++ 11) ดังนั้นจึงทำให้เกิดความสับสน ใช้std::lrint(x)ถ้ามันใช้ได้
Peter Cordes

11

การปัดเศษบางประเภทยังถูกนำมาใช้ใน Boost:

#include <iostream>

#include <boost/numeric/conversion/converter.hpp>

template<typename T, typename S> T round2(const S& x) {
  typedef boost::numeric::conversion_traits<T, S> Traits;
  typedef boost::numeric::def_overflow_handler OverflowHandler;
  typedef boost::numeric::RoundEven<typename Traits::source_type> Rounder;
  typedef boost::numeric::converter<T, S, Traits, OverflowHandler, Rounder> Converter;
  return Converter::convert(x);
}

int main() {
  std::cout << round2<int, double>(0.1) << ' ' << round2<int, double>(-0.1) << ' ' << round2<int, double>(-0.9) << std::endl;
}

โปรดทราบว่าสิ่งนี้ใช้ได้เฉพาะในกรณีที่คุณทำการแปลงเป็นจำนวนเต็ม


2
Boost ยังเสนอชุดของฟังก์ชั่นการปัดเศษอย่างง่าย เห็นคำตอบของฉัน
Daniel Wolf

คุณสามารถใช้boost:numeric::RoundEven< double >::nearbyintโดยตรงหากคุณไม่ต้องการเป็นจำนวนเต็ม บันทึก @DanielWolf ว่าฟังก์ชั่นที่เรียบง่ายจะดำเนินการใช้ 0.5 ที่มีปัญหาเป็นวางโดย aka.nice
Stijn

6

คุณสามารถปัดเศษตัวเลขได้อย่างแม่นยำด้วย:

double round( double x )
{
const double sd = 1000; //for accuracy to 3 decimal places
return int(x*sd + (x<0? -0.5 : 0.5))/sd;
}

4
เว้นแต่ว่าคอมไพเลอร์ของคุณมีขนาดเริ่มต้นที่ 1024 บิตสิ่งนี้จะไม่ถูกต้องสำหรับคู่ขนาดใหญ่ ...
aka.nice

ฉันคิดว่าเป็นสิ่งที่ยอมรับได้เมื่อมันถูกนำมาใช้: หากมูลค่าสองเท่าของคุณคือ 1.0 e + 19 การปัดเศษเป็น 3 แห่งนั้นไม่สมเหตุสมผล
Carl

3
แน่นอน แต่คำถามสำหรับรอบสามัญและคุณไม่สามารถควบคุมวิธีการใช้งานได้ ไม่มีเหตุผลที่รอบจะล้มเหลวที่เพดานและพื้นจะไม่
aka.nice

แห่งนี้มีพฤติกรรมที่ไม่ได้กำหนดไว้สำหรับ args intนอกช่วงของ (ในทางปฏิบัติบน x86 ออกจากช่วงค่า FP จะทำให้CVTTSD2SIการผลิต0x80000000เป็นรูปแบบบิตจำนวนเต็มคือINT_MINซึ่งจากนั้นจะถูกแปลงกลับไปdouble.
ปีเตอร์ Cordes

5

วันนี้มันไม่น่าจะมีปัญหาในการใช้คอมไพเลอร์ C ++ 11 ซึ่งมีไลบรารี่คณิตศาสตร์ C99 / C ++ 11 แต่แล้วคำถามก็กลายเป็น: คุณเลือกใช้ฟังก์ชันการปัดเศษแบบไหน?

C99 / C ++ 11 มักจะไม่จริงฟังก์ชั่นการปัดเศษคุณต้องการround() มันใช้โหมดการปัดเศษแบบขี้ขลาดซึ่งปัดเศษจาก 0 เป็นตัวแบ่งไทม์ในกรณีครึ่งทาง ( +-xxx.5000) หากคุณต้องการโหมดการปัดเศษแบบเฉพาะเจาะจงหรือคุณกำหนดเป้าหมายการใช้งาน C ++ ซึ่งround()เร็วกว่าrint()นั้นให้ใช้ (หรือเลียนแบบพฤติกรรมของมันด้วยคำตอบอย่างอื่นอย่างใดอย่างหนึ่งต่อคำถามนี้ พฤติกรรมการปัดเศษ)

round()ปัดเศษ 's จะแตกต่างจาก IEEE754 เริ่มต้นรอบโหมดที่ใกล้เคียงที่สุดแม้ในขณะที่ผูกหัก ใกล้ที่สุดแม้จะหลีกเลี่ยงอคติทางสถิติในขนาดเฉลี่ยของตัวเลข แต่จะมีอคติต่อจำนวนคู่

มีสองฟังก์ชั่นการปัดเศษห้องสมุดคณิตศาสตร์ที่ใช้เริ่มต้นปัจจุบันโหมดการปัดเศษคือ: std::nearbyint()และstd::rint()ทั้งเพิ่มเข้ามาใน C99 / C ++ 11 จึงจะสามารถใช้งานได้ตลอดเวลาstd::round()คือ ข้อแตกต่างเพียงอย่างเดียวคือnearbyintไม่เพิ่ม FE_INEXACT

ต้องการrint()เหตุผลด้านประสิทธิภาพ : gcc และเสียงดังกราวทั้งสองแบบอินไลน์ได้ง่ายขึ้น แต่ gcc ไม่เคยอินไลน์nearbyint()(แม้จะมี-ffast-math)


gcc / clang สำหรับ x86-64 และ AArch64

ฉันวางฟังก์ชั่นทดสอบบางอย่างไว้ในคอมไพเลอร์ Explorer ของ Matt Godboltซึ่งคุณสามารถดูเอาท์พุท source + asm (สำหรับคอมไพเลอร์หลายตัว) สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการอ่านผลลัพธ์ของคอมไพเลอร์โปรดดูคำถาม & คำตอบนี้และการพูดคุย CppCon2017 ของ Matt: “ คอมไพเลอร์ของฉันทำอะไรให้ฉันเมื่อเร็ว ๆ นี้? ถอดสลักฝาของคอมไพเลอร์” ,

ในรหัส FP มันมักจะชนะใหญ่ในฟังก์ชั่นขนาดเล็กแบบอินไลน์ โดยเฉพาะอย่างยิ่งใน non-Windows ซึ่งการประชุมมาตรฐานนั้นไม่มีการบันทึกการโทรดังนั้นผู้รวบรวมจึงไม่สามารถเก็บค่า FP ใด ๆ ในการลงทะเบียน XMM callได้ ดังนั้นแม้ว่าคุณจะไม่รู้จัก asm จริง ๆ คุณก็ยังสามารถเห็นได้อย่างง่ายดายว่ามันเป็นเพียงแค่การโทรไปยังฟังก์ชั่นห้องสมุดหรือไม่หรือว่ามันสอดคล้องกับคำสั่งทางคณิตศาสตร์หนึ่งหรือสองคำ ทุกสิ่งที่อินไลน์ไปยังคำสั่งหนึ่งหรือสองคำแนะนำนั้นดีกว่าการเรียกใช้ฟังก์ชัน (สำหรับงานนี้โดยเฉพาะบน x86 หรือ ARM)

บน x86 สิ่งใดก็ตามที่อินไลน์ไปยัง SSE4.1 roundsdสามารถปรับเวกเตอร์อัตโนมัติด้วย SSE4.1 roundpd(หรือ AVX vroundpd) (การแปลง FP-> จำนวนเต็มนอกจากนี้ยังมีในรูปแบบ SIMD ที่บรรจุยกเว้นสำหรับ FP-> จำนวนเต็ม 64- บิตซึ่งต้องใช้ AVX512)

  • std::nearbyint():

    • x86 เสียงดังกราว: inlines ไป insn -msse4.1เดียวกับ
    • x86 GCC: inlines ไป insn เดียวเท่านั้นที่มี-msse4.1 -ffast-mathและเฉพาะใน GCC 5.4 และก่อนหน้านี้ ภายหลัง gcc ไม่เคย inlines (บางทีพวกเขาไม่ได้ตระหนักว่าหนึ่งในบิตทันทีสามารถระงับข้อยกเว้นไม่แน่นอนหรือไม่นั่นคือสิ่งที่เสียงดังกราวใช้ แต่ gcc เก่าใช้เหมือนกันทันทีrintเมื่อมัน inline มัน
    • AArch64 gcc6.3: อินไลน์ไปยัง insn เดียวโดยค่าเริ่มต้น
  • std::rint:

    • x86 เสียงดังกราว: อินไลน์ไปยังหนึ่ง insn ด้วย -msse4.1
    • x86 gcc7: inlines ไป insn -msse4.1เดียวกับ (ไม่มี SSE4.1, อินไลน์ไปยังหลายคำแนะนำ)
    • x86 gcc6.x และก่อนหน้านี้: inlines ไป insn -ffast-math -msse4.1เดียวกับ
    • AArch64 gcc: อินไลน์ไปยัง insn เดียวโดยค่าเริ่มต้น
  • std::round:

    • x86 เสียงดังกราว: ไม่อินไลน์
    • x86 gcc: อินไลน์คำสั่งหลายคำสั่งด้วย-ffast-math -msse4.1, ต้องการค่าคงที่เวกเตอร์สองค่า
    • AArch64 gcc: อินไลน์คำสั่งเดียว (HW รองรับโหมดการปัดเศษเช่นเดียวกับค่าเริ่มต้น IEEE และอื่น ๆ ส่วนใหญ่)
  • std::floor/ std::ceil/std::trunc

    • x86 เสียงดังกราว: อินไลน์ไปยังหนึ่ง insn ด้วย -msse4.1
    • x86 gcc7.x: อินไลน์ไปยัง insn เดียวด้วย -msse4.1
    • x86 gcc6.x และรุ่นก่อนหน้า: อินไลน์ไปยัง insn เดียวด้วย -ffast-math -msse4.1
    • AArch64 gcc: อินไลน์โดยค่าเริ่มต้นเป็นคำสั่งเดียว

การปัดเศษเป็นint/ long/ long long:

คุณมีสองตัวเลือกที่นี่: ใช้lrint(เหมือนrintแต่กลับlongหรือlong longเพื่อllrint) หรือใช้ฟังก์ชันการปัดเศษ FP-> FP จากนั้นแปลงเป็นประเภทจำนวนเต็มตามปกติ (ด้วยการตัดปลาย) คอมไพเลอร์บางตัวปรับให้เหมาะสมดีกว่าอีกวิธีหนึ่ง

long l = lrint(x);

int  i = (int)rint(x);

โปรดทราบว่าint i = lrint(x)แปลงfloatหรือdouble-> ก่อนแล้วจึงตัดทอนจำนวนเต็มไปlong intสิ่งนี้สร้างความแตกต่างสำหรับจำนวนเต็มนอกช่วง: พฤติกรรมที่ไม่ได้กำหนดใน C ++ แต่กำหนดไว้อย่างดีสำหรับคำสั่ง x86 FP -> int (ซึ่งคอมไพเลอร์จะส่งเสียงยกเว้นว่ามันเห็น UB ในเวลาคอมไพล์ในขณะที่ทำการเผยแพร่อย่างต่อเนื่อง ได้รับอนุญาตให้สร้างรหัสที่ผิดพลาดหากมีการดำเนินการ)

บน x86 การแปลงจำนวนเต็ม FP-> ที่ล้นจำนวนเต็มสร้างINT_MINหรือLLONG_MIN(รูปแบบบิตของ0x8000000หรือเทียบเท่า 64 บิตโดยมีเพียงชุดบิตลงชื่อ) Intel เรียกค่านี้ว่า "จำนวนเต็มไม่ จำกัด " (ดูรายการคู่มือ , การเรียนการสอน SSE2 ที่แปลง (มีการตัด) เกลาสองครั้งเพื่อลงนามจำนวนเต็ม. มันสามารถใช้ได้กับ 32 บิตหรือปลายทาง 64 บิตจำนวนเต็ม (ในโหมด 64 บิตเท่านั้น). นอกจากนี้ยังมี(แปลงที่มีการปัดเศษปัจจุบัน โหมด) ซึ่งเป็นสิ่งที่เราต้องการคอมไพเลอร์ในการปล่อย แต่โชคร้าย GCC และเสียงดังกราวจะไม่ทำอย่างนั้นได้โดยไม่ต้องcvttsd2sicvtsd2si-ffast-math

ระวังด้วยว่า FP ถึง / จากunsignedint / long นั้นมีประสิทธิภาพน้อยกว่าใน x86 (ไม่มี AVX512) การแปลงเป็น 32 บิตที่ไม่ได้ลงชื่อบนเครื่อง 64 บิตค่อนข้างถูก เพียงแปลงเป็นแบบ 64 บิตและตัดทอน แต่อย่างอื่นมันช้าลงอย่างมาก

  • เสียงดังกราว x86 มี / ไม่มี-ffast-math -msse4.1: (int/long)rintinlines ไป/roundsd cvttsd2si(พลาดการเพิ่มประสิทธิภาพให้กับcvtsd2si) lrintไม่อินไลน์เลย

  • x86 gcc6.x และรุ่นก่อนหน้าโดยไม่มี-ffast-math: ไม่มีทางอินไลน์

  • x86 gcc7 ที่ไม่มี-ffast-math: (int/long)rintปัดเศษและแปลงแยกต่างหาก (ด้วยการเปิดใช้งานคำแนะนำทั้งหมด 2 ชุดของ SSE4.1 มิฉะนั้นจะมีโค้ดที่ใส่ไว้rintโดยไม่ใส่roundsd) lrintไม่อินไลน์
  • x86 gcc ด้วย -ffast-math : ทุกวิธีแบบอินไลน์ถึงcvtsd2si(ดีที่สุด)ไม่จำเป็นต้องใช้ SSE4.1

  • AArch64 gcc6.3 โดยไม่ต้อง-ffast-math: อินไลน์(int/long)rintถึง 2 คำแนะนำ lrintไม่อินไลน์

  • gcc6.3 AArch64 กับ-ffast-math: คอมไพล์ที่จะโทรไป (int/long)rint ไม่อินไลน์ นี่อาจเป็นการเพิ่มประสิทธิภาพที่ไม่ได้รับเว้นแต่ว่าทั้งสองคำแนะนำที่เราได้รับโดยไม่ช้ามากlrintlrint-ffast-math

สิ่งที่ต้องทำ: ICC และ MSVC ยังมีอยู่ใน Godbolt แต่ฉันไม่ได้ดูผลลัพธ์ของสิ่งนี้ ยินดีต้อนรับการแก้ไข ... นอกจากนี้: จะมีประโยชน์มากกว่าไหมถ้าคอมไพเลอร์ / เวอร์ชั่นก่อนแล้วตามด้วยฟังก์ชั่นภายใน? คนส่วนใหญ่จะไม่เปลี่ยนคอมไพเลอร์ตามการรวบรวมจำนวนเต็ม FP-> FP หรือ FP->
Peter Cordes

2
+1 สำหรับการแนะนำrint()ที่เป็นตัวเลือกที่เป็นไปได้ซึ่งมักเป็นกรณี ฉันเดาว่าชื่อround()มีความหมายกับโปรแกรมเมอร์บางคนว่านี่คือสิ่งที่พวกเขาต้องการในขณะที่rint()ดูเหมือนลึกลับ โปรดทราบround()ว่าไม่ได้ใช้โหมดการปัดเศษ "ขี้ขลาด": การปัดเศษไปสัมพันธ์ใกล้ที่สุดคือโหมดการปัดเศษ IEEE-754 (2008) อย่างเป็นทางการ มันอยากรู้อยากเห็นที่nearbyint()ไม่ได้รับการ inline เนื่องจากมันส่วนใหญ่เหมือนกันrint()และควรจะเหมือนกันภายใต้-ffast-mathเงื่อนไข นั่นคือข้อผิดพลาดสำหรับฉัน
njuffa

4

floor(x+0.5)จงระวัง นี่คือสิ่งที่สามารถเกิดขึ้นได้กับตัวเลขคี่ในช่วง [2 ^ 52,2 ^ 53]:

-bash-3.2$ cat >test-round.c <<END

#include <math.h>
#include <stdio.h>

int main() {
    double x=5000000000000001.0;
    double y=round(x);
    double z=floor(x+0.5);
    printf("      x     =%f\n",x);
    printf("round(x)    =%f\n",y);
    printf("floor(x+0.5)=%f\n",z);
    return 0;
}
END

-bash-3.2$ gcc test-round.c
-bash-3.2$ ./a.out
      x     =5000000000000001.000000
round(x)    =5000000000000001.000000
floor(x+0.5)=5000000000000002.000000

นี่คือhttp://bugs.squeak.org/view.php?id=7134 ใช้วิธีแก้ปัญหาเช่นหนึ่งใน @konik

เวอร์ชั่นที่แข็งแกร่งของฉันจะเป็นอย่างไร:

double round(double x)
{
    double truncated,roundedFraction;
    double fraction = modf(x, &truncated);
    modf(2.0*fraction, &roundedFraction);
    return truncated + roundedFraction;
}

เหตุผลที่จะหลีกเลี่ยงพื้น (x + 0.5) อีกจะได้รับที่นี่


2
ฉันสนใจที่จะรู้เกี่ยวกับ downvotes มันเป็นเพราะความเสมอกันนั้นได้รับการแก้ไขให้ห่างจากศูนย์มากกว่าที่จะใกล้เคียงที่สุดหรือเปล่า?
aka.nice

1
หมายเหตุ: ข้อมูลจำเพาะ C บอกว่า "การปัดเศษกรณีครึ่งทางห่างจากศูนย์โดยไม่คำนึงถึงทิศทางการปัดเศษในปัจจุบัน" ดังนั้นการปัดเศษโดยไม่คำนึงถึงคี่ / คู่นั้นเป็นไปตามมาตรฐาน
chux - Reinstate Monica

4

หากคุณต้องการแปลงdoubleผลลัพธ์ของround()ฟังก์ชั่นของคุณเป็นintที่สุดแล้วคำตอบที่ได้รับการยอมรับของคำถามนี้จะมีลักษณะดังนี้:

int roundint(double r) {
  return (int)((r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5));
}

นาฬิกานี้อยู่ที่ประมาณ8.88 nsบนเครื่องของฉันเมื่อส่งผ่านค่าสุ่มอย่างสม่ำเสมอ

ด้านล่างนี้เทียบเท่ากับการใช้งานเท่าที่ฉันสามารถบอกได้ แต่นาฬิกาที่อยู่ที่2.48 nsบนเครื่องของฉันเพื่อความได้เปรียบด้านประสิทธิภาพที่สำคัญ:

int roundint (double r) {
  int tmp = static_cast<int> (r);
  tmp += (r-tmp>=.5) - (r-tmp<=-.5);
  return tmp;
}

สาเหตุที่ทำให้ประสิทธิภาพดีขึ้นคือการข้ามการแยกทาง


แห่งนี้มีพฤติกรรมที่ไม่ได้กำหนดไว้สำหรับ args intนอกช่วงของ (ในทางปฏิบัติบน x86 ออกจากช่วงค่า FP จะทำให้CVTTSD2SIการผลิต0x80000000เป็นรูปแบบบิตจำนวนเต็มคือINT_MINซึ่งจากนั้นจะถูกแปลงกลับไปdouble.
ปีเตอร์ Cordes

2

ไม่จำเป็นต้องใช้อะไรดังนั้นฉันไม่แน่ใจว่าทำไมคำตอบมากมายเกี่ยวข้องกับการกำหนดหน้าที่หรือวิธีการ

ใน C99

เรามีสิ่งต่อไปนี้และและส่วนหัว <tgmath.h> สำหรับมาโครประเภททั่วไป

#include <math.h>
double round (double x);
float roundf (float x);
long double roundl (long double x);

หากคุณไม่สามารถรวบรวมสิ่งนี้ได้คุณอาจเหลือห้องสมุดคณิตศาสตร์ คำสั่งที่คล้ายกับสิ่งนี้ใช้ได้กับคอมไพเลอร์ C ทุกตัวที่ฉันมี (หลายตัว)

gcc -lm -std=c99 ...

ใน C ++ 11

เรามีสิ่งต่อไปนี้และเกินพิกัดเพิ่มเติมใน #include <cmath> ที่ขึ้นอยู่กับจุดลอยตัวความแม่นยำสองเท่าของ IEEE

#include <math.h>
double round (double x);
float round (float x);
long double round (long double x);
double round (T x);

มีสิ่งที่เทียบเท่าในเนมสเปซ stdด้วย

หากคุณไม่สามารถรวบรวมสิ่งนี้ได้คุณอาจใช้การรวบรวม C แทน C ++ คำสั่งพื้นฐานต่อไปนี้สร้างข้อผิดพลาดหรือคำเตือนด้วย g ++ 6.3.1, x86_64-w64-mingw32-g ++ 6.3.0, clang-x86_64 ++ 3.8.0 และชุมชน C ++ 2015 เสมือน

g++ -std=c++11 -Wall

กับกองสามัญ

เมื่อทำการหารเลขลำดับสองตัวโดยที่ T สั้น, int, long หรือเลขลำดับอื่นนิพจน์การปัดเศษจะเป็นเช่นนี้

T roundedQuotient = (2 * integerNumerator + 1)
    / (2 * integerDenominator);

ความถูกต้อง

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

แหล่งที่มาไม่ได้เป็นเพียงตัวเลขจำนวนนัยสำคัญในแมนทิสซาของการเป็นตัวแทน IEEE ของจำนวนจุดลอยตัว แต่ก็มีความเกี่ยวข้องกับการคิดทศนิยมของเราในฐานะมนุษย์

สิบเป็นผลิตภัณฑ์ห้าและสองและ 5 และ 2 ค่อนข้างสำคัญ ดังนั้นมาตรฐานจุดลอยตัวของ IEEE จึงไม่สามารถแสดงได้อย่างสมบูรณ์เป็นตัวเลขทศนิยมสำหรับการเป็นตัวแทนไบนารีดิจิตอลทั้งหมด

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


1
"ฉันไม่แน่ใจว่าทำไมคำตอบมากมายเกี่ยวข้องกับการกำหนดฟังก์ชันหรือวิธีการ" ดูเมื่อถูกถาม - C ++ 11 ยังไม่ออก ;)
jaggedSpire

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

2

ฟังก์ชั่นdouble round(double)การใช้งานของmodfฟังก์ชั่น:

double round(double x)
{
    using namespace std;

    if ((numeric_limits<double>::max() - 0.5) <= x)
        return numeric_limits<double>::max();

    if ((-1*std::numeric_limits<double>::max() + 0.5) > x)
        return (-1*std::numeric_limits<double>::max());

    double intpart;
    double fractpart = modf(x, &intpart);

    if (fractpart >= 0.5)
        return (intpart + 1);
    else if (fractpart >= -0.5)
        return intpart;
    else
        return (intpart - 1);
    }

หากต้องการคอมไพล์สะอาดต้องรวม "math.h" และ "จำกัด " เป็นสิ่งจำเป็น ฟังก์ชั่นการทำงานเป็นไปตามสคีการปัดเศษต่อไปนี้:

  • รอบ 5.0 เป็น 5.0
  • รอบ 3.8 คือ 4.0
  • รอบ 2.3 คือ 2.0
  • รอบ 1.5 คือ 2.0
  • รอบ 0.501 เท่ากับ 1.0
  • รอบ 0.5 คือ 1.0
  • รอบ 0.499 คือ 0.0
  • รอบ 0.01 คือ 0.0
  • รอบ 0.0 เป็น 0.0
  • รอบ -0.01 คือ -0.0
  • รอบ -0.499 คือ -0.0
  • รอบ -0.5 คือ -0.0
  • รอบ -0.501 คือ -1.0
  • รอบ -1.5 คือ -1.0
  • รอบ -2.3 คือ -2.0
  • รอบ -3.8 คือ -4.0
  • รอบของ -5.0 คือ -5.0

2
นี่เป็นทางออกที่ดี ฉันไม่แน่ใจว่าการปัดเศษ -1.5 ถึง -1.0 เป็นมาตรฐาน แต่ฉันคาดว่า -2.0 ด้วยการแสดงความเสียใจ นอกจากนี้ฉันไม่เห็นจุดของผู้พิทักษ์ชั้นนำสองคนแรกหากสามารถลบออกได้
aka.nice

2
ฉันตรวจสอบในมาตรฐาน ISO / IEC 10967-2, open-std.org/jtc1/sc22/wg11/docs/n462.pdfและจากภาคผนวก B.5.2.4 ฟังก์ชันการปัดเศษต้องสมมาตร, rounding_F (x) = neg_F (rounding_F (neg_F (x)))
aka.nice

สิ่งนี้จะช้าลงเมื่อเทียบกับ C ++ 11 rint()หรือnearbyint()แต่ถ้าคุณไม่สามารถใช้คอมไพเลอร์ที่ให้ฟังก์ชันการปัดเศษที่เหมาะสมและคุณต้องการความแม่นยำมากกว่าประสิทธิภาพ ...
Peter Cordes

1

หากคุณต้องการคอมไพล์โค้ดในสภาพแวดล้อมที่สนับสนุนมาตรฐาน C ++ 11 แต่ยังต้องสามารถคอมไพล์โค้ดเดียวกันนั้นในสภาพแวดล้อมที่ไม่รองรับคุณสามารถใช้แมโครฟังก์ชันเพื่อเลือกระหว่าง std :: round () และฟังก์ชั่นที่กำหนดเองสำหรับแต่ละระบบ เพียงแค่ผ่าน-DCPP11หรือ/DCPP11ไปที่ C ++ คอมไพเลอร์ 11 ที่สอดคล้องกับ (หรือใช้ตัวในแมโครรุ่น) และทำให้ส่วนหัวเช่นนี้:

// File: rounding.h
#include <cmath>

#ifdef CPP11
    #define ROUND(x) std::round(x)
#else    /* CPP11 */
    inline double myRound(double x) {
        return (x >= 0.0 ? std::floor(x + 0.5) : std::ceil(x - 0.5));
    }

    #define ROUND(x) myRound(x)
#endif   /* CPP11 */

สำหรับตัวอย่างรวดเร็วดูhttp://ideone.com/zal709

สิ่งนี้จะประมาณค่า std :: round () ในสภาพแวดล้อมที่ไม่เข้ากันได้กับ C ++ 11 รวมถึงการเก็บรักษาสัญลักษณ์บิตสำหรับ -0.0 อย่างไรก็ตามอาจทำให้เกิดประสิทธิภาพการทำงานเล็กน้อยและอาจมีปัญหาเกี่ยวกับการปัดเศษบางค่าที่รู้จักกัน "ปัญหา" ค่าจุดลอยตัวเช่น 0.49999999999999994 หรือค่าที่คล้ายกัน

อีกทางเลือกหนึ่งถ้าคุณเข้าถึงคอมไพเลอร์ที่เข้ากันได้กับ C ++ 11 คุณสามารถคว้า std :: round () จาก<cmath>ส่วนหัวของมันและใช้เพื่อทำให้ส่วนหัวของคุณเองที่กำหนดฟังก์ชั่นหากยังไม่ได้กำหนด โปรดทราบว่านี่อาจไม่ใช่โซลูชันที่ดีที่สุดโดยเฉพาะอย่างยิ่งหากคุณต้องการคอมไพล์สำหรับหลาย ๆ แพลตฟอร์ม


1

ตามการตอบสนองของ Kalaxy ต่อไปนี้เป็นวิธีแก้ปัญหา templated ที่ปัดเลขทศนิยมใด ๆ ให้เป็นจำนวนเต็มที่ใกล้ที่สุดโดยยึดตามการปัดเศษแบบธรรมชาติ นอกจากนี้ยังส่งข้อผิดพลาดในโหมดดีบักหากค่าอยู่นอกช่วงของประเภทจำนวนเต็มจึงทำหน้าที่เป็นฟังก์ชันไลบรารีที่ทำงานได้โดยประมาณ

    // round a floating point number to the nearest integer
    template <typename Arg>
    int Round(Arg arg)
    {
#ifndef NDEBUG
        // check that the argument can be rounded given the return type:
        if (
            (Arg)std::numeric_limits<int>::max() < arg + (Arg) 0.5) ||
            (Arg)std::numeric_limits<int>::lowest() > arg - (Arg) 0.5)
            )
        {
            throw std::overflow_error("out of bounds");
        }
#endif

        return (arg > (Arg) 0.0) ? (int)(r + (Arg) 0.5) : (int)(r - (Arg) 0.5);
    }

1
ในขณะที่ฉันชี้ให้เห็นในการเพิ่มคำตอบของฉัน0.5ไม่ทำงานในทุกกรณี แม้ว่าอย่างน้อยคุณจะจัดการกับปัญหาที่มากเกินไปดังนั้นคุณจึงหลีกเลี่ยงพฤติกรรมที่ไม่ได้กำหนด
Shafik Yaghmour

1

ตามที่ระบุไว้ในความคิดเห็นและคำตอบอื่น ๆ ห้องสมุดมาตรฐาน ISO C ++ ไม่ได้เพิ่มround()จนกว่า ISO C ++ 11 เมื่อฟังก์ชั่นนี้ถูกดึงเข้ามาโดยอ้างอิงกับห้องสมุดคณิตศาสตร์มาตรฐาน ISO C99

สำหรับตัวถูกดำเนินการในเชิงบวกใน [½, UB ] round(x) == floor (x + 0.5)ที่UBคือ 2 23สำหรับfloatเมื่อแมปไป IEEE-754 (2008) binary32และ 2 52สำหรับdoubleเมื่อมันถูกแมปไป IEEE-754 binary64(2008) ตัวเลข 23 และ 52 สอดคล้องกับจำนวนบิต mantissa ที่จัดเก็บในรูปแบบจุดลอยตัวทั้งสองนี้ สำหรับตัวถูกดำเนินการในเชิงบวกใน [0, ½) round(x) == 0และสำหรับตัวถูกดำเนินการในเชิงบวกใน ( UB + ∞] round(x) == x. ในฐานะที่เป็นฟังก์ชั่นที่มีความสมมาตรเกี่ยวกับแกน x ขัดแย้งเชิงลบสามารถจัดการตามxround(-x) == -round(x)

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

ผมทดสอบmy_roundf()อย่างละเอียดถี่ถ้วนกับ newlib roundf()การดำเนินงานโดยใช้คอมไพเลอร์ของอินเทลรุ่น 13 ทั้งสองและ/fp:strict /fp:fastฉันยังตรวจสอบด้วยว่าเวอร์ชัน newlib ตรงกับroundf()ในmathimfไลบรารีของคอมไพเลอร์ Intel การทดสอบแบบละเอียดนั้นเป็นไปไม่ได้สำหรับความแม่นยำสองเท่าround()อย่างไรก็ตามรหัสนั้นมีโครงสร้างเหมือนกับการใช้งานที่มีความแม่นยำเดียว

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>

float my_roundf (float x)
{
    const float half = 0.5f;
    const float one = 2 * half;
    const float lbound = half;
    const float ubound = 1L << 23;
    float a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floorf (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

double my_round (double x)
{
    const double half = 0.5;
    const double one = 2 * half;
    const double lbound = half;
    const double ubound = 1ULL << 52;
    double a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floor (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

uint32_t float_as_uint (float a)
{
    uint32_t r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float uint_as_float (uint32_t a)
{
    float r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float newlib_roundf (float x)
{
    uint32_t w;
    int exponent_less_127;

    w = float_as_uint(x);
    /* Extract exponent field. */
    exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;
    if (exponent_less_127 < 23) {
        if (exponent_less_127 < 0) {
            /* Extract sign bit. */
            w &= 0x80000000;
            if (exponent_less_127 == -1) {
                /* Result is +1.0 or -1.0. */
                w |= ((uint32_t)127 << 23);
            }
        } else {
            uint32_t exponent_mask = 0x007fffff >> exponent_less_127;
            if ((w & exponent_mask) == 0) {
                /* x has an integral value. */
                return x;
            }
            w += 0x00400000 >> exponent_less_127;
            w &= ~exponent_mask;
        }
    } else {
        if (exponent_less_127 == 128) {
            /* x is NaN or infinite so raise FE_INVALID by adding */
            return x + x;
        } else {
            return x;
        }
    }
    x = uint_as_float (w);
    return x;
}

int main (void)
{
    uint32_t argi, resi, refi;
    float arg, res, ref;

    argi = 0;
    do {
        arg = uint_as_float (argi);
        ref = newlib_roundf (arg);
        res = my_roundf (arg);
        resi = float_as_uint (res);
        refi = float_as_uint (ref);
        if (resi != refi) { // check for identical bit pattern
            printf ("!!!! arg=%08x  res=%08x  ref=%08x\n", argi, resi, refi);
            return EXIT_FAILURE;
        }
        argi++;
    } while (argi);
    return EXIT_SUCCESS;
}

ฉันแก้ไขเพื่อหลีกเลี่ยงการสมมติว่าintมีความกว้างมากกว่า 16 บิต แน่นอนว่ามันยังถือว่าfloatเป็น 4 ไบต์ของ IEEE754 binary32 C ++ 11 static_assertหรืออาจเป็นมาโคร#ifdef/ #errorสามารถตรวจสอบได้ (แต่แน่นอนว่าถ้ามี C ++ 11 ให้ใช้คุณควรใช้std::roundหรือสำหรับโหมดการปัดเศษในปัจจุบันใช้std::rintซึ่งสอดคล้องกับ gcc และ clang)
Peter Cordes

BTW, gcc -ffast-math -msse4.1inlines std::round()ไปยังและจากนั้นadd( AND(x, L1), OR(x,L2) roundsdคือมันเป็นธรรมได้อย่างมีประสิทธิภาพการดำเนินการในแง่ของround rintแต่มีเหตุผลที่จะทำด้วยตัวเองใน C ++ ไม่มีแหล่งที่มาเพราะถ้าคุณมีstd::rint()หรือคุณยังมีstd::nearbyint() std::round()ดูคำตอบของฉันสำหรับลิงค์ godbolt และบทสรุปของสิ่งที่อินไลน์หรือไม่ที่มีรุ่น gcc / clang ที่แตกต่างกัน
Peter Cordes

@PeterCordes ฉันตระหนักดีถึงวิธีการใช้round()อย่างมีประสิทธิภาพในแง่ของrint()(เมื่อหลังทำงานในโหมดการปัดเศษที่ใกล้ที่สุดหรือใกล้เคียง): ฉันใช้งานสำหรับไลบรารีคณิตศาสตร์ CUDA มาตรฐาน แต่คำถามนี้ดูเหมือนจะถามว่าจะดำเนินการround()กับ C ++ ก่อนที่จะ C ++ 11 ดังนั้นrint()จะไม่สามารถใช้งานได้อย่างใดอย่างหนึ่งเท่านั้นและfloor() ceil()
njuffa

@PeterCordes ขออภัยฉัน misspoke round()ถูกสังเคราะห์ได้อย่างง่ายดายจากrint()ในการเป็นศูนย์รอบtrunc()โหมดอาคา ไม่ควรตอบก่อนกาแฟครั้งแรก
njuffa

1
@PeterCordes ฉันยอมรับว่ามันเป็นไปได้ว่า OP ไม่จำเป็นต้องมีพฤติกรรมการปัดเศษเฉพาะของround(); โปรแกรมเมอร์ส่วนใหญ่ไม่ได้ตระหนักถึงความแตกต่างระหว่างround()vs rint()กับรอบที่ใกล้ที่สุด - แม้ที่หลังมักจะให้ฮาร์ดแวร์โดยตรงและมีประสิทธิภาพมากขึ้น ฉันสะกดออกมาว่าในการเขียนโปรแกรม CUDA คู่มือเพื่อให้โปรแกรมเมอร์ตระหนักถึง "วิธีที่แนะนำไปรอบเดียวแม่นยำจุดลอยตัวถูกดำเนินการจำนวนเต็มมีผลเป็นความแม่นยำเดียวจำนวนจุดลอยตัวคือrintf()ไม่roundf()"
njuffa

0

ฉันใช้การดำเนินต่อไปนี้ของรอบใน asm สำหรับสถาปัตยกรรม x86 และ MS VS เฉพาะ C ++:

__forceinline int Round(const double v)
{
    int r;
    __asm
    {
        FLD     v
        FISTP   r
        FWAIT
    };
    return r;
}

UPD: เพื่อคืนค่าสองเท่า

__forceinline double dround(const double v)
{
    double r;
    __asm
    {
        FLD     v
        FRNDINT
        FSTP    r
        FWAIT
    };
    return r;
}

เอาท์พุท:

dround(0.1): 0.000000000000000
dround(-0.1): -0.000000000000000
dround(0.9): 1.000000000000000
dround(-0.9): -1.000000000000000
dround(1.1): 1.000000000000000
dround(-1.1): -1.000000000000000
dround(0.49999999999999994): 0.000000000000000
dround(-0.49999999999999994): -0.000000000000000
dround(0.5): 0.000000000000000
dround(-0.5): -0.000000000000000

ค่าผลลัพธ์ควรเป็นค่าทศนิยมที่มีความแม่นยำสองเท่า
facteeker

@ facteeker: ใช่ฉันต้องเห็นค่าตอบแทนประเภทที่ต้องการ ตกลงดู "UPD"
Aleksey F.

คอมไพเลอร์หวังว่าจะเป็นแบบอินไลน์rint()หรือnearbyint()กับroundsdคำสั่งSSE4.1 หรือคำสั่ง x87 frndintซึ่งจะเร็วกว่าการเดินทางไปรอบ ๆ ทั้งร้านค้า / บรรจุซ้ำที่จำเป็นต้องใช้อินไลน์ asm นี้กับข้อมูลในการลงทะเบียน asm MSVC แบบอินไลน์ค่อนข้างมากสำหรับการห่อคำแนะนำเดียวเช่นfrndintเนื่องจากไม่มีวิธีรับอินพุตในรีจิสเตอร์ ใช้ที่ส่วนท้ายของฟังก์ชั่นที่มีผลในst(0)อาจมีความน่าเชื่อถือเป็นวิธีการส่งออกกลับ; เห็นได้ชัดว่ามันปลอดภัยสำหรับeaxจำนวนเต็มแม้เมื่ออยู่ในบรรทัดฟังก์ชันที่มี asm
Peter Cordes

@PeterCordes การเพิ่มประสิทธิภาพที่ทันสมัยยินดีต้อนรับ อย่างไรก็ตามฉันไม่สามารถใช้ SSE4.1 เนื่องจากไม่มีอยู่ในขณะนั้น จุดประสงค์ของฉันคือให้ใช้งานรอบที่น้อยที่สุดซึ่งสามารถทำงานได้แม้ในตระกูล Intel P3 หรือ P4 เก่าตั้งแต่ปี 2000
Aleksey F.

P3 ไม่ได้มี SSE2 ดังนั้นคอมไพเลอร์ที่มีอยู่แล้วจะใช้สำหรับ x87 doubleและดังนั้นจึงควรจะสามารถที่จะปล่อยออกมาเองfrndint rint()หากคอมไพเลอร์ของคุณกำลังใช้ SSE2 การตีกลับ a doubleจาก XMM register เป็น x87 และ back อาจไม่คุ้มค่า
Peter Cordes

0

วิธีที่ดีที่สุดในการปัดเศษค่าทศนิยมด้วยทศนิยม "n" มีดังต่อไปนี้ด้วยในเวลา O (1): -

เราต้องปัดเศษค่าโดย 3 แห่งคือ n = 3. ดังนั้น

float a=47.8732355;
printf("%.3f",a);

-4
// Convert the float to a string
// We might use stringstream, but it looks like it truncates the float to only
//5 decimal points (maybe that's what you want anyway =P)

float MyFloat = 5.11133333311111333;
float NewConvertedFloat = 0.0;
string FirstString = " ";
string SecondString = " ";
stringstream ss (stringstream::in | stringstream::out);
ss << MyFloat;
FirstString = ss.str();

// Take out how ever many decimal places you want
// (this is a string it includes the point)
SecondString = FirstString.substr(0,5);
//whatever precision decimal place you want

// Convert it back to a float
stringstream(SecondString) >> NewConvertedFloat;
cout << NewConvertedFloat;
system("pause");

มันอาจจะเป็นวิธีที่ไม่มีประสิทธิภาพของการแปลงสกปรก แต่ heck มันทำงาน lol และมันก็ดีเพราะมันใช้กับการลอยจริง ไม่เพียงส่งผลกระทบต่อผลลัพธ์ทางสายตา


นี่คือไม่มีประสิทธิภาพเฮฮาและมันตัดทอน (โดยทิ้งเลขท้ายเสมอ) แทนที่จะปัดเศษเป็นใกล้ที่สุด
Peter Cordes

-6

ฉันทำอย่างนี้:

#include <cmath.h>

using namespace std;

double roundh(double number, int place){

    /* place = decimal point. Putting in 0 will make it round to whole
                              number. putting in 1 will round to the
                              tenths digit.
    */

    number *= 10^place;
    int istack = (int)floor(number);
    int out = number-istack;
    if (out < 0.5){
        floor(number);
        number /= 10^place;
        return number;
    }
    if (out > 0.4) {
        ceil(number);
        number /= 10^place;
        return number;
    }
}

3
คุณไม่ได้หมายถึง pow (10 สถานที่) มากกว่าตัวดำเนินการไบนารี ^ ใน 10 ^ สถานที่ใช่หรือไม่ 10 ^ 2 บนเครื่องของฉันให้ฉัน 8 !! อย่างไรก็ตามใน Mac 10.7.4 และ gcc ของฉันรหัสไม่ทำงานและคืนค่าดั้งเดิม
Pete855217
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.