可以使用便携式C

时间:2018-06-29 15:24:57

标签: c floating-point standards

我需要编写类似double_to_int(double val, int *err)的函数 在可能的情况下会将double val转换为整数;否则报告错误(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至少有两个类似的问题: 在this中,尽管它是C ++解决方案,但是它以足够干净的方式解决了-在C中,我们没有用于签名int的可移植数字。 在this答案中,解释了为什么我们不能仅检查(val > INT_MAX || val < INT_MIN)

因此,我看到的唯一可能的干净方法是使用浮点环境,但这被声明为实现定义的功能。

所以我的问题:有没有办法以跨平台的方式实现double_to_int功能(仅基于C标准,甚至没有考虑 目标平台以支持IEEE-754)??

8 个答案:

答案 0 :(得分:3)

[此答案已通过全新方法进行编辑。]

此方法使用C标准中的浮点格式定义-作为有符号的base- 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));
}

答案 1 :(得分:3)

可以从双重到整数的对话可以用可移植的C语言编写”的答案显然是“ ”。

例如,您可以将浮点值sprintf到字符串,进行基于字符串的检查(即,通过对也要进行sprintf的最大值和最小值进行基于字符串的比较),验证,舍入等,然后对已知的值进行sscanf -最终值的有效字符串。

实际上,您将朝着(a)可移植且(b)方便的中间表示形式前进。 C字符串具有很好的可移植性,但不太方便。如果可以使用外部库,则有几个比较方便,但是应该确认其可移植性。

例如(省略了四舍五入):

#include <stdio.h>
#include <math.h>
#include <limits.h>
#include <string.h>

int convert(double inVal) {
    // basic range check - does anybody have an integer format with more than 300 bits?
    if (fabs(inVal) > 1.0E100) {
        printf("well out of range");
        return 1;
    }

    // load string buffer with input
    char buf[110];
    sprintf(buf, "%0105.0f", inVal);

    // do range check on strings
    if (inVal < 0) {
        char minVal[110];
        sprintf(minVal, "%0105d", INT_MIN);
        if (strcmp(buf, minVal) > 0) {
            printf("too small input: %f\n", inVal);
            return -1;  // needs better error signify
        }
    } else {
        char maxVal[110];
        sprintf(maxVal, "%0105d", INT_MAX);
        if (strcmp(maxVal, buf) < 0) {
            printf("too large input: %f\n", inVal);
            return -1;  // needs better error signify
        }
    }

    // do final conversion
    int result;
    sscanf(buf, "%d", &result);

    printf("input: %f result: %d\n", inVal, result);  // diagnostic

    return result;
}

int main()
{
    // test values    
    convert( 0.);
    convert( -123.5);
    convert( 123.5);

    convert( ((double)INT_MIN)-1);
    convert( ((double)INT_MIN));
    convert( ((double)INT_MIN)+1);
    convert( 2.0*((double)INT_MIN));
    convert( ((double)INT_MIN)/2);

    convert( ((double)INT_MAX)-1);
    convert( ((double)INT_MAX));
    convert( ((double)INT_MAX)+1);
    convert( 2.0*((double)INT_MAX));
    convert( ((double)INT_MAX)/2);

    return 0;
}

哪个会产生预期的转化(请参见上方的测试用例):

% gcc test.c ; ./a.out
input: 0.000000 result: 0
input: -123.500000 result: -124
input: 123.500000 result: 124
too small input: -2147483649.000000
input: -2147483648.000000 result: -2147483648
input: -2147483647.000000 result: -2147483647
too small input: -4294967296.000000
input: -1073741824.000000 result: -1073741824
input: 2147483646.000000 result: 2147483646
input: 2147483647.000000 result: 2147483647
too large input: 2147483648.000000
too large input: 4294967294.000000
input: 1073741823.500000 result: 1073741824

答案 2 :(得分:1)

(此答案有争议,尽管我仍然认为我是正确的,所以请不要明智地投票。)

您不能在可移植的C语言中实现这样的功能。

在这方面,它就像malloc&c。

故事的寓意实际上是在C语言中混合类型从来都不是一个好主意。即以不必进行类型转换的方式编写代码。

答案 3 :(得分:1)

  

可以从doubleint的转换用可移植C(?)编写

     

有没有办法以跨平台的方式实现double_to_int函数(仅基于C标准,甚至不考虑目标平台来支持IEEE-754)?

int double_to_int(double val, int *err)

详细信息:(int)val会截断小数部分,因此使用val进行转换的(int)val的范围在数学上是
INT_MIN - 0.9999... ≤ val ≤ INT_MAX + 0.9999...
INT_MIN - 1 < val < INT_MAX + 1


是的,通过跨平台的方式,通过使用精确的浮点数学和常量,代码可以测试转换成功。

2.0*(INT_MAX/2+1)当然可以精确地转换为FP常数。

val - INT_MIN > -1.0val > INT_MIN - 1.0类似,但没有遭受INT_MIN - 1.0可能带来的不精确性(对于普通的2的补码机)。回想一下,整数类型可能比double的精度更高。考虑一个不能精确表示为int的64位INT_MIN - 1.0double

代码未使用(double)INT_MAX,这也可能不准确。


要复制myself

#include <limits.h>
#define DBL_INT_MAXP1 (2.0*(INT_MAX/2+1)) 
#define DBL_INT_MINM1 (2.0*(INT_MIN/2-1)) 

int double_to_int(double val, int *err) {
  if (val < DBL_INT_MAXP1) {
    #if -INT_MAX == INT_MIN
    // rare non-2's complement machine 
    if (val > DBL_INT_MINM1) {
      *err = OK;
      return (int) val;
    }
    #else
    if (val - INT_MIN > -1.0) {
      *err = OK;
      return (int) val;
    }
    #endif 
    // Underflow
    *err = ERR_MINUS_INF;
    return INT_MIN;
  }
  if (x > 0) {
    // Overflow
    *err = ERR_PLUS_INF;
    return INT_MAX;
  }
  // NaN;
  *err = ERR_NAN;
  return 0;
}

弱点:FLT == 10和大于34位的整数类型。

答案 4 :(得分:0)

也许这可能有效:

#define BYTES_TO_BITS(x)    (x*8)

void numToIntnt(double num, int *output) {
    const int upperLimit = ldexp(1.0, (BYTES_TO_BITS(sizeof(int))-1))-1;
    const int lowerLimit = (-1)*ldexp(1.0, (BYTES_TO_BITS(sizeof(int))-1));

    /*
     * or a faster approach if the rounding is acceptable:
     * const int upperLimit = ~(1<<(BYTES_TO_BITS(sizeof(int))-1));
     * const int lowerLimit = (1<<(BYTES_TO_BITS(sizeof(int))-1));
     */

    if(num > upperLimit) {
        /* report invalid conversion */
    } else if (num < lowerLimit) {
        /* report invalid conversion */
    } else {
        *output = (int)num;
    }
}                                                                                                                          

答案 5 :(得分:0)

潜在的问题是找到min_double_to_intmax_double_to_int,分别是最小的double和最大的int,可以将它们转换为int double_to_int(const double value, int *err) { if (!isfinite(value)) { if (isnan(value)) { if (err) *err = ERR_NAN; return 0; } else if (signbit(value)) { if (err) *err = ERR_NEG_INF; return INT_MIN; } else { if (err) *err = ERR_POS_INF; return INT_MAX; } } if (value < min_double_to_int) { if (err) *err = ERR_TOOSMALL; return INT_MIN; } else if (value > max_double_to_int) { if (err) *err = ERR_TOOLARGE; return INT_MAX; } if (err) *err = 0; return (int)value; }

可移植转换功能本身可以在C11中编写为

min_double_to_int

在首次使用上述功能之前,我们需要分配max_double_to_intINT_MAX

编辑于2018-07-03:重写的方法。

我们可以使用一个简单的函数来找到最小的10次幂,其大小至少等于INT_MIN / DBL_MAX_10_EXP。如果它们小于double,则int的范围大于INT_MAX的范围,我们可以将INT_MINdouble强制转换为{{1 }}。

否则,我们构造一个包含INT_MAX / INT_MIN的十进制表示形式的字符串,并使用strtod()将它们转换为double。如果此操作溢出,则意味着double的范围小于int的范围,我们可以将DBL_MAX / -DBL_MAX用作max_double_to_int和{ {1}}。

当我们将min_double_to_int作为INT_MAX时,可以使用循环使用double来增加该值。有限且使用nextafter(value, HUGE_VAL)舍入的最大值仍会产生相同的floor()值,即double

类似地,当我们将max_double_to_int设为双精度时,可以使用循环使用INT_MIN来减小该值。仍然是有限的,并且将(nextafter(value, -HUGE_VAL))向上舍入为相同的ceil()的最大值是double

这是一个示例程序来说明这一点:

min_double_to_int

答案 6 :(得分:0)

是的。 (为简便起见,省略了Nan / inf处理)

int convert(double x) {
   if (x == INT_MAX) {
     return INT_MAX;
   } else if (x > INT_MAX) {
     err = ERR_OUT_OF_RANGE; 
     return INT_MAX;
   } else if (x == INT_MIN) {
     return INT_MIN;
   } else if (x < INT_MIN)
     err = ERR_OUT_OF_RANGE;
     return INT_MIN;
   } else {
     return x;
   }
}

说明。

如链接的答案之一所述,边缘情况是INT_MAX不能精确地表示为double,并且在转换为double时被四舍五入,并且是对称的案例INT_MINif (x > INT_MAX)失败时就是这种情况。也就是说,比较返回false,但是我们仍然无法直接将x转换为int

链接的答案无法识别的是,只有一个双数字未通过测试,即(double)INT_MAX,我们可以通过显式检查x == INT_MAX来轻松地抓住这种情况。

编辑如注释中所述,如果INT_MAXINT_MINdouble范围之外,则可能会失败。尽管极不可能,但是标准并未排除这点。在这样的实现中,转换只是(int)x。在配置时比在运行时检测这种实现应该更容易。如果绝对需要后者,则可以一次执行该操作

static int need_simple_conversion = 0;
char* str = malloc(sizeof(int)*CHAR_BIT+1);
sprintf (str, "%d", INT_MAX);
errno = 0;
if (strtod(s, NULL) == HUGE_VAL && errno == ERANGE) {
   // INT_MAX overflows double => double can never overflow int
   need_simple_conversion = 1;
}

然后

if (need_simple_conversion)
    return x;
else { // as above

对于我们中间的偏执狂,也可以使用INT_MIN进行此操作,并分别执行正负双打检查。

答案 7 :(得分:-1)

据我所知,基本问题提炼为:将INT_MAX和INT_MIN的值标识为double-> int-> double。有趣的是,C有一种表达方式:

int isok(int val) {
   double dv = val;
   int iv = dv;
   return val == iv;
}

由此,上述答案的简明形式可以起作用,因为您可以使用它来确定INT_MAX,INT_MIN是否具有合理的可比性,因此:

if (isok(INT_MAX) && isok(INT_MIN) && f >= INT_MIN && f < INT_MAX) {
     // do your weirdo float stuff here...
}

但是,当然,依靠C严格的类型转换系统可以为编译器提供免费的许可证来重新格式化磁盘,因此可以通过printf / scanf代替它。