计算两个数字之间差异的最短方法?

时间:2012-05-14 19:08:00

标签: c++ math

我即将在C ++中这样做,但我不得不用几种语言来做,这是一个相当普遍和简单的问题,这是最后一次。我已经足够像我一样编码了,我确信必须有更好的方法,所以在我用另一种语言写出相同的长卷曲方法之前我就在这里发帖;

考虑以下代码( lilies!);

// I want the difference between these two values as a positive integer
int x = 7
int y = 3
int diff;
// This means you have to find the largest number first 
// before making the subtract, to keep the answer positive
if (x>y) { 
     diff = (x-y);
} else if (y>x) {
     diff = (y-x);
} else if (x==y) {
    diff = 0;
}

这可能听起来很小但对我而言似乎很重要,只是为了得到两个数字之间的区别。这实际上是一种完全合理的做事方式吗?我是不必要的迂腐,还是我的狡猾感觉有充分的理由刺痛?

5 个答案:

答案 0 :(得分:42)

获得差异的绝对值:

#include <cstdlib>
int diff = std::abs(x-y);

答案 1 :(得分:23)

使用std::abs()功能是一种明确的方法,正如其他人所建议的那样。

但也许你有兴趣在没有库调用的情况下简洁地编写这个函数。

在那种情况下

diff = x > y ? x - y : y - x;

是一条很短的路。

在您的评论中,您建议您对速度感兴趣。在这种情况下,您可能对执行此操作的方法感兴趣,而不需要branchingThis link描述了一些。

答案 2 :(得分:13)

#include <cstdlib>

int main()
{
    int x = 7;
    int y = 3;
    int diff = std::abs(x-y);
}

答案 3 :(得分:2)

这取决于你最短的意思。紧固运行时,最快的编译,最少的行数,最少的内存量。我假设你的意思是运行时间。

#include <algorithm>    // std::max/min   
int diff = std::max(x,y)-std::min(x,y);

这进行了两次比较和一次操作(这一次是不可避免的,但可以通过特定情况下的某些按位操作进行优化,但编译器实际上可能会为您执行此操作)。此外,如果编译器足够智能,它只能进行一次比较并保存结果以进行其他比较。例如,如果X> Y,则从第一次比较中您知道Y&lt; X但我不确定编译器是否会利用这一点。

答案 4 :(得分:2)

所有现有答案都将在极端输入时溢出,从而得到undefined behaviour。 @craq在评论中指出了这一点。

如果您知道自己的值将落在一个狭窄的范围内,则可以按照其他答案的建议进行操作,但是要处理极端的输入(即,稳健地处理任何个可能的输入值) ,您不能简单地减去值然后应用std::abs函数。正如craq正确指出的那样,减法可能溢出,从而导致不确定的行为(考虑INT_MIN - 1),而std::abs调用也可能导致不确定的行为(考虑std::abs(INT_MIN))。确定对的最小和最大值然后执行减法是更好的选择。

更一般而言,signed int无法表示两个signed int值之间的最大差。 unsigned int类型应用作输出值。

我看到3个解决方案。我在这里使用stdint.h中显式大小的整数类型,以关闭不确定性,例如longint是否具有相同的大小和范围。

解决方案1。是底层方法。

// I'm unsure if it matters whether our target platform uses 2's complement,
// due to the way signed-to-unsigned conversions are defined in C and C++:
// >  the value is converted by repeatedly adding or subtracting
// >  one more than the maximum value that can be represented
// >  in the new type until the value is in the range of the new type
uint32_t difference_int32(int32_t i, int32_t j) {
    static_assert(
        (-(int64_t)INT32_MIN) == (int64_t)INT32_MAX + 1,
        "Unexpected numerical limits. This code assumes two's complement."
    );

    // Map the signed values across to the number-line of uint32_t.
    // Preserves the greater-than relation, such that an input of INT32_MIN
    // is mapped to 0, and an input of 0 is mapped to near the middle
    // of the uint32_t number-line.
    // Leverages the wrap-around behaviour of unsigned integer types.

    // It would be more intuitive to set the offset to (uint32_t)(-1 * INT32_MIN)
    // but that multiplication overflows the signed integer type,
    // causing undefined behaviour. We get the right effect subtracting from zero.
    const uint32_t offset = (uint32_t)0 - (uint32_t)(INT32_MIN);
    const uint32_t i_u = (uint32_t)i + offset;
    const uint32_t j_u = (uint32_t)j + offset;

    const uint32_t ret = (i_u > j_u) ? (i_u - j_u) : (j_u - i_u);
    return ret;
}

我尝试使用https://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax的位纠缠技巧来对此进行变体,但是现代代码生成器似乎使用这种变体生成更差的代码。 (我删除了static_assert和评论。)

uint32_t difference_int32(int32_t i, int32_t j) {
    const uint32_t offset = (uint32_t)0 - (uint32_t)(INT32_MIN);
    const uint32_t i_u = (uint32_t)i + offset;
    const uint32_t j_u = (uint32_t)j + offset;

    // Surprisingly it helps code-gen in MSVC 2019 to manually factor-out
    // the common subexpression. (Even with optimisation /O2)
    const uint32_t t = (i_u ^ j_u) & -(i_u < j_u);
    const uint32_t min = j_u ^ t; // min(i_u, j_u)
    const uint32_t max = i_u ^ t; // max(i_u, j_u)
    const uint32_t ret = max - min;
    return ret;
}

解决方案2。简单方法。通过使用更宽的带符号整数类型进行工作来避免溢出。如果输入的有符号整数类型是可用的最大有符号整数类型,则无法使用此方法。

uint32_t difference_int32(int32_t i, int32_t j) {
    return (uint32_t)std::abs((int64_t)i - (int64_t)j);
}

解决方案3。。费力的方式。使用流控制来处理不同的情况。可能效率较低。

uint32_t difference_int32(int32_t i, int32_t j)
{   // This static assert should pass even on 1's complement.
    // It's just about impossible that int32_t could ever be capable of representing
    // *more* values than can uint32_t.
    // Recall that in 2's complement it's the same number, but in 1's complement,
    // uint32_t can represent one more value than can int32_t.
    static_assert( // Must use int64_t to subtract negative number from INT32_MAX
        ((int64_t)INT32_MAX - (int64_t)INT32_MIN) <= (int64_t)UINT32_MAX,
        "Unexpected numerical limits. Unable to represent greatest possible difference."
    );

    uint32_t ret;
    if (i == j) {
        ret = 0;
    } else {

        if (j > i) { // Swap them so that i > j
            const int32_t i_orig = i;
            i = j;
            j = i_orig;
        } // We may now safely assume i > j

        uint32_t magnitude_of_greater; // The magnitude, i.e. abs()
        bool     greater_is_negative; // Zero is of course non-negative
        uint32_t magnitude_of_lesser;
        bool     lesser_is_negative;

        if (i >= 0) {
            magnitude_of_greater = i;
            greater_is_negative = false;
        } else { // Here we know 'lesser' is also negative, but we'll keep it simple
            // magnitude_of_greater = -i; // DANGEROUS, overflows if i == INT32_MIN.
            magnitude_of_greater = (uint32_t)0 - (uint32_t)i;
            greater_is_negative = true;
        }

        if (j >= 0) {
            magnitude_of_lesser = j;
            lesser_is_negative = false;
        } else {
            // magnitude_of_lesser = -j; // DANGEROUS, overflows if i == INT32_MIN.
            magnitude_of_lesser = (uint32_t)0 - (uint32_t)j;
            lesser_is_negative = true;
        }

        // Finally compute the difference between lesser and greater
        if (!greater_is_negative && !lesser_is_negative) {
            ret = magnitude_of_greater - magnitude_of_lesser;
        } else if (greater_is_negative && lesser_is_negative) {
            ret = magnitude_of_lesser - magnitude_of_greater;
        } else { // One negative, one non-negative. Difference is sum of the magnitudes.
            // This will never overflow.
            ret = magnitude_of_lesser + magnitude_of_greater;
        }
    }
    return ret;
}