如何通过最小增量(或接近它)改变浮点数?

Posted

技术标签:

【中文标题】如何通过最小增量(或接近它)改变浮点数?【英文标题】:How to alter a float by its smallest increment (or close to it)? 【发布时间】:2008-09-30 22:29:20 【问题描述】:

我有一个 doublef 并希望有一种方法可以将它稍微大一点(或小一点)以获得一个尽可能接近原始值但仍严格大于(或小于)原件。

它不必关闭到最后一点——更重要的是,我所做的任何更改都可以保证产生不同的值,而不是返回到原来的值。

【问题讨论】:

双精度还是浮点数?根据您拥有的,最小值会有所不同。 是的,我意识到我的问题标题和描述不一致。我认为答案可以解决这两种情况,而接受的答案可以。 【参考方案1】:

检查您的 math.h 文件。如果幸运的话,您已经定义了 nextafternextafterf 函数。它们以可移植且独立于平台的方式完全满足您的需求,并且是 C99 标准的一部分。

另一种方法(可能是备用解决方案)是将浮点数分解为尾数和指数部分。递增很简单:只需将尾数加一即可。如果你得到一个溢出,你必须通过增加你的指数来处理这个问题。递减的工作方式相同。

编辑:正如 cmets 中所指出的,只需在其二进制表示中增加浮点数就足够了。尾数溢出将增加指数,这正是我们想要的。

简而言之,这与 nextafter 所做的相同。

不过,这不会是完全可移植的。您将不得不处理字节序以及并非所有机器都具有 IEEE 浮点数的事实(好的 - 最后一个原因更具学术性)。

同时处理 NAN 和无限可能有点棘手。您不能简单地增加它们,因为它们根据定义而不是数字。

【讨论】:

您特别不想处理尾数溢出,因为溢出将翻转到您想要的指数上。 酷 - 我从没想过。将浮点数递增为整数将完全满足需要。 这很酷 :) 现在那个拒绝我的答案的白痴可以说请撤消它吗? 如果指数增量溢出到符号位会如何工作? 我认为负值也必须区别对待。如果你增加这些结果将比原始值更负。顺便说一句 - 我刚刚在 VS.NET 2008 实现中拆解了 nextafter。他们做的比我预期的要多得多。【参考方案2】:
u64 &x = *(u64*)(&f);
x++;

是的,认真的。

编辑:正如有人指出的那样,这不能正确处理 -ve 数字、Inf、Nan 或溢出。上述更安全的版本是

u64 &x = *(u64*)(&f);
if( ((x>>52) & 2047) != 2047 )    //if exponent is all 1's then f is a nan or inf.

    x += f>0 ? 1 : -1;

【讨论】:

我想知道投反对票的人是否可以评论为什么这没有帮助......我自己,在了解了 nextafter() 函数后,我更喜欢那些,但如果这个可以工作,那么我认为它本身就值得注意。 Mike,运行此程序的包含文件/编译器是什么? 这完全依赖于实现且不可移植。如果你有 EEMMM 可以正常工作,但如果你有 MMMEE 不会给你想要的结果。 未定义行为,违反严格的别名规则 这也不处理-0。【参考方案3】:

绝对而言,您可以添加到浮点值以生成新的不同值的最小数量将取决于该值的当前大小;它将是类型的machine epsilon 乘以当前指数。

查看IEEE spec 以了解浮点表示。最简单的方法是将值重新解释为整数类型,加 1,然后通过检查符号位和指数位检查(如果您关心的话)您是否没有翻转符号或生成 NaN。

或者,您可以使用frexp 获取当前尾数和指数,从而计算要添加的值。

【讨论】:

【参考方案4】:

我需要做同样的事情并想出了这段代码:

double DoubleIncrement(double value)

  int exponent;
  double mantissa = frexp(value, &exponent);
  if(mantissa == 0)
    return DBL_MIN;

  mantissa += DBL_EPSILON/2.0f;
  value = ldexp(mantissa, exponent);
  return value;

【讨论】:

【参考方案5】:

不管怎样,标准 ++ 递增不再起作用的值是 9,007,199,254,740,992。

【讨论】:

【参考方案6】:

这可能不是您想要的,但您仍然可能会发现 numeric_limits 正在使用中。特别是成员 min() 和 epsilon()。

我不相信像 mydouble + numeric_limits::epsilon() 这样的东西会做你想做的事,除非 mydouble 已经接近 epsilon。如果是,那么你很幸运。

【讨论】:

【参考方案7】:

我不久前发现了这段代码,也许它可以帮助您确定可以向上推的最小值,然后将其增加该值。不幸的是,我不记得此代码的参考:

#include <stdio.h>

int main()

    /* two numbers to work with */
    double number1, number2;    // result of calculation
    double result;
    int counter;        // loop counter and accuracy check

    number1 = 1.0;
    number2 = 1.0;
    counter = 0;

    while (number1 + number2 != number1) 
        ++counter;
        number2 = number2 / 10;
    
    printf("%2d digits accuracy in calculations\n", counter);

    number2 = 1.0;
    counter = 0;

    while (1) 
        result = number1 + number2;
        if (result == number1)
            break;
        ++counter;
        number2 = number2 / 10.0;
    

    printf("%2d digits accuracy in storage\n", counter );

    return (0);

【讨论】:

以上是关于如何通过最小增量(或接近它)改变浮点数?的主要内容,如果未能解决你的问题,请参考以下文章

获得接近 2 次幂数的快速方法(浮点数)

从数组浮点数中选择最小值(最低)或最大值(最大值)

元组列表(字符串,浮点数)与 NaN 如何获得最小值?

浮点数的存储与表达

java浮点数常量是啥

有理数到浮点数