可能な場合は double 値を整数に変換するような関数を記述する必要がありますdouble_to_int(double val, int *err)
。そうでない場合はエラー (NAN/INFs/OUT_OF_RANGE) を報告します。
擬似コードの実装は次のようになります。
if isnan(val):
err = ERR_NAN
return 0
if val < MAX_INT:
err = ERR_MINUS_INF
return MIN_INT
if ...
return (int)val
SOには少なくとも2つの同様の質問があります。これ答えは、C++のソリューションではありますが、十分にきれいな方法で解決されています。Cでは、符号付き整数にポータブルな数字がありません。これ答えでは、なぜ単に をチェックすることができないのかが説明されています(val > INT_MAX || val < INT_MIN)
。
したがって、私が考える唯一のクリーンな方法は浮動小数点環境を使用することですが、これは実装定義の機能として規定されています。
そこで私の質問は:クロスプラットフォーム方式で関数を実装する方法はありますかdouble_to_int
(C 標準のみに基づき、IEEE-754 をサポートするターゲット プラットフォームを考慮せずに)?
ベストアンサー1
[この回答は完全に新しいアプローチで編集されました。]
このアプローチでは、C標準の浮動小数点形式の定義(符号付き基数)を使用します。b数値を累乗したb仮数の桁数( によって提供DBL_MANT_DIG
)と指数の制限( によって提供)がわかっていると、エンドポイントとしてDBL_MAX_EXP
正確な値を準備できます。double
最初のコメントで述べられている控えめな追加要件を条件として、すべての準拠 C 実装で動作すると信じています。
/* This code demonstrates safe conversion of double to int in which the
input double is converted to int if and only if it is in the supported
domain for such conversions (the open interval (INT_MIN-1, INT_MAX+1)).
If the input is not in range, an error is indicated (by way of an
auxiliary argument) and no conversion is performed, so all behavior is
defined.
There are a few requirements not fully covered by the C standard. They
should be uncontroversial and supported by all reasonable C implementations:
Conversion of an int that is representable in double produces the
exact value.
The following operations are exact in floating-point:
Dividing by the radix of the floating-point format, within its
range.
Multiplying by +1 or -1.
Adding or subtracting two values whose sum or difference is
representable.
FLT_RADIX is representable in int.
DBL_MIN_EXP is not greater than -DBL_MANT_DIG. (The code can be
modified to eliminate this requirement.)
Deviations from the requested routine include:
This code names the routine DoubleToInt instead of double_to_int.
The only error indicated is ERANGE. Code to distinguish the error more
finely, such as providing separate values for NaNs, infinities, and
out-of-range finite values, could easily be added.
*/
#include <float.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
/* These values will be initialized to the greatest double value not greater
than INT_MAX+1 and the least double value not less than INT_MIN-1.
*/
static double UpperBound, LowerBound;
/* Return the double of the same sign of x that has the greatest magnitude
less than x+s, where s is -1 or +1 according to whether x is negative or
positive.
*/
static double BiggestDouble(int x)
{
/* All references to "digits" in this routine refer to digits in base
FLT_RADIX. For example, in base 3, 77 would have four digits (2212).
In this routine, "bigger" and "smaller" refer to magnitude. (3 is
greater than -4, but -4 is bigger than 3.)
*/
// Determine the sign.
int s = 0 < x ? +1 : -1;
// Count how many digits x has.
int digits = 0;
for (int t = x; t; ++digits)
t /= FLT_RADIX;
/* If the double type cannot represent finite numbers this big, return the
biggest finite number it can hold, with the desired sign.
*/
if (DBL_MAX_EXP < digits)
return s*DBL_MAX;
// Determine whether x is exactly representable in double.
if (DBL_MANT_DIG < digits)
{
/* x is not representable, so we will return the next lower
representable value by removing just as many low digits as
necessary. Note that x+s might be representable, but we want to
return the biggest double less than it, which is also the biggest
double less than x.
*/
/* Figure out how many digits we have to remove to leave at most
DBL_MANT_DIG digits.
*/
digits = digits - DBL_MANT_DIG;
// Calculate FLT_RADIX to the power of digits.
int t = 1;
while (digits--) t *= FLT_RADIX;
return x / t * t;
}
else
{
/* x is representable. To return the biggest double smaller than
x+s, we will fill the remaining digits with FLT_RADIX-1.
*/
// Figure out how many additional digits double can hold.
digits = DBL_MANT_DIG - digits;
/* Put a 1 in the lowest available digit, then subtract from 1 to set
each digit to FLT_RADIX-1. (For example, 1 - .001 = .999.)
*/
double t = 1;
while (digits--) t /= FLT_RADIX;
t = 1-t;
// Return the biggest double smaller than x+s.
return x + s*t;
}
}
/* Set up supporting data for DoubleToInt. This should be called once prior
to any call to DoubleToInt.
*/
static void InitializeDoubleToInt(void)
{
UpperBound = BiggestDouble(INT_MAX);
LowerBound = BiggestDouble(INT_MIN);
}
/* Perform the conversion. If the conversion is possible, return the
converted value and set *error to zero. Otherwise, return zero and set
*error to ERANGE.
*/
static int DoubleToInt(double x, int *error)
{
if (LowerBound <= x && x <= UpperBound)
{
*error = 0;
return x;
}
else
{
*error = ERANGE;
return 0;
}
}
#include <string.h>
static void Test(double x)
{
int error, y;
y = DoubleToInt(x, &error);
printf("%.99g -> %d, %s.\n", x, y, error ? strerror(error) : "No error");
}
#include <math.h>
int main(void)
{
InitializeDoubleToInt();
printf("UpperBound = %.99g\n", UpperBound);
printf("LowerBound = %.99g\n", LowerBound);
Test(0);
Test(0x1p31);
Test(nexttoward(0x1p31, 0));
Test(-0x1p31-1);
Test(nexttoward(-0x1p31-1, 0));
}