在 C 中如何安全地找到 2 个有符号整数之间的绝对差?

use*_*248 5 c math absolute-value difference

绝对差是两个数字之间差的绝对值。假设我有 2 个int变量(xy),我想找到绝对差。一个简单的解决方案是:

unsigned diff = abs(x-y);
Run Code Online (Sandbox Code Playgroud)

然而,如果发生溢出(例如 if xisINT_MINyis ) ,这些会调用未定义的行为并给出不正确的结果INT_MAX。这会返回1(假设环绕行为)而不是按UINT_MAX预期返回。

Gab*_*les 8

好的,下面的工作。@user16217248 让我开始了。请参阅该答案下的讨论。

如何安全高效地寻找abs((int)num1 - (int)num2)

/// Safely and efficiently return `abs((int)num1 - (int)num2)`
unsigned int abs_num1_minus_num2_int(int num1, int num2)
{
    unsigned int abs_diff = num1 > num2 ?
        (unsigned int)num1 - (unsigned int)num2 :
        (unsigned int)num2 - (unsigned int)num1;

    return abs_diff;
}
Run Code Online (Sandbox Code Playgroud)

秘诀是num1 > num2有符号整数值进行三元比较,然后重新解释将它们转换为无符号整数值,以便在获取 的绝对值时允许明确定义的上溢和下溢行为num1 - num2

这是我的完整测试代码:

Absolute_value_of_num1_minus_num2.c来自我的eRCaGuy_hello_world存储库:

///usr/bin/env ccache gcc -Wall -Wextra -Werror -O3 -std=gnu17 "$0" -o /tmp/a -lm && /tmp/a "$@"; exit
// For the line just above, see my answer here: https://stackoverflow.com/a/75491834/4561887

#include <limits.h>
#include <stdbool.h> // For `true` (`1`) and `false` (`0`) macros in C
#include <stdint.h>  // For `uint8_t`, `int8_t`, etc.
#include <stdio.h>   // For `printf()`


#define TEST_EQ(func, num1, num2, equals) \
    printf("%s\n", func((num1), (num2)) == (equals) ? "Passed" : "FAILED")

/// Safely and efficiently return `abs((int8_t)num1 - (int8_t)num2)`
uint8_t abs_num1_minus_num2_int8(int8_t num1, int8_t num2)
{
    // Note: due to implicit type promotion rules, rule 2 in my answer here
    // (https://stackoverflow.com/a/72654668/4561887) applies, and both the `>`
    // comparison, as well as subtraction, take place below as `int` types.
    // While signed `int` overflow and underflow is undefined behavior, none of
    // that occurs here.
    // - It's just useful to understand that even though we are doing
    //   `(uint8_t)num1 -(uint8_t)num2`, the C compiler really sees it as this:
    //   `(int)(uint8_t)num1 - (int)(uint8_t)num2`.
    // - This is because all small types smaller than `int`, such as `uint8_t`,
    //   are first automatically implicitly cast to `int` before any
    //   mathematical operation or comparison occurs.
    // - The C++ compiler therefore sees it as this:
    //   `static_cast<int>(static_cast<unsigned char>(num1)) - static_cast<int>(static_cast<unsigned char>(num2))`.
    //   - Run this code through https://cppinsights.io/ to see that.
    //     See here: https://cppinsights.io/s/bfc425f6 --> and click the play
    //     button.
    uint8_t abs_diff = num1 > num2 ?
        (uint8_t)num1 - (uint8_t)num2 :
        (uint8_t)num2 - (uint8_t)num1;

    // debugging
    printf("num1 = %4i (%3u); num2 = %4i (%3u); num1-num2=%3u;  ",
        num1, (uint8_t)num1, num2, (uint8_t)num2, abs_diff);

    return abs_diff;
}

/// Safely and efficiently return `abs((int)num1 - (int)num2)`
unsigned int abs_num1_minus_num2_int(int num1, int num2)
{
    unsigned int abs_diff = num1 > num2 ?
        (unsigned int)num1 - (unsigned int)num2 :
        (unsigned int)num2 - (unsigned int)num1;

    // debugging
    printf("num1 = %11i (%10u); num2 = %11i (%10u); num1-num2=%10u;  ",
        num1, (unsigned int)num1, num2, (unsigned int)num2, abs_diff);

    return abs_diff;
}


int main()
{
    printf("Absolute difference tests.\n");

    // ---------------
    // int8_t types
    // ---------------

    int8_t num1_8;
    int8_t num2_8;

    printf("\n");
    printf("INT8_MIN = %i, INT8_MAX = %i\n", INT8_MIN, INT8_MAX); // -128, 127

    num1_8 = -7;
    num2_8 = -4;
    TEST_EQ(abs_num1_minus_num2_int8, num1_8, num2_8, 3);

    num1_8 = INT8_MIN;
    num2_8 = INT8_MAX;
    TEST_EQ(abs_num1_minus_num2_int8, num1_8, num2_8, UINT8_MAX);

    num1_8 = INT8_MAX;
    num2_8 = INT8_MIN;
    TEST_EQ(abs_num1_minus_num2_int8, num1_8, num2_8, UINT8_MAX);

    num1_8 = 100;
    num2_8 = 10;
    TEST_EQ(abs_num1_minus_num2_int8, num1_8, num2_8, 90);

    num1_8 = 10;
    num2_8 = 100;
    TEST_EQ(abs_num1_minus_num2_int8, num1_8, num2_8, 90);

    num1_8 = 10;
    num2_8 = 10;
    TEST_EQ(abs_num1_minus_num2_int8, num1_8, num2_8, 0);

    num1_8 = INT8_MAX;
    num2_8 = 1;
    TEST_EQ(abs_num1_minus_num2_int8, num1_8, num2_8, 126);

    num1_8 = 1;
    num2_8 = INT8_MAX;
    TEST_EQ(abs_num1_minus_num2_int8, num1_8, num2_8, 126);

    // ---------------
    // int types
    // ---------------

    int num1;
    int num2;

    printf("\n");
    printf("INT_MIN = %i, INT_MAX = %i\n", INT_MIN, INT_MAX); // -2147483648, 2147483647

    num1 = -7;
    num2 = -4;
    TEST_EQ(abs_num1_minus_num2_int, num1, num2, 3);

    num1 = INT_MIN;
    num2 = INT_MAX;
    TEST_EQ(abs_num1_minus_num2_int, num1, num2, UINT_MAX);

    num1 = INT_MAX;
    num2 = INT_MIN;
    TEST_EQ(abs_num1_minus_num2_int, num1, num2, UINT_MAX);

    num1 = 100;
    num2 = 10;
    TEST_EQ(abs_num1_minus_num2_int, num1, num2, 90);

    num1 = 10;
    num2 = 100;
    TEST_EQ(abs_num1_minus_num2_int, num1, num2, 90);

    num1 = 10;
    num2 = 10;
    TEST_EQ(abs_num1_minus_num2_int, num1, num2, 0);

    num1 = INT_MAX;
    num2 = 1;
    TEST_EQ(abs_num1_minus_num2_int, num1, num2, 2147483646);

    num1 = 1;
    num2 = INT_MAX;
    TEST_EQ(abs_num1_minus_num2_int, num1, num2, 2147483646);


    return 0;
}
Run Code Online (Sandbox Code Playgroud)

示例运行和输出:

eRCaGuy_hello_world/c$ ./absolute_value_of_num1_minus_num2.c
Absolute difference tests.

INT8_MIN = -128, INT8_MAX = 127
num1 =   -7 (249); num2 =   -4 (252); num1-num2=  3;  Passed
num1 = -128 (128); num2 =  127 (127); num1-num2=255;  Passed
num1 =  127 (127); num2 = -128 (128); num1-num2=255;  Passed
num1 =  100 (100); num2 =   10 ( 10); num1-num2= 90;  Passed
num1 =   10 ( 10); num2 =  100 (100); num1-num2= 90;  Passed
num1 =   10 ( 10); num2 =   10 ( 10); num1-num2=  0;  Passed
num1 =  127 (127); num2 =    1 (  1); num1-num2=126;  Passed
num1 =    1 (  1); num2 =  127 (127); num1-num2=126;  Passed

INT_MIN = -2147483648, INT_MAX = 2147483647
num1 =          -7 (4294967289); num2 =          -4 (4294967292); num1-num2=         3;  Passed
num1 = -2147483648 (2147483648); num2 =  2147483647 (2147483647); num1-num2=4294967295;  Passed
num1 =  2147483647 (2147483647); num2 = -2147483648 (2147483648); num1-num2=4294967295;  Passed
num1 =         100 (       100); num2 =          10 (        10); num1-num2=        90;  Passed
num1 =          10 (        10); num2 =         100 (       100); num1-num2=        90;  Passed
num1 =          10 (        10); num2 =          10 (        10); num1-num2=         0;  Passed
num1 =  2147483647 (2147483647); num2 =           1 (         1); num1-num2=2147483646;  Passed
num1 =           1 (         1); num2 =  2147483647 (2147483647); num1-num2=2147483646;  Passed
Run Code Online (Sandbox Code Playgroud)

相邻相关

  1. 上面的“绝对减法”让我想起了您也可以使用整数数学来完成的“舍入除法”解决方案集。为此,请参阅我的其他答案:舍入整数除法(而不是截断)。我介绍了进行整数除法时的向上舍入、向下舍入和舍入到最接近的值。

也可以看看

  1. 我对C 和 C++ 中的隐式转换/升级以及整数和浮点排名和升级规则的回答
  2. https://cppinsights.io/ - 一个非常有用的工具,它将您的 C++ 代码扩展为编译器所看到的内容,包括在编译器中应用所有自动隐式类型提升规则之后。
    1. 例如:请参阅上面的代码: https: //cppinsights.io/s/bfc425f6 --> 然后单击播放按钮将其转换并扩展为编译器所看到的内容。