Cで数値を丸める方法はありますか?
天井と床は使いたくない。他に選択肢はありますか?
グーグルで答えを探したときに、このコードスニペットに出くわしました。
(int)(num < 0 ? (num - 0.5) : (num + 0.5))
上記の行は、float num = 4.9の場合でも、常に値を4として出力します。
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:~$
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))
_
num
の値がint
の範囲に近くない場合、キャスト_(int)
_は未定義の動作になります。
_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 丸めモードを設定することです。
それがそんなに良い考えかどうかはわかりません。そのコードはキャストに依存しており、正確な切り捨ては未定義であると確信しています。
float result = (num - floor(num) > 0.5) ? ceil(num) : floor(num);
キャストに依存しないので、これははるかに良い方法だと思います(基本的にはShirokoが投稿したものです)。
googledコードは正しく機能します。その背後にある考え方は、小数が.5未満の場合は切り捨て、それ以外の場合は切り上げるというものです。 (int)
は、小数点を削除するint型にfloatをキャストします。正の数値に.5を追加すると、次の整数にドロップされます。負の数から.5を引くと、同じことが行われます。
あなたが探しているのは:int n = (d - floor(d) > 0.5) ? ceil(d) : floor(d);
だと思います
数値に0.5を追加して型キャストし、整数で型キャストして出力します。それ以外の場合は、引数をそれぞれの数値として渡すround()を使用できます。
値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;
}
_fenv.h
_(C99で導入)でfesetround()
を使用できる場合があります。可能な引数は、マクロ_FE_DOWNWARD
_、_FE_TONEAREST
_、_FE_TOWARDZERO
_、および_FE_UPWARD
_ですが、必ずしもすべてが定義されているわけではなく、プラットフォーム/実装でサポートされているものだけであることに注意してください。です。次に、_math.h
_(C99も)でさまざまなround
、rint
、およびnearbyint
関数を使用できます。このようにして、値が正か負かに関係なく、目的の丸め動作を1回設定し、同じ関数を呼び出すことができます。
(たとえば、lround
を使用すると、通常、必要なものを取得するために、通常の使用で丸め方向を設定する必要さえありません。)
int round(double x)
{
return x >= 0.0 ? int(x + 0.5) : int(x - int(x-1) + 0.5) + int(x-1);
}
天井と床のあるバージョンよりも高速になります。