模运算似乎不适用于所有

时间:2016-01-31 01:17:40

标签: c cpu intel modulo modulus

所以......模运算似乎并不适用于所有的64位值。

这是设置边缘情况的C代码:

#include <stdio.h>

int main(int argc, char *argv[]) {
    long long max_ll =   0xFFFFFFFFFFFFFFFF;
    long long large_ll = 0x0FFFFFFFFFFFFFFF;
    long long mask_ll =  0x00000F0000000000;

    printf("\n64-bit numbers:\n");
    printf("0x%016llX\n", max_ll % mask_ll);
    printf("0x%016llX\n", large_ll % mask_ll);

    long max_l =   0xFFFFFFFF;
    long large_l = 0x0FFFFFFF;
    long mask_l =  0x00000F00;

    printf("\n32-bit numbers:\n");
    printf("0x%08lX\n", max_l % mask_l);
    printf("0x%08lX\n", large_l % mask_l);

    return 0;
}

输出显示:

64-bit numbers:
0xFFFFFFFFFFFFFFFF
0x000000FFFFFFFFFF

32-bit numbers:
0xFFFFFFFF
0x000000FF

这里发生了什么?

为什么不对所有1的64位值进行模运算,但它会在所有1的32位值上工作?

这是Intel CPU的一个错误吗?或者以某种方式与C?还是别的什么?

更多信息

我在装有Intel i5-4570S CPU的Windows 10计算机上。我使用了Visual Studio 2015中的cl编译器。

我还通过进入程序员模式使用Windows Calculator应用程序(版本10.1601.49020.0)验证了此结果。如果你尝试用任何东西模数0xFFFF FFFF FFFF FFFF,它就会自行返回。

指定unsigned vs signed didn似乎没有任何区别。

请赐教:)我实际上确实有这个操作的用例...所以它不是纯粹学术性的。

3 个答案:

答案 0 :(得分:3)

您的程序使用错误的格式说明符导致undefined behaviour

%llX只能用于unsigned long long。如果你使用正确的说明符%lld,那么明显的谜团就会消失:

#include <stdio.h>

int main(int argc, char* argv[])
{
    long long max_ll =   0xFFFFFFFFFFFFFFFF;
    long long mask_ll =  0x00000F0000000000;

    printf("%lld %% %lld = %lld\n", max_ll, mask_ll, max_ll % mask_ll);
}

Output:

-1 % 16492674416640 = -1

在ISO C中,%运算符的定义是(a/b)*b + a%b == a。此外,对于负数,/遵循“截断为零”。

因此-1 / 164926744166400,因此-1 % 16492674416640必须为-1才能使上述公式有效。

如评论中所述,以下一行:

long long max_ll =   0xFFFFFFFFFFFFFFFF;

导致实现定义的行为(假设您的系统具有long long作为64位类型)。常量0xFFFFFFFFFFFFFFFF的类型为unsigned long long,超出long long的范围,其最大允许值为0x7FFFFFFFFFFFFFFF

当对签名类型进行超出范围的赋值时,行为是实现定义的,这意味着编译器文档必须说明会发生什么。

通常,这将被定义为生成long long范围内的值,并且具有与unsigned long long常量相同的表示。在2的补码中,(long long)-1具有与unsigned long long0xFFFFFFFFFFFFFFFF相同的表示形式,这解释了为什么最终max_ll保留了值-1

答案 1 :(得分:2)

实际上确实会将这些值定义为signed还是unsigned

#include <stdio.h>
#include <limits.h>

int main(void) {
#if ULLONG_MAX == 0xFFFFFFFFFFFFFFFF
    long long max_ll =   0xFFFFFFFFFFFFFFFF;  // converts to -1LL
    long long large_ll = 0x0FFFFFFFFFFFFFFF;
    long long mask_ll =  0x00000F0000000000;

    printf("\n" "signed 64-bit numbers:\n");
    printf("0x%016llX\n", max_ll % mask_ll);
    printf("0x%016llX\n", large_ll % mask_ll);

    unsigned long long max_ull =   0xFFFFFFFFFFFFFFFF;
    unsigned long long large_ull = 0x0FFFFFFFFFFFFFFF;
    unsigned long long mask_ull =  0x00000F0000000000;

    printf("\n" "unsigned 64-bit numbers:\n");
    printf("0x%016llX\n", max_ull % mask_ull);
    printf("0x%016llX\n", large_ull % mask_ull);
#endif

#if UINT_MAX == 0xFFFFFFFF
    int max_l =   0xFFFFFFFF;  // converts to -1;
    int large_l = 0x0FFFFFFF;
    int mask_l =  0x00000F00;

    printf("\n" "signed 32-bit numbers:\n");
    printf("0x%08X\n", max_l % mask_l);
    printf("0x%08X\n", large_l % mask_l);

    unsigned int max_ul =   0xFFFFFFFF;
    unsigned int large_ul = 0x0FFFFFFF;
    unsigned int mask_ul =  0x00000F00;

    printf("\n" "unsigned 32-bit numbers:\n");
    printf("0x%08X\n", max_ul % mask_ul);
    printf("0x%08X\n", large_ul % mask_ul);
#endif
    return 0;
}

生成此输出:

signed 64-bit numbers:
0xFFFFFFFFFFFFFFFF
0x000000FFFFFFFFFF

unsigned 64-bit numbers:
0x000000FFFFFFFFFF
0x000000FFFFFFFFFF

signed 32-bit numbers:
0xFFFFFFFF
0x000000FF

unsigned 32-bit numbers:
0x000000FF
0x000000FF

64位十六进制常量0xFFFFFFFFFFFFFFFF在存储到-1时具有值long long。这实际上是由于超出范围转换为签名类型而定义的实现,但在英特尔处理器上,对于当前编译器,转换只保留相同的位模式。

请注意,您没有使用<stdint.h>中定义的固定大小整数:int64_tuint64_tint32_tuint32_tlong long类型在标准中指定为具有至少64位,在Intel x86_64上,它们具有,long具有至少32位,但对于相同的处理器,大小在环境之间不同: Windows 10中的32位(即使在64位模式下)和MaxOS / 10和linux64上的64位。这就是为什么long unsignedsigned可能会产生相同结果的LLONG_MIN / -1情况的惊人行为的原因。它们不在Windows上,但它们在Linux和MacOS中运行,因为计算是以64位完成的,这些值只是正数。

另请注意,由于带符号的算术溢出,LLONG_MIN % -11 / 0都会调用未定义的行为,并且在Intel PC上不会忽略此行为,它通常会触发未捕获的异常并退出程序,就像1 % 0/** * @param uniqueList * @param permutationSize * @param permutation * @param only Only show the permutation of permutationSize, * else show all permutation of less than or equal to permutationSize. */ public static void my_permutationOf(List<Integer> uniqueList, int permutationSize, List<Integer> permutation, boolean only) { if (permutation == null) { assert 0 < permutationSize && permutationSize <= uniqueList.size(); permutation = new ArrayList<>(permutationSize); if (!only) { System.out.println(Arrays.toString(permutation.toArray())); } } for (int i : uniqueList) { if (permutation.contains(i)) { continue; } permutation.add(i); if (!only) { System.out.println(Arrays.toString(permutation.toArray())); } else if (permutation.size() == permutationSize) { System.out.println(Arrays.toString(permutation.toArray())); } if (permutation.size() < permutationSize) { my_permutationOf(uniqueList, permutationSize, permutation, only); } permutation.remove(permutation.size() - 1); } }

答案 2 :(得分:1)

尝试将unsigned放在long long之前。作为带符号的数字,你的0xFF ... FF在大多数平台上实际为-1。

此外,在您的代码中,您的32位数字仍然是64位(您也将它们声明为long long)。