C ++でのfloatのround()


232

単純な浮動小数点丸め関数が必要なので、次のようにします。

double round(double);

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

私はmath.hでそれを見つけることができますがceil()、できfloor()ませんround()

それは別の名前で標準C ++ライブラリに存在しますか、それとも欠落していますか?


1
数値を丸めた数値として出力したいだけの場合std::cout << std::fixed << std::setprecision(0) << -0.9、たとえば、を実行するだけでよいようです。
フランク

44
これを保護する...見事な新しい丸めスキームを使用する新規ユーザーは、既存の回答を最初に読む必要があります。
Shog9、2011

12
roundはC ++ 11以降で使用できます<cmath>。残念ながら、Microsoft Visual Studioを使用している場合は、まだ表示されません。connect.microsoft.com
Alessandro

3
私の回答で述べたように、独自のロールroundを行うことには多くの注意事項があります。C ++ 11以前は、標準はを含まないC90に依存していましたround。C ++ 11はC99に依存していますroundが、C99にtruncはさまざまなプロパティがあり、アプリケーションによってはより適切なものも含まれています。ほとんどの回答は、ユーザーがさらに問題のある整数型を返したい場合があることを無視しているようにも見えます。
Shafik Yaghmour 14年

2
@uvts_cvsこれは、最新バージョンのVisual Studioに問題がないようです。ライブ確認してください
Shafik Yaghmour 2014年

回答:


144

C ++ 98標準ライブラリには、round()はありません。自分で書くこともできます。以下は、切り上げの実装です。

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

C ++ 98標準ライブラリにラウンド関数がないと考えられる理由は、実際にはさまざまな方法で実装できるためです。上記は一般的な方法の1つですが、多くの丸めを行う場合は、バイアスが少なく一般的にはより良い、round-to-evenなどあります。ただし、実装は少し複雑です。


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:中間点で何が起こるかを決定する丸めの定義複数あるため、「正しい」と「間違った」はありません。判断を下す前に事実を確認してください。
2013年

16
@MuhammadAnnaqeeb:そうです、C ++ 11のリリース以降、状況は大幅に改善されました。この質問は、人生が困難で喜びが少ない別の時に尋ねられて答えられました。それは、その時代に生きて戦った英雄、そして現代の道具を使うことがまだできない貧しい魂へのオードとしてここに残っています。
Andreas Magnusson 2014

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

詳細については、Boostのドキュメントを参照してください。

編集:C ++ 11、があるのでstd::roundstd::lround、とstd::llround


2
私はプロジェクトですでにブーストを使用しており、これに対して+1を行いましたfloor(value + 0.5)
Gustavo Maciel 2014年

@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 ます

ISO / IEC 9899:1990の7節およびISO / IEC 9899 / Amd.1:1995の7節に記述されているライブラリーを、以降、標準Cライブラリーと呼びます。1)

cppreferenceのround、lround、llroundCドキュメントに移動すると、roundおよび関連する関数がC99の一部であり、C ++ 03以前では使用できないことがわかります。

C ++ 11では、C ++ 11がC標準ライブラリの C99ドラフト標準に依存しているため、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のもう1つのオプションは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、またはブーストトランケーションを使用することです。

自分のバージョンのラウンドをロールするのは難しい

自分でロールすることは、見た目よりも難しいため、おそらく努力する価値はありません。浮動小数点を最も近い整数丸める、パート1浮動小数点を最も近い整数に丸める、パート2およびフロートを最も近い整数に丸める、パート3で説明します。

たとえば、実装を使用std::floorして追加する一般的なロールは、0.5すべての入力に対して機能しません。

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

これが失敗する入力の1つは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 ) 

オーバーフローが発生しますライブで確認してください)。

Cでround()を実装する簡潔な方法に対するこの回答を見ると、これが実際にどれほど難しいかがわかりますか?単精度浮動小数点ラウンドの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 2014

1
素晴らしい完全な答え-特に0.5未満の部分。別のニッチ:round(-0.0)。C仕様が指定されていないようです。-0.0結果として私は期待します。
chux-モニカを2015年

3
@chuxは興味深いものであり、IEEE 754-2008標準では、丸めによってゼロと無限大の符号が保持されることを指定しています(5.9を参照)。
Ruslan 2016年

1
@Shafikこれは素晴らしい答えです。丸めでさえ簡単な操作だとは思っていません。
Ruslan 2016年

1
おそらく、数値的およびパフォーマンス上の理由からC ++ 11が使用可能な場合よりstd::rint()も望ましいことは、言及する価値std::round()があります。round()の特別なモードとは異なり、現在の丸めモードを使用します。rint単一の命令にインライン化できるx86では、はるかに効率的です。(gccと打ち鳴らすさえせずにそれを行う-ffast-math godbolt.org/g/5UsL2eほぼ相当のみの打ち鳴らすインラインながら、nearbyint()ARMはのための単一命令をサポートしている)round()が、x86のそれが唯一のインライン複数の命令を持つことができ、かつ唯一と上の-ffast-math
ピーター・コルド

71

丸めから整数の結果が必要な場合は、ceilやfloorを通過させる必要がないことに注意してください。つまり、

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

3
ただし、0.49999999999999994の期待される結果は得られません(もちろん、期待する結果によっては、0の方が1よりも合理的と思われます)
stijn

@stijn良いキャッチ。定数に長い二重リテラルサフィックスを追加すると、例の問題が修正されることがわかりましたが、他の精度の例ではキャッチできないのかどうかわかりません。
kalaxy 2013年

1
ところで、0.5の代わりに0.49999999999999994を追加すると、入力として0.49999999999999994と5000000000000001.0の両方で問題なく動作します。すべての値で問題ないかどうかはわかりませんが、これが最終的な修正であることを示すリファレンスが見つかりませんでした。
stijn 2013年

1
@stijn 2つの整数のちょうど中間の値が丸められる方向を気にしない場合は、すべての値で問題ありません。考えずに、次のケースのケース分析で証明します。0<= d <0.5、0.5 <= d <1.5、1.5 <= d <2 ^ 52、d> = 2 ^ 52。単精度のケースも徹底的にテストしました。
Pascal Cuoq 2013年

3
4.9 [conv.fpint]に従って、「切り捨てられた値を宛先タイプで表すことができない場合の動作は未定義です。」、これは少し危険です。他のSOの回答は、これを確実に行う方法を説明しています。
Tony Delroy、2015

41

cmathのC ++ 11以降で利用可能です(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
またlroundllround積分結果がある
sp2danny 2015

@ sp2danny:またはそれ以上、ファンキーなゼロから離れたタイブレークの代わりにlrint現在の丸めモードを使用しますround
Peter Cordes 2017年

27

通常はとして実装されfloor(value + 0.5)ます。

編集:そして私が知っている少なくとも3つの丸めアルゴリズムがあるため、それはおそらく丸められていません:ゼロへの丸め、最も近い整数への丸め、および銀行員の丸め。最も近い整数への丸めを求めています。


1
「ラウンド」の異なるバージョンを区別することは良いことです。どちらを選ぶかについても知っておくとよいでしょう。
xtofl 2009年

5
確かに、すべてが「正しい」と合理的に主張できるさまざまな丸めアルゴリズムがあります。ただし、floor(value + 0.5)はこれらの1つではありません。0.49999997fまたは同等のdoubleなどの一部の値の場合、答えは正しくありません。すべてが0であることに同意すると、1.0に丸められます。詳細については、この記事を参照してください。 blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
ブルース・ドーソン

14

私たちが見ている2つの問題があります。

  1. 丸め変換
  2. タイプ変換。

丸め変換とは、±float / doubleを最も近い床/ ceil float / doubleに丸めることを意味します。あなたの問題はここで終わるかもしれません。ただし、Int / Longを返すことが予想される場合は、型変換を実行する必要があるため、「オーバーフロー」の問題がソリューションに影響を与える可能性があります。SO、関数のエラーをチェックしてください

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.5LONG_MAX+0.5合併症が発生します。 正確な変換のために精度をLONG_MAX超える可能性がありますdouble。さらに正確に表現でき、キャストに失敗する正確な結果が得られる可能性があるため、assert(x < LONG_MAX+0.5); (<vs <=)が必要になる可能性があります。他のコーナーの問題も。LONG_MAX+0.5(x)+0.5LONG_MAX+1long
chux-2016年

関数を呼び出さないでください。round(double)その名前の標準の数学ライブラリ関数(C ++ 11)がすでに存在するため、混乱を招きます。利用std::lrint(x)可能な場合に使用します。
Peter Cordes 2017年

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は、単純な丸め関数のセットも提供します。私の答えを見てください。
ダニエルウルフ、

boost:numeric::RoundEven< double >::nearbyint整数にしたくない場合は、直接使用することもできます。@DanielWolfは、aka.niceによってレイアウトされた問題がある+0.5を使用して単純な関数が実装されていることに注意してください
stijn

6

あなたはn桁の精度に丸めることができます:

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
コンパイラのintサイズのデフォルトが1024ビットでない限り、これは巨大なdoubleに対して正確ではありません...
aka.nice

それが使用される場合、それは許容できると思います:double値が1.0 e + 19の場合、3桁に丸めるのは意味がありません。
カール

3
確かに、しかし、問題は一般的なラウンドに対するものであり、それがどのように使用されるかを制御することはできません。ceilとfloorが失敗しないのに、ラウンドが失敗する理由はありません。
aka.nice 2012年

これは、の範囲外の引数に対して未定義の動作をしますint。(x86で実際には、範囲外のFP値が作るCVTTSD2SI農産物を0x80000000、すなわち、整数ビットパターンとしてINT_MIN、その後に変換バックされる、double
ピーターコルド

5

最近では、C99 / C ++ 11数学ライブラリを含むC ++ 11コンパイラを使用することは問題になりません。しかし、問題は次のようになります。どの丸め関数を選びますか?

C99 / C ++ 11 round()は、多くの場合、実際に必要な丸め関数ではありません。中途半端な場合のタイブレークとして0から離れて丸めるファンキーな丸めモードを使用します(+-xxx.5000)。具体的にその丸めモードが必要な場合、またはC ++実装を対象としている場合round()rint()、それを使用します(または、この質問の他の回答の1つを使用してその動作をエミュレートし、額面どおりに特定の注意深く再現しました)丸め動作。)

round()の丸めは、IEEE754のデフォルトのから最も近いモードへのタイブレークとは異なります。最近傍は、数値の平均の大きさの統計的バイアスを回避しますが、偶数に向かってバイアスをかけます。

現在のデフォルトの丸めモードを使用する2つの数学ライブラリ丸め関数があります。std::nearbyint()およびstd::rint()、C99 / C ++ 11で追加されたので、いつでも使用できますstd::round()。唯一の違いは、nearbyintFE_INEXACT が発生しないことです。

好むrint()パフォーマンス上の理由の両方gccと打ち鳴らすより簡単にそれをインライン化が、GCCは決してインライン:nearbyint()(さえと-ffast-math


x86-64およびAArch64のgcc / clang

いくつかのテスト関数をMatt GodboltのCompiler Explorerに配置しました。ソース+ asm出力を確認できます(複数のコンパイラーの場合)。コンパイラー出力の読み取りの詳細については、このQ&AとMattのCppCon2017の講演を参照してください。「私のコンパイラーは最近何ができましたか?コンパイラの蓋を外す」

FPコードでは、小さな関数をインライン化することは通常大きな利点です。特にWindows以外では、標準の呼び出し規約には呼び出し保存レジスタがないため、コンパイラはFP値をXMMレジスタに保持できませんcall。そのため、asmを本当に知らなくても、それがライブラリ関数への末尾呼び出しであるのか、1つまたは2つの数学命令にインライン化されているのかを簡単に確認できます。1つまたは2つの命令にインライン化するものはすべて、関数呼び出し(x86またはARMでのこの特定のタスク)よりも優れています。

x86では、SSE4.1にインラインroundsd化するものはすべて、SSE4.1 roundpd(またはAVX vroundpd)で自動ベクトル化できます。(AVX512を必要とするFP-> 64ビット整数を除き、FP->整数変換はパックされたSIMD形式でも利用できます。)

  • std::nearbyint()

    • x86 clang:で単一のinsnにインライン化し-msse4.1ます。
    • x86 gcc:-msse4.1 -ffast-mathgcc 5.4 以前でのみ、単一のinsnにインライン化します。後でgccがインライン化することは決してありません(おそらく、即時ビットの1つが不正確な例外を抑制できることに気付いていませんでしたか?それはclangが使用するものですが、古いgcc rintはインライン化する場合と同じ即時を使用します)。
    • AArch64 gcc6.3:デフォルトでは、単一のinsnにインライン化します。
  • std::rint

    • x86 clang:単一のinsnへのインライン化 -msse4.1
    • x86 gcc7:で単一のinsnにインライン化し-msse4.1ます。(SSE4.1なしでは、いくつかの指示にインライン化されます)
    • x86 gcc6.x以前:で単一のinsnにインライン化し-ffast-math -msse4.1ます。
    • AArch64 gcc:デフォルトで単一のinsnにインライン化
  • std::round

    • x86 clang:インライン化しません
    • x86 gcc:で複数の命令にインライン化します-ffast-math -msse4.1。2つのベクトル定数が必要です。
    • AArch64 gcc:単一の命令にインライン化します(この丸めモードおよびIEEEのデフォルトなどのHWサポート)。
  • std::floor/ std::ceil/std::trunc

    • x86 clang:単一のinsnへのインライン化 -msse4.1
    • x86 gcc7.x:単一のinsnへのインライン化 -msse4.1
    • x86 gcc6.x以前:単一のinsnへのインライン化 -ffast-math -msse4.1
    • AArch64 gcc:デフォルトで単一の命令にインライン化

int/ long/に丸めるlong long

ここには2つのオプションがあります:使用するlrintrintただし、を返すlong、またはlong longforのように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-> integer変換により、INT_MINorまたはLLONG_MIN0x8000000符号ビットセットのみの64ビット相当のビットパターン)が生成されます。Intelはこれを「整数の不定」値と呼びます。(参照手動エントリ、それはで利用できることを切り捨てと変換()符号付き整数のスカラーダブル。SSE2命令を32ビットまたは64ビットの整数宛先(64ビットモードでのみ)もあります現在の丸めと(変換がモード)、これはコンパイラーが出力したいものですが、残念ながらgccとclangはなしではそれを行いません。cvttsd2sicvtsd2si-ffast-math

また、unsignedint / long へのFP はx86(AVX512なし)では効率が悪いことに注意してください。64ビットマシンでの32ビット符号なしへの変換はかなり安価です。64ビットの符号付きに変換して切り捨てるだけです。しかし、それ以外の場合は大幅に遅くなります。

  • なし/付きのx86打ち鳴らす-ffast-math -msse4.1(int/long)rintにインラインroundsd/ cvttsd2si。(への最適化がありませんcvtsd2si)。 lrintインライン化しません。

  • x86 gcc6.xおよびそれ以前-ffast-math:どちらもインライン化しない

  • x86 gcc7なし-ffast-math(int/long)rint丸め、個別に変換します(SSE4.1の合計2つの命令が有効になっています。それ以外の場合は、rintなしでインライン化されたコードの束が含まれますroundsd)。 lrintインライン化しません。
  • x86 gcc with -ffast-math:(最適)にインライン化するすべての方法cvtsd2si。SSE4.1は不要。

  • AArch64 gcc6.3なし-ffast-math(int/long)rint2つの命令にインライン化します。 lrintインライン化しない

  • AArch64 gcc6.3 with -ffast-math(int/long)rintは、の呼び出しにコンパイルされlrintます。 lrintインライン化しません。これを行わない2つの命令-ffast-mathが非常に遅い場合を除き、これは最適化を見逃している可能性があります。

TODO:GodboltではICCとMSVCも利用できますが、これについては出力を確認していません。編集は歓迎します...また、コンパイラ/バージョンで最初に分解し、次にその中の関数で分解する方が便利でしょうか?ほとんどの人は、FP-> FPまたはFP->整数の丸めをどれだけ適切にコンパイルするかに基づいてコンパイラを切り替えることはしません。
Peter Cordes

2
+1はrint()、それが実行可能な選択である場所を推奨するためのもので、通常そうです。round()プログラマーにはこの名前が意味するものだと思いますが、これはrint()神秘的であるように見えます。round()「ファンキーな」丸めモードを使用しないことに注意してください。round-to-nearest-ties-awayは、公式のIEEE-754(2008)丸めモードです。とnearbyint()ほぼ同じであり、条件下rint()同一である必要があることを考えると、インライン化されないのは奇妙です-ffast-math。それは私にはバグっぽく見えます。
njuffa 2017年

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

ここで、floor(x + 0.5)を回避するもう1つの理由を示します


2
反対票について知りたいです。ネクタイが最も近い偶数ではなくゼロから離れて解決されるためですか?
aka.nice 14

1
注:C仕様では、「現在の丸め方向に関係なく、ケースをゼロから半分に丸める」と記載されているため、奇数/偶数に関係なく丸めを行うことが準拠しています。
chux-モニカを2015年

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

パフォーマンスが向上する理由の1つに、分岐のスキップがあります。


これは、の範囲外の引数に対して未定義の動作をしますint。(x86で実際には、範囲外のFP値が作るCVTTSD2SI農産物を0x80000000、すなわち、整数ビットパターンとしてINT_MIN、その後に変換バックされる、double
ピーターコルド

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の場合

IEEE倍精度浮動小数点に依存する#include <cmath>には、以下の追加のオーバーロードがあります。

#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、およびVisual C ++ 2015 Communityでエラーも警告も生成しません。

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

序数部あり

Tがshort、int、long、または別の序数である2つの序数を除算する場合、丸め式はこれです。

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

正確さ

浮動小数点演算で奇妙に見える不正確さが現れることは間違いありませんが、これは数値が表示される場合のみであり、丸めとはほとんど関係がありません。

ソースは、浮動小数点数のIEEE表現の仮数の有効桁数だけではなく、人間としての10進数の考え方に関連しています。

10は5と2の積であり、5と2は比較的素数です。したがって、IEEE浮動小数点標準は、すべてのバイナリデジタル表現の10進数として完全に表すことができない場合があります。

これは、丸めアルゴリズムの問​​題ではありません。タイプの選択、計算の設計、データ入力、数値の表示の際に考慮すべき数学的現実です。アプリケーションがこれらの10進2進変換の問題を示す数字を表示する場合、アプリケーションはデジタル現実には存在しないため変更する必要がある精度を視覚的に表現しています。


1
「なぜこれほど多くの回答が定義、関数、またはメソッドを含むのかはわかりません。」それが尋ねられたときを見てください-C ++ 11はまだ出ていませんでした。;)
jaggedSpire 2017年

@jaggedSpire、それが適切だと思われる場合は、親指を上げてください。高スコアの答えはすべて、今日最も一般的に使用されているコンパイラのコンテキストでは時代遅れで誤解を招くためです。
FauChristian 2018年

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"と "limits"が含まれている必要があります。この関数は、次の丸めスキーマに従って機能します。

  • 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を期待します。また、先頭のガードの要点はわかりません。最初の2つは削除できた場合です。
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を参照してください。

これは、-0.0の符号ビットの保持を含め、C ++ 11に準拠していない環境でのstd :: round()に近似しています。ただし、パフォーマンスにわずかな影響を与える可能性があり、0.49999999999999994または類似の値など、特定の既知の「問題」の浮動小数点値を丸める際に問題が発生する可能性があります。

または、C ++ 11準拠のコンパイラにアクセスできる場合は、<cmath>ヘッダーからstd :: round()を取得し、それを使用して、まだ定義されていない場合に関数を定義する独自のヘッダーを作成できます。ただし、特に複数のプラットフォーム用にコンパイルする必要がある場合は、これは最適なソリューションではない可能性があることに注意してください。


1

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

1
私の答えで指摘したように追加0.5はすべての場合に機能するわけではありません。少なくともオーバーフローの問題に対処することで、未定義の動作を回避できます。
Shafik Yaghmour 2014

1

コメントやその他の回答で指摘されているように、ISO C ++標準ライブラリはround()、この関数がISO C99標準数学ライブラリを参照することによって組み込まれたISO C ++ 11まで追加されませんでした。

[½、ub ]の正のオペランドのround(x) == floor (x + 0.5)場合、ubはIEEE-754(2008)にマップされた場合は2 23floatIEEE-754(2008)binary32にマップされた場合は 2 52です。数値23と52は、これら2つの浮動小数点形式で格納されている仮数ビットの数に対応しています。[+0、½)の正のオペランド、および(ub、+∞]の正のオペランドの場合関数はx軸に対して対称であるため、負の引数はに従って処理できます。doublebinary64round(x) == 0round(x) == xxround(-x) == -round(x)

これにより、以下のコンパクトなコードになります。コンパイルして、さまざまなプラットフォームで妥当な数の機械語命令を生成します。私はGPUで最もコンパクトなコードを観察しましたが、ここにmy_roundf()は約12の命令が必要です。プロセッサーのアーキテクチャーとツールチェーンに応じて、この浮動小数点ベースのアプローチは、別の回答で参照されている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;
}

int16ビット幅以上であると想定しないように編集しました。もちろん、それfloatが4バイトのIEEE754 binary32であることを前提としています。C ++ 11 static_assertまたはおそらくマクロ#ifdef/それ#errorをチェックすることができます。(もちろん、C ++ 11が使用可能な場合は、を使用するstd::roundか、現在の丸めモードを使用する場合はstd::rint、gccとclangで適切にインライン化する必要があります)。
Peter Cordes 2017年

ところで、gcc -ffast-math -msse4.1インラインstd::round()add( AND(x, L1), OR(x,L2)し、その後roundsd。つまりround、の観点からかなり効率的に実装されrintます。あなたが持っている場合ので、しかし、C ++ソースに手動でこれを実行する理由は、ありませんstd::rint()std::nearbyint()あなたも持っていますstd::round()。ゴッドボルトのリンクと、さまざまなgcc / clangバージョンでインライン化するものとしないものの概要については、私の回答を参照してください。
Peter Cordes 2017年

@PeterCordes私は(後者がround-to-nearest-or-evenモードで動作している場合)のround()点で効率的に実装する方法をよく知っていますrint():私はそれをCUDA標準数学ライブラリに実装しました。ただし、この質問は、round()C ++ 11より前のC ++で実装する方法を尋ねるように見えたためrint()floor()およびのみで利用できませんceil()
njuffa 2017年

@PeterCordes申し訳ありませんが、私は間違って話しました。round()は、ゼロへの丸めモード(別名)から簡単に合成さrint()れます。最初のコーヒーの前に応答するべきではありませんでした。trunc()
njuffa 2017年

1
@PeterCordes私は、OPがの特定の丸め動作を必要としない可能性が高いことに同意しround()ます。ほとんどのプログラマーは単純に、最も近い丸round()rint()と対の違いを認識していません。後者は通常、ハードウェアによって直接提供されるため、より効率的です。私はプログラマに認識させるためにプログラミングガイドCUDAでそのうちに綴ら:「結果は単精度浮動小数点数であると、整数に単精度浮動小数点オペランドを丸めるための推奨される方法があるrintf()、ありませんroundf()」。
njuffa 2017年

0

私は、x86アーキテクチャおよびMS VS固有のC ++のラウンドインasmの次の実装を使用します。

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

UPD:double値を返す

__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

結果値は倍精度の浮動小数点値でなければなりません。
Truthseeker 2015年

@ Truthseeker:ええ、必要な戻り値の型を確認する必要がありました。OK、「UPD」を参照。
Aleksey F.

コンパイラーは、インラインで、rint()またはnearbyint()SSE4.1 roundsd命令またはx87 frndint命令にインライン化します。これは、レジスター内のデータに対してこのインラインasmを使用するために必要な2つのストア/リロードラウンドトリップよりもはるかに高速です。MSVCインラインasm frndintは、レジスタに入力を取得する方法がないため、単一の命令をラップする場合など、かなりの負荷を負っています。関数の最後で使用して結果st(0)を返すと、出力を返す方法として信頼できる場合があります。eaxasmを含む関数をインライン化する場合でも、整数の場合は明らかに安全です。
Peter Cordes 2017年

@PeterCordes最新の最適化を歓迎します。ただし、SSE4.1は現時点では存在しなかったため、使用できませんでした。私の目的は、2000年代の古いIntel P3またはP4ファミリーでも機能するラウンドの最小限の実装を提供することでした。
Aleksey F.17年

P3にはSSE2さえないので、コンパイラーは既にx87を使用しているので、のためにそれ自体を発行doubleできるはずfrndintですrint()。コンパイラーがSSE2を使用している場合、a doubleをXMMレジスターからx87 にバウンスして戻すことは価値がありません。
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");

それは変換の非効率的な汚い方法かもしれませんが、まあ、それは笑います。そして、それは実際のフロートに適用されるので、良いことです。視覚的に出力に影響を与えるだけではありません。


これは陽気に非効率的であり、最も近い値に丸める代わりに(常に後続の数字を破棄することにより)切り捨てます。
Peter Cordes 2017年

-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
10 ^ placeの二項演算子^ではなく、pow(10、place)を意味していませんでしたか?私のマシンの10 ^ 2は8を与えます!! それにもかかわらず、私のMac 10.7.4とgccでは、コードが機能せず、元の値が返されます。
Pete855217 2012
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.