【问题标题】:Return error code as double以双精度返回错误代码
【发布时间】:2015-03-17 20:44:11
【问题描述】:

我想知道我是否可以在 C 中以以下方式将错误代码返回为双精度:

double getValue()
{
    double ret = 0;

    if (error1)
    {
        return -1;
    }
    if (error2)
    {
        return -2;
    }
    return ret = ....;
}

int main(void)
{
    double val = getValue();

    if (-1 == val)
    {
        printf("getValue: error1\n")
        return -1;
    }
    else if (-2 == val)
    {
        printf("getValue: error2\n");
        return -2;
    }

    ......
    return 0;
}

所以当返回值 >= 0 时,它是可用于计算的正确值。当值小于零时发生错误。 当我将返回值与 -1 或 -2 进行比较时,我会遇到浮点比较问题吗?

【问题讨论】:

  • 没有。尽管如此,标志值通常不是一个好主意。考虑boost::variant<double, error_code>std::expected 喜欢这个提议:github.com/ptal/std-expected-proposal
  • 您的问题是关于 c 还是 c++?

标签: c++ c comparison point floating


【解决方案1】:

标志值是个坏主意。浮点标志值是双精度的,即使是双精度。

如果您使用的是 IEEE 双精度浮点值,则值 -1-2 完全可以表示为 doubles,并且比较明确。如果您仅复制double 或仅读取该值,则不会出现“魔术错误”。事实上,在传统的 2s 补码 32 位 ints 的系统上,每个 int 都可以精确地表示为 IEEE 双精度浮点值。

现在,您认为像x /3. * 3. 这样无关紧要的转换会破坏身份,因此代码非常脆弱:脆弱既是因为标志值是脆弱的,也是因为浮点等价通常是在实践中很脆弱。

在 C++ 中,有无数种方法可以做到这一点,但不那么脆弱。

enum error_code {a,b,c};
boost::variant<double, error_code> getValue();

是一个标记的联合,可以包含doubleerror_code。有一个 std::expected 提案,您可以查看它是一个标记联合,其中第一个值是唯一有效的值(有点像 std::experimental::optionalboost::variant 之间的交叉)。

这两种方法都会以类型安全的方式返回值,其中错误是与非错误返回类型不同的类型值。

替代解决方案包括单独返回错误代码(作为返回值,或将指向错误代码的指针作为参数(我称之为 ICU 风格))。在这种情况下,double 可以设置为一些无害的值(例如,NaN),而不是未初始化。

double getValue( error_code* e );

error_code getValue( double* out );

其中enum error_code { a = -1, b = -2 } 是错误代码的枚举。

【讨论】:

  • 非常棒,除了我认为“不会出现‘魔法错误’”的一件事,考虑到您在下一段中介绍的内容,这可能会产生误导。
  • @LightnessRacesinOrbit 好吧,这不是“魔术”。它发生在定义明确的操作中。我想明确地说一些安全操作是个好主意:特别是,复制是安全的,阅读是安全的。
  • 如果你想使用你最初的想法,那么我会返回 -1.1、-2.1、-3.1 等。对于任何整数,此返回值将始终在 (n,n+0.5) 范围内n 转换为其对应的双精度值。
【解决方案2】:

@LightnessRacesinOrbit 打败了我,但我还是输入了它。

您可以通过将要设置的值作为指针参数并返回状态来实现。这样一来,*ret 的值就不会被禁止。

int getValue(double *ret)
{
    *ret = ...;

    if (error1)
        return -1;
    if (error2)
        return -2;

    return 0;
}

那么调用代码可以如

double myval;
int err;
if ((err = getValue(&myval)) == 0)
    printf ("getValue() returned %f\n", myval);
else
    printf ("getValue() returned error %d\n", err);

【讨论】:

  • 我必须更精确。我不能使用 int 代替 double。所以让我们假设在 C/C++ 语言中进行以下比较: double a = -1; if (-1 == a) { printf("a=-1\n");所以我在代码的一个地方将 -1 设置为双变量,以表明发生了错误,而在代码的另一个地方我想检查一下。 “if”语句会按预期工作吗?感谢您的帮助。
  • @emba 你得到 两个 值。您提供的double *ret 作为函数参数和状态,即函数返回值。我更新了答案。
【解决方案3】:

是的,您可能会遇到浮点错误。

因此请考虑改用异常,或者可能返回 int 错误代码并在成功时填充 double“输出参数”:

int getValue(double& ret)
{
    if (error1)
        return -1;
    if (error2)
        return -2;

    ret = ....;
    return 0;
}

【讨论】:

  • 您是在假设一个奇怪的浮点系统,还是 IEEE 双精度浮点值?因为-1-2 可以完美地表示为 IEEE 双精度浮点值。
  • @Yakk 我什么都不假设。依赖任何double 的严格相等检查是愚蠢的。当然,在这个特定的、精确的例子中,没有“计算”可言。但是谁知道将来会发生什么变化呢?如果他开始根据小数计算错误代码怎么办?无论您意味着的值如何,错误都可能潜入,最终得到完美的表现。
  • 不过,我不打算在这里详细介绍它;随意写下你自己的答案!
  • 完成!顺便说一句,double&amp; ret=0?它们是一个有趣的参考。 :)
【解决方案4】:

这样做是不必要的,并且会使错误处理变得困难,您应该创建一个enum,您可以在其中根据需要添加或删除错误代码,而且您实际上不需要记住-1 是什么或做什么-2 意思是,给每个错误一个描述性的名称,然后这样做

enum ErrorCodes {NoError, Error1, Error2, ... , ErrorN};
enum ErrorCodes getValue(double *value)
 {
    if (error1)
        return Error1;
    if (error2)
        return Error2;
    .
    .
    .
    if (errorN)
        return ErrorN;
    *value = resultOfCalculation;

    return NoError;
 }

然后

enum ErrorCode code;
double         value;

code = getValue(&value);
switch (code)
 {
    case NoError:
        /* use value here */
        break;
    case Error1:
        /* handle error 1 */
        break;
    /* and so on */
 }

我认为这更好更优雅,因为你可以在任何时候应用它来进行强大的错误检查,无论目标值是什么类型,这对于 struct 或 @ 来说都是一样的987654327@ 或 int 数组。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-17
    • 2012-01-08
    相关资源
    最近更新 更多