【问题标题】:Qsort comparisonQsort比较
【发布时间】:2018-08-18 00:39:55
【问题描述】:

我正在将C++ 代码转换为Go,但是我很难理解这个比较函数:

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

using namespace std;

typedef struct SensorIndex
{   double value;
    int    index;
} SensorIndex;

int comp(const void *a, const void* b)
{   SensorIndex* x = (SensorIndex*)a;
    SensorIndex* y = (SensorIndex*)b;

    return abs(y->value) - abs(x->value);
}

int main(int argc , char *argv[])
{

    SensorIndex *s_tmp;

    s_tmp = (SensorIndex *)malloc(sizeof(SensorIndex)*200);

    double q[200] = {8.48359,8.41851,-2.53585,1.69949,0.00358129,-3.19341,3.29215,2.68201,-0.443549,-0.140532,1.64661,-1.84908,0.643066,1.53472,2.63785,-0.754417,0.431077,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256,-0.123256};

    for( int i=0; i < 200; ++i ) {
        s_tmp[i].value = q[i];
        s_tmp[i].index = i;
    }

    qsort(s_tmp, 200, sizeof(SensorIndex), comp);

    for( int i=0; i<200; i++)
    {   
        cout << s_tmp[i].index << " " << s_tmp[i].value << endl;
    }

}

我希望“comp”函数允许从最高(绝对)值到次要排序,但在我的环境(gcc 32 位)中,结果是:

1 8.41851
0 8.48359
2 -2.53585
3 1.69949
11 -1.84908
5 -3.19341
6 3.29215
7 2.68201
10 1.64661
14 2.63785
12 0.643066
13 1.53472
4 0.00358129
9 -0.140532
8 -0.443549
15 -0.754417
16 0.431077
17 -0.123256
18 -0.123256
19 -0.123256
20 -0.123256
...

此外,我觉得奇怪的一件事是,通过在线服务执行相同的代码,我得到不同的值(cpp.sh,C++98):

0 8.48359
1 8.41851
5 -3.19341
6 3.29215
2 -2.53585
7 2.68201
14 2.63785
3 1.69949
10 1.64661
11 -1.84908
13 1.53472
4 0.00358129
8 -0.443549
9 -0.140532
12 0.643066
15 -0.754417
16 0.431077
17 -0.123256
18 -0.123256
19 -0.123256
20 -0.123256
...

有什么帮助吗?

【问题讨论】:

  • 这不是 C++
  • @Slava 是的。它在我的 C++ 编译器上编译得非常好。
  • @Galik 不是 c++ 编译器可以编译的所有东西都是 c++。例如,大多数 C 代码都可以很好地编译
  • 他的意思是你所做的一切(除了cout)都是C,而不是C++。
  • @Slava TBF 至少有 using namespace std;cout 至少将代码限定为 C++ 而不是纯 C。

标签: go qsort


【解决方案1】:

此行为是由使用abs(一个与int 配合使用的函数)并传递double 参数引起的。 doubles 被隐式转换为 int,在比较它们之前截断小数部分。本质上,这意味着您取原始数字,去掉符号,然后去掉小数点右边的所有内容并比较这些值。所以8.123-8.9 都转换为8,并且比较相等。由于减法的输入是相反的,因此排序是按幅度降序排列的。

您的cpp.sh 输出反映了这一点;所有大小在 8 到 9 之间的值都首先出现,然后是 3-4s,然后是 2-3s、1-2s 和小于 1 的值。

如果您想修复此问题以实际按一般降序排序,您需要一个正确使用 the double-friendly fabs function 的比较函数,例如

int comp(const void *a, const void* b)
{   SensorIndex* x = (SensorIndex*)a;
    SensorIndex* y = (SensorIndex*)b;
    double diff = fabs(y->value) - fabs(x->value);
    if (diff < 0.0) return -1;
    return diff > 0;
}

更新: 进一步阅读,似乎&lt;cmath&gt;std::absdoubles 合作了很长时间,但只添加了doubles 的std::abs到 C++17 中的 &lt;cstdlib&gt;(其中整数 abs 函数驻留)。 And the implementers got this stuff wrong all the time, so different compilers would behave differently at random。无论如何,这里给出的两个答案都是正确的;如果您没有包含 &lt;cmath&gt; 并且您使用的是 C++17 之前的编译器,您应该只能访问基于整数的 std::abs 版本(或来自 @987654347 的 ::abs @),这将在比较之前截断每个值。即使您使用了正确的std::absreturning the result of double subtraction as an int would drop fractional components of the difference,也会使幅度差小于1.0 的任何值看起来相等。更糟糕的是,取决于执行的特定比较及其排序(因为并非所有值都相互比较),这种影响的后果可能会连锁,因为比较排序更改可能会使 1.0 看起来等于 1.6 反过来会出现等于2.5,即使1.0 将被正确识别为小于2.5,如果它们被相互比较;理论上,只要每个数字与其他数字相差在 1.0 以内,比较就可以评估为好像它们都彼此相等(病态的情况是的,但肯定会发生较小的此类错误运行)。

要点是,弄清楚这段代码的真实意图的唯一方法是弄清楚它最初编译的确切编译器版本和 C++ 标准,并在那里进行测试。

【讨论】:

  • 所以简而言之,原来的函数根本上是错误的,但仍然试图按照我的想法将元素从大到小排序?那么......我可以认为这是一个可怕的逻辑错误吗?
  • @Usin:我刚刚更新了答案。目前还不清楚 what 原始函数试图做什么。我最初描述的实现(在 C++98 下使用基于整数的abs 看到)在内部是一致的,但很奇怪(按绝对值的整数分量一致地分组)。如果它是用double 友好的std::abs 编译的,由于Galik 指出的问题,这将是非常非常错误的,以可怕和不可预测的方式。在这两种情况下,都不会有从最高到最低幅度的可靠绝对排序。
  • @Galik 的建议替换使用 std::sort 和 lambda 函数将在 C++17 上获得从最高到最低幅度的可靠排序,如果您使用 #include-ed &lt;cstdlib&gt; 和 C+ +11 或更高,如果您包含 &lt;cmath&gt;;在所有情况下,为避免歧义,您都需要明确命名空间限定 std::abs,以避免与 C 中的 abs 函数(仅适用于 int)混淆。
  • 感谢您的回答,我正在使用 C++17 编译器,但肯定最初使用的是 C++17 之前的编译器(代码来自 2011-2012),所以,除非有意它没有截断小数(我不相信),我被引导认为这是一个逻辑错误,并且由于此函数在更复杂的系统中使用,因此错误也可能在输出中没有明显影响的情况下逃脱。
【解决方案2】:

您的比较功能存在错误。您返回一个int,这意味着您失去绝对差异小于1的元素值之间的区别

int comp(const void* a, const void* b)
{
    SensorIndex* x = (SensorIndex*)a;
    SensorIndex* y = (SensorIndex*)b;

    // what about differences between 0.0 and 1.0?
    return abs(y->value) - abs(x->value); 
}

你可以这样修复它:

int comp(const void* a, const void* b)
{   SensorIndex* x = (SensorIndex*)a;
    SensorIndex* y = (SensorIndex*)b;

    if(std::abs(y->value) < std::abs(x->value))
        return -1;

    return 1;
}

一种更现代(更安全)的方法是使用std::vectorstd::sort

// use a vector for dynamic arrays
std::vector<SensorIndex> s_tmp;

for(int i = 0; i < 200; ++i) {
    s_tmp.push_back({q[i], i});
}

// use std::sort
std::sort(std::begin(s_tmp), std::end(s_tmp), [](SensorIndex const& a, SensorIndex const& b){

    return std::abs(b.value) < std::abs(a.value);
});

【讨论】:

  • abs 在他们还没有返回任何东西之前就破坏了这种区别; abs accepts and returns ints, not doubles,因此数据在进行比较之前就被截断了。你的代码没有解决这个问题,因为8.18.9 都会被abs 转换为8,所以它们看起来仍然是相等的。
  • @ShadowRanger 好的,它对我有用,因为在 C++17 std::abs 对于双打来说已经超载了......!
  • @ShadowRanger 实际上在 C++11 中添加了浮点重载。
  • 重载是在 C++11 中添加的,但仅作为 cmath 的一部分;直到 C++17,它们才被添加为 cstdlib(函数的基于整数的版本来自)的一部分,所以如果你不小心包含它,唯一存在的 abs 是整数基于。在这种情况下,实施者并不总是仔细遵循规范so the whole thing is a giant mess,并且 OP 正在使用 C++98 进行测试,因此他们肯定处于“皇家混乱”时代。
  • 感谢您的回答,这是对@ShadowRanger 的有用和补充,但鉴于解释的详细信息,我接受他的回答。
猜你喜欢
  • 2014-10-10
  • 1970-01-01
  • 2016-09-08
  • 1970-01-01
  • 2018-03-26
  • 2015-05-22
  • 2019-07-01
  • 2019-09-03
相关资源
最近更新 更多