0

早些时候我想出了一些东西,我解决了,但后来让我看一个类似的例子:

int b = 35000000; //35million
int a = 30000000;
unsigned long n = ( 100 * a ) / b;

输出:4294967260

我只是更改aunsigned long,然后会出现正确的 85% 输出,因为a它是一个有符号的 32 位整数。但这让我后来。a期间没有赋值,( 100 * a )只是简单的计算,应该出现正确的值,即 30 亿,而不是溢出。要了解是否真的没有分配给a我,我a从代码中删除并改为手动写入值:

int b = 35000000;
unsigned long n = ( 100 * 30000000 ) / b;

最大的惊喜是输出也是: 4294967260
当然可以将 30 亿的值分配给unsigned long. 我的第一个想法是( 100 * 30000000 )导致溢出,但后来我问“溢出什么?没有什么可以溢出”。然后我改成b无符号长,甚至最令人惊讶的是输出正确率为 85%。

在第一个示例中更改aunsigned long

int b = 35000000;
unsigned long a = 30000000;
unsigned long n = ( 100 * a ) / b;

并保持b原样int工作,但在第二个例子中它没有,发生了什么?

让我用工作的和不工作的重新编写所有示例,这可能有点压倒性。

作品(输出 = 85):

int b = 35000000;
unsigned long a = 30000000;
unsigned long n = ( 100 * a ) / b;

作品(输出 = 85):

unsigned long b= 35000000;
unsigned long n = ( 100 * 30000000 ) / b;

不起作用(溢出):

int b = 35000000;
int a = 30000000;
unsigned long n = ( 100 * a ) / b;

不起作用(溢出):

int b = 35000000;
unsigned long n = ( 100 * 30000000 ) / b;
4

5 回答 5

3

让我解释一下这里发生了什么。
上:

int b= 35000000;
unsigned long n = ( 100 * 30000000 ) / b;

该值不正确,因为溢出发生在( 100 * 30000000 ) But on:

unsigned long b= 35000000;
unsigned long n = ( 100 * 30000000 ) / b;

值是正确的,那么发生了什么?

在第一个示例b中是 a int,正如 Tony 所说,发生溢出是因为( 100 * 30000000 )分配临时值 的寄存器能够保存 32 位有符号整数,这是因为 100 是 int 而 30000000 也是 int AND因为b是也是一个 int,这种情况下的寄存器是智能的,当右侧的所有int值都是时,它假定这些值也必须是 an,int但是当一个强大unsigned long的人参加聚会时,它知道将 an 除以 anintunsigned long错误的/ b,所以它将 的值存储( 100 * 30000000 )unsigned long

于 2012-09-19T03:26:47.873 回答
2

在 C++ 中,有一些称为“文字常量”的编程元素。

例如(取自这里):

157 //整数常量

0xFE //整数常量

'c' // 字符常量

0.2 // 浮动常量

0.2E-01 // 浮动常量

"dog" // 字符串字面量

所以,回到你的例子,将两个s100 * 30000000相乘。int这就是为什么会有溢出。任何时候对相同类型的操作数执行算术运算,都会得到相同类型的结果。此外,在代码片段unsigned long a = 30000000;中,您将获取一个整数常量并将其分配给type30000000的变量。aunsigned long

要获得所需的输出,ul请在末尾添加后缀:unsigned long n = ( 100ul * 30000000ul ) / b;.

这是一个对后缀进行解释的网站。

为什么 /b 当 b 是 unsigned long 时仍然是一个有趣的问题

因为在除以之前100 * 30000000执行,并且操作数都是 type 。bint

于 2012-09-19T03:06:44.320 回答
1

作品(输出 = 85):

unsigned long b= 35000000;
unsigned long n = ( 100 * 30000000 ) / b;

不在这里,使用:

#include <iostream>

int main() {
    unsigned long b= 35000000;
    unsigned long n = ( 100 * 30000000 ) / b;
    std::cout << n << std::endl;
    return 0;
}

输出为 527049830640 (即使使用默认警告级别,编译器也会警告溢出)。

关键是,正如Mark Ransom 已经写的那样,算术运算的类型取决于其操作数的类型。

常量 100int的类型是 ,常量 30000000 的类型也是如此(假设 32 位或更大int的 s,long int如果int是 16 位)。所以乘法是在 type 处执行的int,并且对于 32 位ints 它会溢出。溢出是未定义的行为,但环绕是该未定义行为的最常见表现,导致 value -1294967296。然后将乘法的结果转换为除法的类型b(因为它是无符号类型并且 - 在 C 术语中 - 它的整数转换等级不小于 的int)。

转换为无符号整数类型意味着减少模 2^WIDTH。如果 的宽度unsigned long为 32,则最后一次转换的结果为2^32 - 1294967296 = 3000000000,从而得到商 85。但如果 - 在我的系统上 - 的宽度unsigned long为 64 位,则该转换的结果为2^64 - 1294967296 = 18446744072414584320

于 2012-09-19T04:20:36.797 回答
1

32 位有符号整数可以表示的最大数不溢出是 2147483647。100*30000000 比这个大。

算术运算的类型完全独立于您将其存储到的变量的类型。它基于操作数的类型。如果两个操作数都是 type int,则结果也将是 type int,然后该结果将在存储到变量之前进行转换。

于 2012-09-19T03:18:29.103 回答
0

另一种常见的解决方案是在对其进行操作之前将其中一个常量类型转换为更大的结果类型。我更喜欢这种方法,因为不是每个人都记得所有可能的后缀。包括我自己。

在这种情况下,我会使用:

unsigned long n = ( (unsigned long)100 * 30000000 ) / b;

可悲的是,这是一件事汇编语言——是的,汇编语言——在 C、C++ 和许多其他语言中没有做到这一点:将M位整数乘以N位整数的结果是 ( M +N ) 位整数,而不是 (max( M , N )) 位整数。

编辑:马克提出了一个有趣的观点:编译器不会“向前看”到结果的存储位置以推断结果类型。因此,C++ 要求任何子表达式的结果本身是确定性的。换句话说,100 * 30000000总是可以在不查看任何其他代码的情况下确定确切的类型。

于 2012-09-19T03:16:50.430 回答