C/C++整数乘积的溢出问题的解决
作者:剑心诀
一、为什么会溢出?
整数乘积的溢出问题是指两个整数相乘得到的结果超过了所能表示的数据类型的范围。
在计算机中,整数的表示是有限的,即存在一个最大值和最小值。当进行乘法运算时,如果结果超出了整数的表示范围,就会发生溢出。这种情况下,计算结果将不再准确,并且可能导致数据丢失或错误的计算结果。
比如:int类型,C 语言标准规定了 int 类型必须至少能表示 -32767 到 32767 之间的整数,也就是说,int 类型的最小值和最大值范围为 -215 到 215-1。
一般而言,int 类型在 32 位操作系统上占用 4 个字节(32 位),在 64 位操作系统上占用 8 个字节(64 位)。使用 limits.h 头文件可以查看当前编译器中 int 类型的最大值和最小值。
#include<iostream> #include <limits.h> using namespace std; int main() { printf("INT_MIN: %d\n", INT_MIN); printf("INT_MAX: %d\n", INT_MAX); system("pause"); return 0; }
至于INT_MIN的值为什么是-2147483648,原因是:
在 32 位系统中,int 类型通常占据 4 个字节,即 32 位,而最小的带符号整数(-2^31)的二进制补码表示恰好对应于 -2147483648。
二、怎样解决?
可以使用更大范围的整数类型,比如 long long 或者 int64_t 等,以支持更大范围的数值计算。
根据取值范围,灵活选用整数类型:
C数据类型 | 最小值 | 最大值 |
---|---|---|
[signed] char | -128 | 127 |
unsigned char | 0 | 255 |
short | -32768 | 32767 |
unsigned short | 0 | 65535 |
int | -2 147 483 648 | 2 147 483 647 |
unsigned | 0 | 4 294 967 295 |
long | -2 147 483 648 | 2 147 483 647 |
unsigned long | 0 | 4 294 967 295 |
int32_t | -2 147 483 648 | 2 147 483 647 |
uin32_t | 0 | 4 294 967 295 |
int64_t | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 |
uint64_t | 0 | 18 446 744 073 709 551 615 |
32位程序上C语言整型数据类型的典型取值范围
C数据类型 | 最小值 | 最大值 |
---|---|---|
[signed] char | -128 | 127 |
unsigned char | 0 | 255 |
short | -32768 | 32767 |
unsigned short | 0 | 65535 |
int | -2 147 483 648 | 2 147 483 647 |
unsigned | 0 | 4 294 967 295 |
long | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 |
unsigned long | 0 | 18 446 744 073 709 551 615 |
int32_t | -2 147 483 648 | 2 147 483 647 |
uin32_t | 0 | 4 294 967 295 |
int64_t | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 |
uint64_t | 0 | 18 446 744 073 709 551 615 |
64位程序上C语言整型数据类型的典型取值范围
图中的注意事项:
取值范围不是对称的———负数的范围比整数的范围大1.当我们考虑如何表示负数的时候,会看到为什么会是这样子。
C数据类型 | 最小值 | 最大值 |
---|---|---|
[signed] char | -127 | 127 |
unsigned char | 0 | 255 |
short | -32767 | 32767 |
unsigned short | 0 | 65535 |
int | -32767 | 32767 |
unsigned | 0 | 65535 |
long | -2 147 483 647 | 2 147 483 647 |
unsigned long | 0 | 4 294 967 295 |
int32_t | -2 147 483 648 | 2 147 483 647 |
uin32_t | 0 | 4 294 967 295 |
int64_t | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 |
uint64_t | 0 | 18 446 744 073 709 551 615 |
C语言的整型数据类型的保证的取值范围。C语言标准要求这些数据类型必须至少具有这样的取值范围
C语言标准定义了每种数据类型必须能够表示的最小的取值范围。如上图所示,它的取值范围与32位和64位所示的典型实现一样或者小一些。特别地,除了固定大小的数据类型是例外,我们看到它们只要求正数和负数的取值范围是对称的。此外,数据类型int可以用2个字节的数字来实现。这几乎退到了16位机器的时代。还可以看到,long的大小可以用4个字节的数字来实现,对32位程序来说这是很典型的。固定大小的数据类型保证数值的范围与32位程序上C语言整型数据类型的典型取值范围一致。包括负数与正数的不对称性。
C语言支持多种整型数据类型——表示有限范围的整数。如图所示,其中给出了“典型”32位和64位机器的取值范围。每种类型都能用关键字来指定大小,这些关键字包括c h a r 、 s h o r t 、 l o n g char、short、longchar、short、long,同时还可以指示被表示的数字是非负数(声明为u n s i g n e d unsignedunsigned),或者可能是负数(默认即可)。为这些不同的大小分配的字节数可根据程序编译为32位gcc -m32 prog.c或者64位gcc -m64 prog.c而有所不同。根据字节分配,不同的大小所能表示的值的范围是不同的。特别注意,这里给出来的唯一一个与机器有关的取值范围是大小指示符long的。大多数64位的机器使用8个字节的表示。比32位机器上使用的4个字节的表示的取值范围大的多
补充:字数据大小
每台计算机都有一个字长(word size),指明指针数据的标称大小(nominal size)。
因为虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。也就是说,对于一个字长为w ww位的机器而言,虚拟地址的范围位0 − 2 w − 1 0 - 2^w-10−2w−1,程序最多访问2 w 2^w2w个字节
最近这些年,出现了大规模的从32位字长机器到64位字长机器的迁移。这种情况首先出现在为大型科学和数据库应用设计的高端机器上,之后是台式机和笔记本电脑,最近则出现在智能手机的处理器上。32位字长限制虚拟地址空间为4千兆字节(写作4GB),也就是说,刚刚超过4 × 1 0 9 4\times10^94×109个字节。扩展到64位字长使得虚拟地址空间为16EB,大约是1.84 × 1 0 19 1.84\times 10^{19}1.84×1019字节
基本c数据类型的典型大小(以字节为单位)。分配的字节数受如何编译的影响而变化。
三、看个例题
求 a 的 b 次方对 p 取模的值,其中 0≤a,b,c≤109, c>0
- 用c++写:
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll a,b,c; int main(){ cin >> a >> b >> c; ll ans = 1; while (b) { if(b&1) { ans = ans * a % c; } a = a * a % c; b /= 2; // 将指数右移一位 } cout << ans % c << endl; return 0; }
这里用long long类型来表示a,b,c三个数,防止整数溢出,同时用到了快速幂算法,防止两整数相乘的结果发生整数溢出。
快速幂算法通过对指数 y 进行二进制拆分,将指数的幂运算转化为多个底数的平方运算,从而减少了计算次数,提高了算法效率。
在 C 和 C++ 中,y & 1 表示对变量 y 的值和二进制数 1 进行按位与(AND)操作。具体来说,这个表达式会将 y 的二进制表示的最低位与 1 进行按位与运算,得到的结果为 0 或 1。
如果 y 的最低位是 1,那么 y & 1 的结果就是 1;如果 y 的最低位是 0,那么 y & 1 的结果就是 0。
这种操作通常用于判断一个整数是奇数还是偶数。因为二进制数的最低位为 1 表示奇数,为 0 表示偶数,所以 y & 1 可以快速判断 y 是奇数还是偶数。
- c的写法: (和c++只有一点点区别,文末补充)
#include<stdio.h> int main(){ long long a,b,c,d=1; scanf("%ld %ld %ld",&a,&b,&c); while(b){ if(b&1) d=d*a%c; a=a*a%c; b>>=1; } printf("%ld",d%c); }
- python的写法
a, b, c = map(int, input().split()) print(pow(a, b, c))
但是如果python以下写法,数值很大的时候就会发生溢出。
a, b, c = map(int, input().split()) ans = pow(a, b) % c print(ans)
虽然Python 中的整数类型是动态的,不会存在固定的最大值,但如果结果超出了 Python 能表示的范围,也会发生溢出。就比如pow(a, b) % c这一步,当a, b, c 很大的时候,也是会发生整数溢出。
但是python有个很方便的函数,就是pow()的用法
pow(a, b, c)
具体参数含义如下:
a:底数
b:指数
c:模数
这个函数返回值为 (a**b) % c 的结果。
这个函数返回值为 (a**b) % c 的结果。
例如,pow(2, 3, 5) 将返回 3,因为 2 的 3 次方是 8,然后对 5 取模的结果是 3。
pow(a, b, c)
函数在需要进行大数运算并对结果取模
的情况下非常有用。
四、补充:scanf和cin的区别
scanf()
是 C 语言中的输入函数,而 cin
是 C++ 中的输入流对象。它们之间有以下几个区别:
语言:
scanf()
是 C 语言的函数,而cin
是 C++ 的输入流对象。输入方式:
scanf()
是使用格式化字符串来指定输入格式,可以通过不同的格式说明符(如%d
、%f
、%s
等)来读取不同类型的数据。而cin
使用运算符重载和类型推断来直接从标准输入流中读取数据,不需要显式指定输入格式。错误处理:
scanf()
在读取输入时需要注意错误处理,因为它不能自动处理输入格式不匹配或类型不正确的情况。而cin
可以检测到输入类型不匹配等错误,并提供相应的错误处理机制,比如将输入流置于错误状态,清除错误标志等。输入缓冲:
scanf()
函数对输入的处理是基于缓冲区的,它会将输入数据读取到缓冲区中,然后根据格式字符串进行解析。而cin
对象则是基于流的输入,它会逐个字符地从输入流中读取并解析数据,不需要缓冲区。输入分隔符:
scanf()
默认以空格、制表符、换行符等作为输入分隔符,可以通过格式化字符串来指定不同的分隔符。而cin
默认以空格、制表符、换行符等作为输入分隔符,但它还提供了更灵活的方式来处理不同的输入分隔符。
总的来说,scanf()
是 C 语言中的函数,使用格式化字符串来指定输入格式,而 cin
是 C++ 中的输入流对象,使用运算符重载和类型推断来直接从标准输入流中读取数据,并提供更灵活的错误处理机制。两者在输入方式、错误处理、缓冲处理和输入分隔符上有所不同。
以上就是C/C++整数乘积的溢出问题的解决的详细内容,更多关于C/C++整数及乘积溢出的资料请关注脚本之家其它相关文章!