【问题标题】:Range analysis of floating point values?浮点值的范围分析?
【发布时间】:2014-06-02 23:57:45
【问题描述】:

我有一个使用浮点计算的图像处理程序。但是,我需要将它移植到不支持浮点的处理器。因此,我必须更改程序以使用定点计算。为此,我需要适当缩放这些浮点数,为此我需要知道所有值的范围,包括浮点计算的中间值。

有没有一种方法可以让我只运行程序并自动为我提供程序中所有浮点计算的范围?尝试手动计算范围太麻烦了,所以如果有一些工具可以做到这一点,那就太棒了!

【问题讨论】:

  • 你不能使用软浮点实现吗?我不知道你要的这种工具。

标签: c++ c image-processing floating-point fixed-point


【解决方案1】:

对于自包含的 C 程序,您可以使用 Frama-C's value analysis 获取浮点变量的范围,例如下面的 h

而变量gh 计算得出:

有一种规范语言来描述输入的范围(没有这些信息就很难说出任何信息)。在上面的示例中,我使用该语言来指定 float_interval 期望执行的函数:

/*@ ensures \is_finite(\result) && l <= \result <= u ; */
float float_interval(float l, float u);

Frama-C 最容易在 Linux 上安装,它带有 Debian 和 Ubuntu 二进制包,可从发行版中获得最新(但通常不是最新)版本。

如果您可以发布您的代码,这将有助于判断这种方法是否现实。例如,如果您的代码是 C++(您的问题没有说,它带有多个语言标签),那么当前版本的 Frama-C 将无济于事,因为它只接受 C 程序。

【讨论】:

  • 太好了。我的理解是,您的工具能够在不运行程序的情况下计算范围,即通过静态分析/抽象解释。您展示了单个变量的示例,但是否也可以针对范围集中考虑程序中的所有浮点变量。其次,能否也指定变量的输入范围?输入是指初始化变量的值。
  • @MetallicPriest 我说明了如何通过更改我的答案中的示例来处理您的第二个问题,现在包括对函数 float_interval 的调用,分析器只知道其中的描述 \is_finite(\result) &amp;&amp; l &lt;= \result &lt;= u
  • @MetallicPriest 对于您的第一个问题,我必须指出,与 iavr 的提议相比,这种方法的一个优点是您可以观察每次计算的范围并使用最佳表示对于每个。例如,如果您的程序将 [1.0 .. 256.0] 范围内的两个数字相乘,它将帮助您推断出 8Q8 * 8Q8 -> 16Q0 乘法是最好的。目前描述的 iavr 的提案只会告诉你一些数字上升到 65025。
  • @MetallicPriest 但是,如果您真的想要所有变量的统一表示,则可以通过编程方式迭代所有状态和变量,并收集适用于所有变量的上下界程序。一种方法是编写自己的 Frama-C 插件。价值分析插件使其结果可供任何其他插件使用。 GUI 仅在我的屏幕截图中利用了这个界面。任何其他插件都可以通过相同的 API 访问相同的信息。
  • 谢谢帕斯卡。您的工具看起来非常令人印象深刻。令人惊讶的是,您甚至可以在不运行程序的情况下使用它做多少事情。我一定会试一试的。
【解决方案2】:

您可以使用一些“测量”替代您的浮动类型,按照这些思路 (live example):

template<typename T>
class foo
{
    T val;

    using lim = std::numeric_limits<int>;

    static int& min_val() { static int e = lim::max(); return e; }
    static int& max_val() { static int e = lim::min(); return e; }

    static void sync_min(T e) { if (e < min_val()) min_val() = int(e); }
    static void sync_max(T e) { if (e > max_val()) max_val() = int(e); }

    static void sync(T v)
    {
        v = std::abs(v);
        T e = v == 0 ? T(1) : std::log10(v);
        sync_min(std::floor(e)); sync_max(std::ceil(e));
    }

public:
    foo(T v = T()) : val(v) { sync(v); }
    foo& operator=(T v) { val = v; sync(v); return *this; }

    template<typename U> foo(U v) : foo(T(v)) {}
    template<typename U> foo& operator=(U v) { return *this = T(v); }

    operator T&() { return val; }
    operator const T&() const { return val; }

    static int min() { return min_val(); }
    static int max() { return max_val(); }
};

像这样使用

int main ()
{
    using F = foo<float>;
    F x;

    for (F e = -10.2; e <= 30.4; e += .2)
        x = std::pow(10, e);

    std::cout << F::min() << " " << F::max() << std::endl;  // -11 31
}

这意味着您需要为您的浮动类型(floatdouble)定义一个别名(例如,Float),并在整个程序中始终如一地使用它。这可能不方便,但最终可能会证明是有益的(因为这样您的程序就更通用了)。如果您的代码已经在浮动类型上进行了模板化,那就更好了。

在这个参数化之后,您可以通过将Float 定义为foo&lt;T&gt;T 来将您的程序切换到“测量”或“发布”模式,其中T 是您的floatdouble .

好处是您不需要外部工具,您自己的代码执行测量。不好的是,按照目前的设计,它不会捕获所有中间结果。为此,您必须在 foo 上定义所有(例如算术)运算符。这可以完成,但需要更多工作。

【讨论】:

  • 太棒了。正是由于这些令人敬畏的功能,我喜欢 C++ :)!
【解决方案3】:

您不能在不支持浮点的硬件上使用浮点代码是不正确的——编译器将提供软件例程来执行浮点运算——它们可能会相当慢——但如果它对你来说足够快的话应用,这是阻力最小的路径。

实现一个定点数据类型类并让其成员函数检测上溢/下溢作为调试选项可能是最简单的(因为否则检查会减慢您的代码)。

我建议你看看Anthony Williams' fixed-Point math C++ library。它在 C++ 中并定义了一个具有广泛功能和运算符重载的 fixed 类,因此只需将现有代码中的 floatdouble 替换为 fixed 即可在很大程度上使用它。它使用int64_t 作为底层整数数据类型,具有34 个整数位和28 个小数位(34Q28),因此适用于大约8 个小数位,并且比int32_t 范围更广。

它没有我建议的下/溢出检查,但它是您添加自己的一个很好的起点。

在 32 位 ARM 上,该库的执行速度比软件浮点快大约 5 倍,并且在性能上与 ARM 的 C 代码 VFP 单元相当。

请注意,此库中的 sqrt() 函数对于非常小的值的精度性能很差,因为它在可以保留的中间计算中丢失了低位。可以通过用我在this question 中提供的代码替换它来改进它。

【讨论】:

  • 是的,我知道编译器能够为浮点仿真生成代码,但就像你说的那样,它太慢了。这就是为什么,我更喜欢那里的定点。
  • @MetallicPriest:很公平,但你的问题并不清楚,
猜你喜欢
  • 2021-02-25
  • 2022-11-12
  • 1970-01-01
  • 1970-01-01
  • 2015-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多