web-dev-qa-db-ja.com

浮動小数点数をCで最も近い整数に丸める方法は?

Cで数値を丸める方法はありますか?

天井と床は使いたくない。他に選択肢はありますか?

グーグルで答えを探したときに、このコードスニペットに出くわしました。

(int)(num < 0 ? (num - 0.5) : (num + 0.5))

上記の行は、float num = 4.9の場合でも、常に値を4として出力します。

8
webgenius

4.9 + 0.5は5.4であり、コンパイラが深刻に壊れていない限り、4に丸めることはできません。

Googledコードが4.9の正解を与えることを確認しました。

marcelo@macbookpro-1:~$ cat round.c 
#include <stdio.h>

int main() {
    float num = 4.9;
    int n = (int)(num < 0 ? (num - 0.5) : (num + 0.5));
    printf("%d\n", n);
}
marcelo@macbookpro-1:~$ make round && ./round
cc     round.c   -o round
5
marcelo@macbookpro-1:~$
10
Marcelo Cantos

Cでfloatを丸めるために、ニーズを満たす3つの_<math.h>_関数があります。 rintf()をお勧めします。

_float nearbyintf(float x);
_

nearbyint関数は、現在の丸め方向を使用し、「不正確」浮動小数点例外を発生させずに、引数を浮動小数点形式の整数値に丸めます。 C11dr§7.12.9.32

または

_float rintf(float x);
_

rint関数がnearbyint関数(7.12.9.3)と異なるのは、結果の値が引数と異なる場合にrint関数が「inexact」浮動小数点例外を発生させる可能性があるという点だけです。 C11dr§7.12.9.42

または

_float roundf(float x);
_

round関数は、現在の丸め方向に関係なく、引数を浮動小数点形式の最も近い整数値に丸め、ゼロから半分のケースを丸めます。 C11dr§7.12.9.62


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

void rtest(const char *fname, double (*f)(double x), double x) {
  printf("Clear inexact flag       :%s\n", feclearexcept(FE_INEXACT) ? "Fail" : "Success");
  printf("Set round to nearest mode:%s\n", fesetround(FE_TONEAREST)  ? "Fail" : "Success");

  double y = (*f)(x);
  printf("%s(%f) -->  %f\n", fname,x,y);

  printf("Inexact flag             :%s\n", fetestexcept(FE_INEXACT) ? "Inexact" : "Exact");
  puts("");
}

int main(void) {
  double x = 8.5;
  rtest("nearbyint", nearbyint, x);
  rtest("rint", rint, x);
  rtest("round", round, x);
  return 0;
}
_

出力

_Clear inexact flag       :Success
Set round to nearest mode:Success
nearbyint(8.500000) -->  8.000000
Inexact flag             :Exact

Clear inexact flag       :Success
Set round to nearest mode:Success
rint(8.500000) -->  8.000000
Inexact flag             :Inexact

Clear inexact flag       :Success
Set round to nearest mode:Success
round(8.500000) -->  9.000000
Inexact flag             :Exact
_

OPのコードの弱点は何ですか?

_(int)(num < 0 ? (num - 0.5) : (num + 0.5))
_
  1. numの値がintの範囲に近くない場合、キャスト_(int)_は未定義の動作になります。

  2. _num +/- 0.5_の結果が不正確な場合。 _0.5_はdoubleであり、floatよりも高い精度で加算が行われるため、これはここでは起こりそうにありません。 numと_0.5_の精度が同じである場合、数値に_0.5_を追加すると、数値の丸めの回答が得られる場合があります。 (これはOPの投稿の整数の四捨五入ではありません。)例:0.5未満の数値は、OPの目標ごとに0に四捨五入する必要がありますが、_num + 0.5_は、1.0から1.0未満の最小のdoubleまでの正確な答えになります。 。正確な答えは表現できないので、その合計は、通常1.0に丸められ、誤った答えになります。同様の状況は、多数の場合に発生します。


「上記の行は、_float num =4.9_の場合でも、常に値を4として出力する」というOPのジレンマ。述べられているように説明することはできません。追加のコード/情報が必要です。 OPが_int num = 4.9;_を使用した可能性があります。


_// avoid all library calls
// Relies on UINTMAX_MAX >= FLT_MAX_CONTINUOUS_INTEGER - 1
float my_roundf(float x) {
  // Test for large values of x 
  // All of the x values are whole numbers and need no rounding
  #define FLT_MAX_CONTINUOUS_INTEGER  (FLT_RADIX/FLT_EPSILON)
  if (x >= FLT_MAX_CONTINUOUS_INTEGER) return x;
  if (x <= -FLT_MAX_CONTINUOUS_INTEGER) return x;

  // Positive numbers
  // Important: _no_ precision lost in the subtraction
  // This is the key improvement over OP's method
  if (x > 0) {
    float floor_x = (float)(uintmax_t) x;
    if (x - floor_x >= 0.5) floor_x += 1.0f;
    return floor_x;
  }

  if (x < 0) return -my_roundf(-x);
  return x; //  x is 0.0, -0.0 or NaN
}
_

少しテストしました-後で時間があればそうします。

一般的な解決策は、 rint() を使用し、必要に応じて FLT_ROUNDS 丸めモードを設定することです。

3
Dan Story

それがそんなに良い考えかどうかはわかりません。そのコードはキャストに依存しており、正確な切り捨ては未定義であると確信しています。

float result = (num - floor(num) > 0.5) ? ceil(num) : floor(num);

キャストに依存しないので、これははるかに良い方法だと思います(基本的にはShirokoが投稿したものです)。

3
Puppy

googledコードは正しく機能します。その背後にある考え方は、小数が.5未満の場合は切り捨て、それ以外の場合は切り上げるというものです。 (int)は、小数点を削除するint型にfloatをキャストします。正の数値に.5を追加すると、次の整数にドロップされます。負の数から.5を引くと、同じことが行われます。

1
datdo

あなたが探しているのは:int n = (d - floor(d) > 0.5) ? ceil(d) : floor(d);だと思います

1
Shiroko

数値に0.5を追加して型キャストし、整数で型キャストして出力します。それ以外の場合は、引数をそれぞれの数値として渡すround()を使用できます。

0

値xを精度pに丸めます。ここで、0 <p <無限大です。 (f.ex. 0.25、0.5、1、2、…)

float RoundTo(float x, float p)
{
  float y = 1/p;
  return int((x+(1/(y+y)))*y)/y;
}

float RoundUp(float x, float p)
{
  float y = 1/p;
  return int((x+(1/y))*y)/y;
}

float RoundDown(float x, float p)
{
  float y = 1/p;
  return int(x*y)/y;
}
0
JJussi

_fenv.h_(C99で導入)でfesetround()を使用できる場合があります。可能な引数は、マクロ_FE_DOWNWARD_、_FE_TONEAREST_、_FE_TOWARDZERO_、および_FE_UPWARD_ですが、必ずしもすべてが定義されているわけではなく、プラットフォーム/実装でサポートされているものだけであることに注意してください。です。次に、_math.h_(C99も)でさまざまなroundrint、およびnearbyint関数を使用できます。このようにして、値が正か負かに関係なく、目的の丸め動作を1回設定し、同じ関数を呼び出すことができます。

(たとえば、lroundを使用すると、通常、必要なものを取得するために、通常の使用で丸め方向を設定する必要さえありません。)

0
Arkku
int round(double x)
{
return x >= 0.0 ? int(x + 0.5) : int(x - int(x-1) + 0.5) + int(x-1);
}

天井と床のあるバージョンよりも高速になります。

0