编写自己的数学楼层函数C的实现

时间:2017-01-25 16:31:45

标签: c math floor

我在考虑floor中提供的math.h功能。它很容易使用:

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

int main(void)
{
  for (double a = 12.5; a < 13.4; a += 0.1)
    printf("floor of  %.1lf is  %.1lf\n", a, floor(a));
  return 0;
}

如果我想编写自己的实现怎么办?它看起来会像这样:

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

double my_floor(double num)
{
    return (int)num;
}

int main(void)
{
    double a;

    for (a = 12.5; a < 13.4; a += 0.1)
        printf("floor of  %.1lf is  %.1lf\n", a, floor(a));

    printf("\n\n");

    for (a = 12.5; a < 13.4; a += 0.1)
        printf("floor of  %.1lf is  %.1lf\n", a, my_floor(a));

    return 0;
}

似乎它不适用于负数(my_floor),但第二个似乎没问题(my_floor_2):

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

double my_floor(double num)
{
    return (int)num;
}

double my_floor_2(double num)
{
    if(num < 0)
        return (int)num - 1;
    else
        return (int)num;
}

int main(void)
{
    double a1 = -12.5;

    printf("%lf\n", floor(a1));
    printf("%lf\n", my_floor(a1));
    printf("%lf\n", my_floor_2(a1));

    return 0;
}

程序输出:

img

其中一个最终是否正确?

5 个答案:

答案 0 :(得分:5)

不,你不能这样解决它。编写自己的实现的最佳方法是从平台上的C标准库中窃取一个。但请注意,可能包含特定于平台的细微差别,因此可能无法移植。

C标准库floor函数通常很聪明,因为它不能通过转换为整数类型来工作。如果确实如此,那么您将面临signed整数溢出的风险,其行为未定义。 (请注意,int的最小可能范围是-32767到+32767。

精确实现还取决于平台上使用的浮点方案。

对于使用IEEE754浮点和long long类型的平台,您可以采用此方案:

  1. 如果数字的大小大于2的53次方,则将其返回(因为它已经是整数)。
  2. 否则,转换为64位类型(很长),并将其返回。

答案 1 :(得分:2)

你的两次尝试都有局限性:

  • 如果double值超出int类型的范围,则转换为int是实现定义的。
  • 如果double值为负但为整数,则返回(int)num - 1不正确。

这是一个(几乎)可移植版本,试图处理所有情况:

double my_floor_2(double num) {
    if (num >= LLONG_MAX || num <= LLONG_MIN || num != num) {
        /* handle large values, infinities and nan */
        return num;
    }
    long long n = (long long)num;
    double d = (double)n;
    if (d == num || num >= 0)
        return d;
    else
        return d - 1;
}

如果类型long long的值位数多于类型double,则应该是正确的,大多数现代系统都是如此。

答案 2 :(得分:0)

C ++ 和32位算术中,可以这样做:

//---------------------------------------------------------------------------
// IEEE 754 double MSW masks
const DWORD _f64_sig    =0x80000000;    // sign
const DWORD _f64_exp    =0x7FF00000;    // exponent
const DWORD _f64_exp_sig=0x40000000;    // exponent sign
const DWORD _f64_exp_bia=0x3FF00000;    // exponent bias
const DWORD _f64_exp_lsb=0x00100000;    // exponent LSB
const DWORD _f64_exp_pos=        20;    // exponent LSB bit position
const DWORD _f64_man    =0x000FFFFF;    // mantisa
const DWORD _f64_man_msb=0x00080000;    // mantisa MSB
const DWORD _f64_man_bits=       52;    // mantisa bits
// IEEE 754 single masks
const DWORD _f32_sig    =0x80000000;    // sign
const DWORD _f32_exp    =0x7F800000;    // exponent
const DWORD _f32_exp_sig=0x40000000;    // exponent sign
const DWORD _f32_exp_bia=0x3F800000;    // exponent bias
const DWORD _f32_exp_lsb=0x00800000;    // exponent LSB
const DWORD _f32_exp_pos=        23;    // exponent LSB bit position
const DWORD _f32_man    =0x007FFFFF;    // mantisa
const DWORD _f32_man_msb=0x00400000;    // mantisa MSB
const DWORD _f32_man_bits=       23;    // mantisa bits
//---------------------------------------------------------------------------
double f64_floor(double x)
    {
    const int h=1;      // may be platform dependent MSB/LSB order
    const int l=0;
    union _f64          // semi result
        {
        double f;       // 64bit floating point
        DWORD u[2];     // 2x32 bit uint
        } y;
    DWORD m,a;
    int sig,exp,sh;
    y.f=x;
    // extract sign
    sig =y.u[h]&_f64_sig;
    // extract exponent
    exp =((y.u[h]&_f64_exp)>>_f64_exp_pos)-(_f64_exp_bia>>_f64_exp_pos);
    // floor bit shift
    sh=_f64_man_bits-exp; a=0;
    if (exp<0)
        {
        a=y.u[l]|(y.u[h]&_f64_man);
        if (sig) return -1.0;
        return 0.0;
        }
    // LSW
    if (sh>0)
        {
        if (sh<32) m=(0xFFFFFFFF>>sh)<<sh; else m=0;
        a=y.u[l]&(m^0xFFFFFFFF); y.u[l]&=m;
        }
    // MSW
    sh-=32;
    if (sh>0)
        {
        if (sh<_f64_exp_pos) m=(0xFFFFFFFF>>sh)<<sh; else m=_f64_sig|_f64_exp;
        a|=y.u[h]&(m^0xFFFFFFFF); y.u[h]&=m;
        }
    if ((sig)&&(a)) y.f--;
    return y.f;
    }
//---------------------------------------------------------------------------
float f32_floor(float x)
    {
    union               // semi result
        {
        float f;        // 32bit floating point
        DWORD u;        // 32 bit uint
        } y;
    DWORD m,a;
    int sig,exp,sh;
    y.f=x;
    // extract sign
    sig =y.u&_f32_sig;
    // extract exponent
    exp =((y.u&_f32_exp)>>_f32_exp_pos)-(_f32_exp_bia>>_f32_exp_pos);
    // floor bit shift
    sh=_f32_man_bits-exp; a=0;
    if (exp<0)
        {
        a=y.u&_f32_man;
        if (sig) return -1.0;
        return 0.0;
        }
    if (sh>0)
        {
        if (sh<_f32_exp_pos) m=(0xFFFFFFFF>>sh)<<sh; else m=_f32_sig|_f32_exp;
        a|=y.u&(m^0xFFFFFFFF); y.u&=m;
        }
    if ((sig)&&(a)) y.f--;
    return y.f;
    }
//---------------------------------------------------------------------------

重点是制作将从尾数中清除十进制位的掩码,如果是负输入,则非零清零位会减少结果。要访问各个位,您可以使用union将浮点值转换为整数表示(如示例中所示)或使用指针代替。

我在简单的 VCL 应用中对此进行了测试,如下所示:

float f32;
double f64;
AnsiString txt="";
// 64 bit
txt+="[double]\r\n";
for (f64=-10.0;f64<=10.0;f64+=0.1)
 if (fabs(floor(f64)-f64_floor(f64))>1e-6)
    {
    txt+=AnsiString().sprintf("%5.3lf %5.3lf %5.3lf\r\n",f64,floor(f64),f64_floor(f64));
    f64_floor(f64);
    }
for (f64=1;f64<=1e307;f64*=1.1)
    {
    if (fabs(floor( f64)-f64_floor( f64))>1e-6) { txt+=AnsiString().sprintf("%lf lf lf\r\n", f64,floor( f64),f64_floor( f64));
    f64_floor( f64); }
    if (fabs(floor(-f64)-f64_floor(-f64))>1e-6) { txt+=AnsiString().sprintf("%lf lf lf\r\n",-f64,floor(-f64),f64_floor(-f64));
    f64_floor(-f64); }
    }
// 32 bit
txt+="[float]\r\n";
for (f32=-10.0;f32<=10.0;f32+=0.1)
 if (fabs(floor(f32)-f32_floor(f32))>1e-6)
    {
    txt+=AnsiString().sprintf("%5.3lf %5.3lf %5.3lf\r\n",f32,floor(f32),f32_floor(f32));
    f32_floor(f32);
    }
for (f32=1;f32<=1e37;f32*=1.1)
    {
    if (fabs(floor( f32)-f32_floor( f32))>1e-6) { txt+=AnsiString().sprintf("%lf lf lf\r\n", f32,floor( f32),f32_floor( f32));
    f32_floor( f32); }
    if (fabs(floor(-f32)-f32_floor(-f32))>1e-6) { txt+=AnsiString().sprintf("%lf lf lf\r\n",-f32,floor(-f32),f32_floor(-f32));
    f32_floor(-f32); }
    }
mm_log->Lines->Add(txt);

没有差异结果(因此在所有测试案例中,它都匹配math.h floor()值。如果您想在 VCL 之外拍摄,则只需更改{{1到你手头的任何字符串类型,并将输出从AnsiString更改为你得到的任何东西(如控制台TMemo::mm_log或其他)

如果出现差异,cout的双重调用是为了进行调试(您可以直接放置断点并单步处理错误情况)。

<强> [注释]

请注意单词的顺序( MSW,LSW )与平台有关,因此您应相应地调整fxx_floor()常量。此代码未经过优化,因此很容易理解,因此不要指望它会很快。

答案 3 :(得分:0)

尽管其他人已经说过您需要从C标准库中获取它,但我已经构建了一个floor函数并在tio.run (Try It Online)onlinegdb.com上对其进行了测试。该函数本身不需要任何#include文件,但是为了打印出答案,我添加了stdio.h(在tio.runonlinegdb.com中,不在此处)。在这里:

long double myFloor(long double x) /* Change this to your liking: long double might
                                             be float in your situation.  */
{
    long double xcopy=x<0?x*-1:x;
    unsigned int zeros=0;
    long double n=1;
    for(n=1;xcopy>n*10;n*=10,++zeros);
    for(xcopy-=n;zeros!=-1;xcopy-=n)
        if(xcopy<0)
        {
            xcopy+=n;
            n/=10;
            --zeros;
        }
    xcopy+=n;
    return x<0?(xcopy==0?x:x-(1-xcopy)):(x-xcopy);
}

浮点数的下限是小于或等于它的最大整数。以下是一些示例:

floor(5.7)  = 5
floor(3)    = 3
floor(9.9)  = 9
floor(7.0)  = 7
floor(-7.9) = -8
floor(-5.0) = -5
floor(-3.3) = -3
floor(0)    = 0
floor(-0.0) = -0
floor(-0)   = -0

答案 4 :(得分:0)

与宽整数类型相比,浮点类型的精度足够小时,如果浮点值在整数范围内,则强制转换为该整数类型。

请查看该函数以获取long long范围之外的值,NAN,无穷大和-0.0所需的沙子调整。

#if DBL_MANT_DIG >= 64
#error TBD code
#endif

// LLONG_MAX is not exact as a double, yet LLONG_MAX + 1 is
#define LLONG_MAX_P1 ((LLONG_MAX/2 + 1)*2.0)

double my_floor(double x) {
  if (x >= 0.0) {
    if (x < LLONG_MAX_P1) {
      return (double)(long long)x;
    }
    return x;
  } else if (x < 0.0) {
    if (x >= LLONG_MIN) {
      long long ix = (long long) x; 
      return (ix == x) ? x : (double)(ix-1);
    }
    return x;
  }
  return x; // NAN
}