سؤال

أنا بحاجة بسيطة النقطة العائمة التقريب وظيفة ، وبالتالي:

double round(double);

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

لا يمكن أن تجد ceil() و floor() في الرياضيات.ح - ولكن ليس round().

هل هو موجود في المعيار C++ مكتبة تحت اسم آخر ، أو هو في عداد المفقودين ؟ ؟

هل كانت مفيدة؟

المحلول

وليس هناك جولة () في المكتبة القياسية C ++ 98. يمكنك كتابة واحدة على الرغم من نفسك. وفيما يلي هو تنفيذ على مدار نصف يصل :

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

والسبب المحتمل هو عدم وجود وظيفة جولة في المكتبة القياسية C ++ 98 هو أنه لا يمكن في الواقع أن تنفذ بطرق مختلفة. ما سبق هو طريقة واحدة مشتركة ولكن هناك آخرون مثل جولة إلى حتى ، التي أقل منحازة وعموما أفضل إذا كنت تنوي القيام به الكثير من التقريب. انها قليلا أكثر تعقيدا لتنفيذ بالرغم من ذلك.

نصائح أخرى

دفعة يقدم مجموعة بسيطة من التقريب وظائف.

#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::lround, ، std::llround.

C++03 القياسية تعتمد على C90 معيار ما القياسية يدعو المكتبة القياسية C والتي تتناول في مشروع C++03 القياسية (الأقرب للجمهور مشروع معيار C++03 N1804) القسم 1.2 المراجع المعيارية:

المكتبة الموضحة في المادة 7 من ISO/IEC 9899:1990 ، الفقرة 7 من ISO/IEC 9899/Amd.1:1995 المشار إليه فيما يلي باسم القياسية ج المكتبة.1)

إذا ذهبنا إلى ج وثائق الجولة ، lround, llround على cppreference يمكننا أن نرى أن جولة والمهام ذات الصلة هي جزء من C99 وبالتالي لن تكون متوفرة في C++03 أو قبل.

في C++11 هذه التغييرات منذ C++11 يعتمد على C99 مشروع معيار ج المكتبة القياسية وبالتالي يوفر الأمراض المنقولة جنسيا::جولة العودة لا يتجزأ أنواع الأمراض المنقولة جنسيا::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 سيكون الأمراض المنقولة جنسيا::trunc وهو:

يحسب أقرب عدد صحيح لا زيادة في حجم من الأرجنتين.

#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 من النقطة العائمة نوع يمكن تحويلها إلى prvalue من نوع العدد الصحيح.تحويل باقتطاع;هذا هو جزء كسري هو التخلص منها. سلوك غير معرف إذا كان اقتطاع قيمة لا تكون ممثلة في نوع الوجهة.[...]

على سبيل المثال:

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

نظرا std::numeric_limits<unsigned int>::max() هو 4294967295 ثم الدعوة التالية:

myround( 4294967296.5f ) 

سوف يسبب تجاوز ، (تراه يعيش).

يمكننا أن نرى مدى صعوبة هذا حقا هو من خلال النظر في هذا الجواب موجزة طريقة لتنفيذ الجولة() في ج ؟ التي الرجوع 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 يمكن أن يكون خيارا لأنه هو اختبار جيد التنفيذ.

قد يكون من المفيد الإشارة إلى أن إذا أردت نتيجة عدد صحيح من التقريب لا تحتاج لتمرير ذلك إما من خلال سقف أو أرضية. أي بمعنى.

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

وأنها متاحة منذ 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

وانها تنفذ عادة على أنها floor(value + 0.5).

وتحرير: وانها ربما لا يسمى الجولة لأن هناك على الأقل ثلاثة خوارزميات التقريب أعرف: جولة إلى الصفر، جولة إلى أقرب عدد صحيح، والتقريب الخاصة banker. تسألون لجولة إلى أقرب عدد صحيح.

هناك 2 مشاكل نحن نبحث في:

  1. التقريب التحويلات
  2. نوع التحويل.

التقريب التحويلات يعني التقريب ± تطفو/مزدوجة إلى أقرب الكلمة/سقف تطفو/مزدوجة.قد تكون مشكلتك تنتهي هنا.ولكن إذا كنت من المتوقع أن يعود الباحث/طويلة تحتاج إلى إجراء نوع التحويل ، وبالتالي "تجاوز" المشكلة قد ضرب الحل الخاص بك.لذا لا تحقق الخطأ في وظيفة الخاص بك

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

ويتم تنفيذ نوع معين من التقريب أيضا في دفعة:

#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;
}

لاحظ أن هذا يعمل فقط إذا كنت تفعل تحويل إلى عدد صحيح.

هل يمكن تقريبه إلى الأرقام ن الدقة:

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;
}

إذا كنت تريد في نهاية المطاف إلى تحويل الانتاج double وظيفة round() الخاص بك إلى int، ثم الحلول المقبولة لهذا السؤال سيبدو شيئا مثل:

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

وهذه الساعات في حوالي <قوية> 8.88 م على الجهاز الخاص بي عندما مرت في القيم العشوائية بشكل موحد.

وأدناه ما يعادل وظيفيا، بقدر ما استطيع ان اقول، ولكن في الساعات في <قوية> 2.48 م على الجهاز الخاص بي، لميزة كبيرة في الأداء:

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

وكان من بين الأسباب التي أدت إلى تحسين الأداء هو تخطي المتفرعة.

في هذه الأيام لا ينبغي أن يكون مشكلة استخدام 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() لأسباب تتعلق بالأداء:دول مجلس التعاون الخليجي و رنة كل مضمنة بشكل أكثر سهولة ، ولكن دول مجلس التعاون الخليجي لم inlines nearbyint() (حتى مع -ffast-math)


دول مجلس التعاون الخليجي/رنة ل x86-64 و AArch64

أضع بعض اختبار وظائف مات Godbolt هو مترجم Explorer, حيث يمكنك أن ترى مصدر + asm الإخراج (متعددة المجمعين).لمزيد من المعلومات حول القراءة مترجم الإخراج ، انظر هذا Q&A, و مات CppCon2017 الحديث: "ما لي المترجم به بالنسبة لي في الآونة الأخيرة ؟ Unbolting مترجم غطاء",

في FP رمز, انها عادة ما تكون فوز كبير على مضمنة وظائف صغيرة.وخاصة على غير ويندوز ، حيث القياسية الدعوة الاتفاقية لا يوجد لديه الاتصال الحفاظ على السجلات ، حتى المترجم لا يمكن أن تبقي أي FP القيم في XMM يسجل عبر call.لذلك حتى لو كنت لا أعرف حقا asm, لا يزال يمكنك بسهولة معرفة ما إذا كان مجرد ذيل-الدعوة إلى المكتبة وظيفة أو ما إذا كان المضمنة إلى واحد أو اثنين من الرياضيات التعليمات.أي شيء inlines إلى واحد أو اثنين من التعليمات أفضل من استدعاء دالة (على هذه المهمة خاصة على x86 أو الذراع).

على x86 أي شيء inlines إلى SSE4.1 roundsd يمكن لصناعة السيارات vectorize مع SSE4.1 roundpd (أو AVX vroundpd).(FP->صحيح التحويلات متوفرة أيضا في وجبات SIMD شكل ، باستثناء FP->64-بت عدد صحيح الذي يتطلب AVX512.)

  • std::nearbyint():

    • x86 رنة:inlines واحد insn مع -msse4.1.
    • x86 دول مجلس التعاون الخليجي:inlines واحد insn فقط مع -msse4.1 -ffast-math, فقط على دول مجلس التعاون الخليجي 5-4 و في وقت سابق.في وقت لاحق من دول مجلس التعاون الخليجي لم inlines ذلك (ربما لم أدرك أن أحد فورية بت يمكن قمع غير دقيق استثناء ؟ هذا ما رنة تستخدم ، ولكن كبار السن دول مجلس التعاون الخليجي يستخدم نفس فورية أما بالنسبة rint عندما لا مضمنة ذلك)
    • AArch64 gcc6.3:inlines واحد insn بشكل افتراضي.
  • std::rint:

    • x86 رنة:inlines واحد insn مع -msse4.1
    • x86 gcc7:inlines واحد insn مع -msse4.1.(دون SSE4.1, inlines إلى عدة تعليمات)
    • x86 gcc6.x في وقت سابق:inlines واحد insn مع -ffast-math -msse4.1.
    • AArch64 دول مجلس التعاون الخليجي:inlines واحد insn بشكل افتراضي
  • std::round:

    • x86 رنة:لا مضمنة
    • x86 دول مجلس التعاون الخليجي:inlines إلى تعليمات متعددة مع -ffast-math -msse4.1, تتطلب اثنين من ناقلات الثوابت.
    • AArch64 دول مجلس التعاون الخليجي:inlines إلى تعليمة واحدة (الأب دعم هذا التقريب وضع فضلا عن IEEE الافتراضي و معظم الآخرين.)
  • std::floor / std::ceil / std::trunc

    • x86 رنة:inlines واحد insn مع -msse4.1
    • x86 gcc7.x:inlines واحد insn مع -msse4.1
    • x86 gcc6.x في وقت سابق:inlines واحد insn مع -ffast-math -msse4.1
    • AArch64 دول مجلس التعاون الخليجي:inlines افتراضيا إلى تعليمة واحدة

التقريب إلى 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 بت تعادل مع علامة بت تعيين).إنتل يدعو هذا "عدد صحيح إلى أجل غير مسمى" قيمة.(انظر على cvttsd2si الإدخال اليدوي, ، SSE2 تعليمات بتحويل (مع الاقتطاع) العددية مزدوجة إلى توقيع عدد صحيح.إنه المتاحة مع 32-بت أو 64-بت عدد صحيح المقصد (في وضع 64 بت فقط).هناك أيضا cvtsd2si (تحويل الحالية التقريب الوضعية) ، وهو ما نود مترجم تنبعث منها ، ولكن للأسف دول مجلس التعاون الخليجي و رنة لن تفعل ذلك دون -ffast-math.

حذار أيضا أن FP من unsigned الباحث / طويلة هو أقل كفاءة على x86 (دون AVX512).التحويل إلى 32-بت غير الموقعة على 64 بت الجهاز جميلة رخيصة ؛ مجرد تحويل إلى 64-بت و اقتطاع.ولكن على خلاف انها أبطأ بشكل ملحوظ.

  • x86 رنة مع/بدون -ffast-math -msse4.1: (int/long)rint inlines إلى roundsd / cvttsd2si.(غاب الأمثل إلى cvtsd2si). lrint لا مضمنة في كل شيء.

  • x86 gcc6.x في وقت سابق دون -ffast-math:ولا طريقة inlines

  • x86 gcc7 دون -ffast-math: (int/long)rint جولات يحول بشكل منفصل (مع 2 المجموع تعليمات SSE4.1 تمكين خلاف ذلك مع مجموعة من التعليمات البرمجية المضمنة على rint دون roundsd). lrint لا مضمنة.
  • x86 دول مجلس التعاون الخليجي مع -ffast-math: كل الطرق مضمنة إلى cvtsd2si (الأمثل), ، لا حاجة SSE4.1.

  • AArch64 gcc6.3 دون -ffast-math: (int/long)rint inlines 2 التعليمات. lrint لا مضمنة

  • AArch64 gcc6.3 مع -ffast-math: (int/long)rint برمجيا إلى استدعاء lrint. lrint لا مضمنة.هذا قد يكون غاب الأمثل إلا اثنين تعليمات نصل دون -ffast-math هي بطيئة جدا.

حذار من 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.استخدام محلول مثل واحد من @كونيك.

بلدي نسخة قوية سيكون شيئا مثل:

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

سبب آخر لتجنب الكلمة(x+0.5) يعطى هنا.

ليست هناك حاجة لتنفيذ أي شيء ، لذلك أنا لست متأكدا من السبب في الكثير من الإجابات إشراك يحدد الوظائف أو الطرق.

في C99

لدينا ما يلي و رأس <tgmath.h> النوع-عام وحدات الماكرو.

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

إذا كنت لا يمكن ترجمة هذا, ربما كنت قد تركت مكتبة الرياضيات.أمر مشابه لما هذا يعمل على كل ج مترجم علي (عدة).

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);

هناك حكمه في مساحة الأمراض المنقولة جنسيا أيضا.

إذا كنت لا يمكن ترجمة هذا قد يكون باستخدام C تجميع بدلا من C++.الأساسية التالية الأمر لا تنتج أخطاء أو تحذيرات مع g++ 6.3.1, x86_64-w64-mingw32-g++ 6.3.0, رنة x86_64++ 3.8.0 و Visual C++ 2015 المجتمع.

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

مع ترتيبي شعبة

عند تقسيم اثنين الأعداد الترتيبية ، حيث T هو قصير, int, طويل, أو آخر ترتيبي التقريب التعبير هذا.

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

دقة

لا شك أن المظهر الغريب أخطاء تظهر في عمليات النقطة العائمة, ولكن هذا فقط عندما تظهر الأرقام و ليس له علاقة تذكر مع التقريب.

المصدر ليست مجرد رقم من ارقام كبيرة في العشري من IEEE تمثيل النقطة العائمة رقم ، فمن ذات الصلة لدينا عشري التفكير مثل البشر.

عشرة هو نتاج خمس و اثنين و 5 و 2 نسبيا رئيس الوزراء.ولذلك النقطة العائمة لـ IEEE معايير لا يمكن أن تكون ممثلة تماما كما الأرقام العشرية لجميع الثنائية الرقمية التمثيل.

هذا ليس مشكلة مع خوارزميات التقريب.فمن الرياضية الحقيقة التي ينبغي مراعاتها أثناء اختيار أنواع تصميم حسابات, مدخل بيانات, و عرض أرقام.إذا كان تطبيق يعرض الأرقام التي تظهر هذه العشرية-ثنائي تحويل القضايا ، ثم تطبيق بصريا معربا عن الدقة التي لا وجود لها في الواقع الرقمي ويجب تغييرها.

وظيفة 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);
    }

أن تكون ترجمة نظيفة ، يتضمن "الرياضيات.ح" و "القيود" ضرورية.وظيفة يعمل وفقا التالية التقريب المخطط:

  • جولة 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

إذا كنت تحتاج إلى أن تكون قادرة على ترجمة التعليمات البرمجية في البيئات التي تدعم معيار C ++ 11، ولكنها تحتاج أيضا لتكون قادرة على تجميع أن نفس الرمز في البيئات التي لا تدعم ذلك، يمكنك استخدام ماكرو وظيفة ل الاختيار بين الأمراض المنقولة جنسيا :: جولة () وظيفة مخصصة لكل نظام. يمر -DCPP11 أو /DCPP11 إلى مترجم المتوافقة 11 C ++ (أو استخدام المدمج في وحدات الماكرو الإصدار)، وجعل رأس مثل هذا:

// 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 :: جولة () في البيئات التي لا C ++ المتوافقة 11، بما في ذلك الحفاظ على بت تسجيل ل-0.0. فإنه قد يسبب ضربة أداء طفيف، ومع ذلك، ومن المرجح أن يكون لديك مشاكل مع التقريب معينة "مشكلة" المعروفة القيم الفاصلة العائمة مثل 0.49999999999999994 أو قيم متشابهة.

وبدلا من ذلك، إذا كان لديك الوصول إلى مترجم المتوافقة 11 C ++، هل يمكن أن مجرد انتزاع الأمراض المنقولة جنسيا :: جولة () من رأس <cmath> لها، واستخدامها لجعل رأس الخاص الذي يحدد وظيفة إذا لم تكن بالفعل يعرف. لاحظ أن هذا قد لا يكون الحل الأمثل، ومع ذلك، لا سيما إذا كنت تحتاج إلى ترجمة لمنصات متعددة.

وبناء على استجابة Kalaxy، وما يلي هو الحل قالب أن جولات أي رقم النقطة العائمة إلى أقرب عدد صحيح نوع على أساس التقريب الطبيعي. فإنه يلقي أيضا خطأ في وضع التصحيح إذا كانت قيمة خارج النطاق من نوع عدد صحيح، وبالتالي يقضي تقريبا بوصفها وظيفة مكتبة قابلة للحياة.

    // 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);
    }

وكما أشار في تصريحات وإجابات أخرى، المكتبة ISO C ++ القياسية لا تضيف round() حتى ISO C ++ 11، عندما تم سحب هذه الوظيفة في بالرجوع إلى مكتبة الرياضيات القياسية ISO C99.

لالمعاملات إيجابية في [½، <م> يو بي ] round(x) == floor (x + 0.5)، حيث <م> يو بي هو 2 <سوب> 23 في لfloat عند تعيينها إلى IEEE-754 (2008 ) binary32، و2 <سوب> 52 في لdouble عندما تم تعيينه لIEEE-754 (2008) binary64. أرقام 23 و 52 تتوافق مع عدد من المخزنة بت العشري في هذه صيغتين الفاصلة العائمة. لالمعاملات إيجابية في [+0، ½) round(x) == 0، والمعاملات إيجابية في (يو بي ، + ∞ <م>] round(x) == x. كما هي وظيفة متماثل حول محور س، ويمكن التعامل معها الحجج السلبية x وفقا لround(-x) == -round(x).

وهذا يؤدي إلى رمز المدمجة أدناه. أنه يجمع إلى عدد معقول من تعليمات الجهاز عبر مختلف المنصات. أنا لاحظت الرمز الأكثر التعاقد على وحدات معالجة الرسومات، حيث يتطلب my_roundf() نحو عشرة التعليمات. اعتمادا على بنية المعالج وtoolchain، يمكن أن يكون هذا النهج القائم على الفاصلة العائمة إما أسرع أو أبطأ من التنفيذ على أساس صحيح من newlib المشار إليها في الجواب يختلف .

واختبرت my_roundf() شامل ضد تنفيذ newlib roundf() باستخدام إنتل مترجم النسخة 13، مع كل /fp:strict و/fp:fast. راجعت أيضا أن النسخة newlib يطابق roundf() في المكتبة mathimf من المترجم إنتل. اختبار شامل غير ممكن ل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;
}

وأنا استخدم بعد تنفيذ جولة في تعدين الذهب حرفيا للهندسة المعمارية 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

وأفضل طريقة لتقريب قيمة عائمة من "ن" منازل عشرية، هو على النحو التالي مع في O (1) الوقت: -

وعلينا أن جولة قبالة القيمة من خلال 3 أماكن أي ن = 3.So،

float a=47.8732355;
printf("%.3f",a);
// 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");

قد يكون وسيلة قذرة عدم كفاءة التحويل ولكن هيك، وأنها تعمل لول. وهذا امر جيد، لأنه ينطبق على تعويم الفعلي. ليس فقط يؤثر على الانتاج بصريا.

وفعلت ذلك:

#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;
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top