[深入浅出C语言]理解取整、取余和取模

article/2025/11/9 12:50:50

关于C语言的取模运算,也许你只了解正数取模,而从未接触过负数取模,本文就来分享一波C语言取整、取模和取余的个人学习经验与心得,希望对你有所帮助。

笔者水平有限,难免存在纰漏,欢迎指正交流。

关于取整

你真的了解取整么?那你知道有几种取整方式吗?

除了最常见的向0取整以外其实是是有很多的取整方式的。

向0取整

C语言中整数除法就是遵循这一取整方式。下面代码中浮点数字面量拷贝赋值时发生了隐式类型转换,向0取整。

#include<stdio.h>
int main()
{int i = -2.9;int j = 2.9;printf("%d\n", i); //结果是-2printf("%d\n", j); //结果是2return 0;
}

 有一个trunc取整函数(C99),同作用。

​比如:

#include<math.h>
#include<stdio.h>
int main()
{int i = -2.9;int j = 2.9;printf("%f\n", trunc(i)); //结果是-2printf("%f\n", trunc(j)); //结果是2return 0;
}

floor取整

本质是向-∞取整,注意输出格式要不然看不到结果,比如:

#include <stdio.h>
#include <math.h> //因为使用了floor函数,需要添加该头文件
int main()
{printf("%.1f\n", floor(-2.9)); //结果是-3printf("%.1f\n", floor(-2.1)); //结果是-3printf("%.1f\n", floor(2.9)); //结果是2printf("%.1f\n", floor(2.1)); //结果是2return 0;
}

ceil取整

本质是向+∞取整,注意输出格式要不然看不到结果,比如:

#include <stdio.h>
#include <math.h>
int main()
{printf("%.1f\n", ceil(-2.9)); //结果是-2printf("%.1f\n", ceil(-2.1)); //结果是-2printf("%.1f\n", ceil(2.9)); //结果是3printf("%.1f\n", ceil(2.1)); //结果是3return 0;
}

round取整

本质是四舍五入取整,比如:

#include <stdio.h>
#include <math.h>
int main()
{printf("%.1f\n", round(2.1));//结果是2printf("%.1f\n", round(2.9));//结果是3printf("%.1f\n", round(-2.1));//结果是-2printf("%.1f\n", round(-2.9));//结果是-3return 0;
}

汇总例子

#include <stdio.h>
#include <math.h>
int main()
{const char * format = "%.1f \t%.1f \t%.1f \t%.1f \t%.1f\n";printf("value\tround\tfloor\tceil\ttrunc\n");printf("-----\t-----\t-----\t----\t-----\n");printf(format, 2.3, round(2.3), floor(2.3), ceil(2.3), trunc(2.3));printf(format, 3.8, round(3.8), floor(3.8), ceil(3.8), trunc(3.8));printf(format, 5.5, round(5.5), floor(5.5), ceil(5.5), trunc(5.5));printf(format, -2.3, round(-2.3), floor(-2.3), ceil(-2.3), trunc(-2.3));printf(format, -3.8, round(-3.8), floor(-3.8), ceil(-3.8), trunc(-3.8));printf(format, -5.5, round(-5.5), floor(-5.5), ceil(-5.5), trunc(-5.5));return 0;
}

​接下来深度理解取余/取模运算。

C/C++Linux服务器开发高级架构师/C++后台开发架构师​免费学习地址

另外还整理一些C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享有需要的可以自行添加:Q群:720209036 点击加入~ 群文件共享

关于取余和取模

你知道2/(-2),2%(-2)的值分别是多少吗?我们一点点来看。

取模运算的定义与引例

给出一个定义: 如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r 且0 ≤ r < d。其中,q被称为商,r 被称为余数,取模运算求取的就是这个余数r。 举个例子:

int main()
{int a = 10;int b = 3;printf("%d\n", a % b);//a = q*d + r => 10 = 3*3 + 1return 0;
}

结果显而易见会是1,那如果改成下面这样结果又会如何?

int main()
{int a = -10;//改成负数了int b = 3;printf("%d\n", a % b);return 0;
}

​而在python中结果却是完全不一样的:

>>> print(-10%3)
2
>>>

分析

很显然,前面关于取模运算的定义并不能较好地满足这两门语言上的取模运算。

因为在C语言中,-10%3出现了负数,而根据定义是应该满足 a = q*d + r 且0 ≤ r < d的,这就说明C语言中取模运算得到的余数,是不满足我们所谓的取模定义的——出现了r<0的情况。

故有了一个修订版的定义:

如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r , q 为整数,且0 ≤ |r|< |d|。其中,q 被称为商,r 被称为余数,取模运算求取的就是这个余数r。 有了这个新的定义,那么C或者Python中的取模运算,就都能解释了。

解释C: -10 = (-3) * 3 + (-1)

解释Python:-10 = (?)* 3 + 2,其中,可以推导出来(?)必须是-4,即-10 = (-4)* 3 + 2,才能满足定义,这也和下面-10//3的结果吻合。

>>>print(-10//3)
-4
>>>

所以,在不同语言,同一个计算表达式,负数取模运算结果是不同的。我们可以分别叫做正余数和负余数。

由上面的例子可以看出,具体余数r的大小,本质是取决于商q的。

而商,又取决于谁呢?取决于除法计算的时候的取整规则。

比如:在C中,10/(-3)的值向0取整,所以得到-3;而在python中,10/(-3)的值向-∞取整,所以得到-4

​取余和取模一样吗

注意我前面说的是取模运算,接下来才是取余和取模。

并不是完全严格等价的,虽然大部分情况下差不多(用的都是正数)。

取余或者取模,都应该要算出商,然后才能得出余数。

我们这里主要发掘一下二者的区别。

本质 1 取整

取余:尽可能让商进行向0取整。

取模:尽可能让商进行向-∞取整。

结合上面讲的例子,可知:

C中%,本质其实是取余。

Python中%,本质其实是取模。

操作数是正还是负是有差别的:

对任何一个大于0的数,对其进行向0取整和向-∞取整,取整方向是一致的,故此时取模等价于取余。

对任何一个小于0的数,对其进行向0取整和向-∞取整,取整方向是相反的,故此时取模不等价于取余。

​本质 2 符号

参与取模运算的两个数据,如果同符号,取模等价于取余。

因为同符号数据相除,得到的商,一定是正数,即大于0!故在对其商进行取整的时候,取模等价于取余。

那如果参与运算的数据符号不同呢?比如:

int main()
{printf("%d\n", -10 / 3); //结果:-3printf("%d\n\n", -10 % 3); //结果:-1  因为-10=(-3)*3+(-1)printf("%d\n", 10 / -3); //结果:-3printf("%d\n\n", 10 % -3); //结果:1  因为10=(-3)*(-3)+1return 0;
}

所以可以看出:如果两数符号不同,余数的求法参考之前定义,而余数符号与被除数相同。此时取余不等价于取模,因为符号不同的数据相除得到的商一定是负数,对商取整时取模和取余提供的方向相反。

但是,在python中却是这样:

>>> print(-10//3)
-4
>>> print(10//-3)
-4
>>> print(-10%3)
2
>>> print(10%-3)
-2
>>>

我们发现在这里余数符号与被除数的相反,why?

还记得前面讲过的规律吗?

具体余数r的大小,本质是取决于商q的。

而商,又取决于谁呢?取决于除法计算的时候的取整规则。

我们来看一个不怎么严谨的数学推导,理解一下即可:

重新看看定义:

如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r , q 为整数,且0 ≤ |r|< |d|。其中,q 被称为商,r 被称为余数。

a = q*d + r 变换成 r = a - q*d 变换成 r = a + (-q*d),我们重点讨论的是r的符号问题。

对于:x = y + z,这样的表达式,x的符号与|y|、|z|中更大的那一个数的符号一致,若是y和z互为相反数则x为0。

而r = a + (-q*d)中,|a|和|-q*d|谁大,取决于商q的取整方式(a和d固定 )。

C语言中是向0取整的,也就是q本身的绝对值是减小的。 如:

-10/3=-3.33... 向0取整 -3,a=-10 对应绝对值为|10|, -q*d=-(-3)*3=9 对应绝对值为|9|

10/-3=-3.33... 向0取整 -3,a=10 对应绝对值为|10|,-q*d=-(-3)*(-3)=-9 对应绝对值为|9|

所以-q*d的绝对值变小了,而且往往比a的绝对值要小,因此r的符号就取决于a的符号。

而python是向-∞取整的,也就是q本身的绝对值是增大的。 如:

-10/3=-3.33... 向-∞取整 -4,a=-10 对应绝对值为|10|, -q*d=-(-4)*3=12 对应绝对值为|12|

10/-3=-3.33... 向-∞取整 -4, a=10 对应绝对值为|10|, -q*d=-(-4)*(-3)=-12 对应绝对值为|12|

所以-q*d的绝对值都变大了,而且往往比a的绝对值要大,因此r的符号就取决于-q*d的符号,又因为我们这里一直在讨论的是不同符号的数据取余/取模,所以除数d的符号与被除数a的相反,并且得到的商q一定是负数,-q正好把负号抵消掉了,-q*d的符号就取决于d了,也就是说最终r的符号取决于d的符号,同时与a的符号相反。

结论:如果参与取余的两个数据符号不同,在C语言中(或者其他采用向0取整的语言如:C++,Java),余数符号与被除数的符号相同,而在python语言中(或者其他采用-∞取整的语言),余数符号与被除数的符号相反。

最后我们再看回最开始提到的问题:2/(-2),2%(-2)的值分别是多少?

int main()
{printf("%d\n", 2 / (-2)); //-1printf("%d\n", 2 % (-2)); //2=(-1)*(-2)+r,r就是0return 0;
}

参考资料

编辑切换为居中

添加图片注释,不超过 140 字(可选)

推荐一个零声教育C/C++后台开发的免费公开课程,个人觉得老师讲得不错,分享给大家:C/C++后台开发高级架构师,内容包括Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

原文地址:[深入浅出C语言]理解取整、取余和取模 - 掘金


http://chatgpt.dhexx.cn/article/HEr0lD4U.shtml

相关文章

取整和取余

取整和取余在数学的除法运算中&#xff0c;两个数相除是可以算到小数位的。但是&#xff0c;在c语言中两个整数相除其结果是一个整数&#xff0c;只有两数中至少有一个为实数的时候&#xff0c;他们相除的结果才是小数。因此&#xff0c;两个整数相除的结果就会涉及到取整问题。…

C/C++取整函数

C常用的取整函数有三个ceil&#xff0c;floor&#xff0c;round ceil的英文释义&#xff1a;装天花板&#xff0c;装船内格子板&#xff1b;函数功能是向上取整 floor的英文释义&#xff1a;楼层; 地面&#xff0c;地板&#xff1b;函数功能是向下取整 round的英文释义&…

【C语言】取整函数ceil()用法

ceil功能&#xff1a; 返回大于等于表达式的最小整数。 ceil--数学函数&#xff0c;同样要加数学头文件#include<math.h> ceil返回大于等于表达式的最小整数&#xff0c;例如&#xff1a;float a4.5 那么int ceil&#xff08;a&#xff09;5 ceil函数和float强制转换为…

【C语言】深度理解-取整

深度理解 取整的概念 关于取整的介绍向零取整trunc()函数----向0取整 向负无穷大取整floor()函数 向正无穷大取整ceil()函数 四舍五入取整round()函数 总结 关于取整的介绍 在数学中取整方法通常是“四舍五入”&#xff0c;但是在我们编程语言中的取整方法却大有不同。大部分学…

什么是取整?有几种取整方式?C语言又是哪种方式?取模取余一样吗?

大家都知道取整这回事&#xff0c;但是对于取整只有单一的认识&#xff0c;请看下面代码。 int main() {int j -2.9;int i 2.9;printf("%d\n", j);printf("%d\n", i);return 0; } 看这串代码我们先不管数据溢出的问题&#xff0c;直接看最后的结果等于 …

C语言学习之取整取余

1.前言 对于取整&#xff0c;在数学中我们最常见的作法就是四舍五入取整。而在C语言中&#xff0c;我们则是直接通过取整数部分&#xff0c;舍弃小数部分的方式进行取整。那么&#xff0c;所有语言的取整方式是相同的吗&#xff1f;而对于取余&#xff0c;我们可能经常会听到取…

C语言取整方法

文章目录 1. 直接赋值给整型变量int2. 整数除法运算&#xff08;“int / int”&#xff09;取整除法四舍五入 3. 使用floor函数4. 使用ceil函数5. 使用round函数 1. 直接赋值给整型变量int 作用&#xff1a; 使用该方法是将小数部分去除。 int i 3.5;或i (int)3.5; #include…

数据结构——浅谈链表

上午翻源码&#xff0c;翻到了原来学习数据结构时自己实现的链表源码&#xff0c;特此总结一下。源码可能有很多不完善的地方&#xff0c;请多谅解。 按照惯例&#xff0c;还是先来介绍下什么是链表。 链表是一种数据结构&#xff0c;在内存中通过节点记录内存地址而相互链接形…

数据结构:链表-C语言实现

文章目录 链表一. 前言二. 链表的定义2.1 概念2.2 分类 三. 单向无头不循环链表3.1 概念和说明3.2 定义链表结构体3.3 函数接口3.3.1 申请节点3.3.2 链表头插3.3.3 链表尾插3.3.4 在pos节点之后插入3.3.5 在pos节点之前插入3.3.6 链表头删3.3.7 链表尾删3.3.8 删去pos节点3.3.9…

数据结构——链表

数组是常用的数据结构&#xff0c;但是有其局限性&#xff1a; 编译期需要确定元素大小 数组在内存中是连续的&#xff0c;插入或者删除需要移动数组中其他数据 数组适合处理确定长度的&#xff0c;对于插入或者删除不敏感的数据。如果数据是频繁变化的&#xff0c;就需要选择…

数据结构-链表

链表 一、介绍1、单链表1、单链表结构体&#xff1a;2、单链表头插法&#xff1a;3、单链表尾插法&#xff1a; 二、例题1、双指针&#xff08;获取倒数第K个元素、获取中间位置的元素、判断链表是否存在环、判断环的长度、查找第一个公共节点、回文链表&#xff09;1、 判断链…

C语言数据结构、十字链表的分析及实现

目录 前言 一、什么是十字链表 二、认识十字链表 1.十字链表的组成 2.顶点和弧的连接 三、代码逻辑实现 1.出度 2.入度 总结 前言 无论是什么程序都要和数据打交道&#xff0c;一个好的程序员会选择更优的数据结构来更好的解决问题&#xff0c;因此数据结构的重要性不言…

JS 数据结构:链表

单链表 每个节点中只包含一个指针域的链表称为单链表。 头结点—其指针域指向表中第一个结点的指针&#xff08;头结点不是必须的&#xff0c;只是习惯上加上头结点&#xff0c;而头结点的数据域一般记录的是该链表的相关数据&#xff0c;如&#xff1a;链表长度&#xff09;…

数据结构中链表和列表的区别

顺序表和链表由于存储结构上的差异&#xff0c;导致它们具有不同的特点&#xff0c;适用于不同的场景。通过系统地学习顺序 表和链表我们知道&#xff0c;虽然它们同属于线性表&#xff0c;但数据的存储结构有本质的不同。 • 顺序表存储数据&#xff0c;需预先申请一整块足够…

数据结构(六)——循环链表

一、循序链表简介 1、循环链表的定义 循环链表的任意元素都有一个前驱和一个后继&#xff0c;所有数据元素在关系上构成逻辑上的环。 循环链表是一种特殊的单链表&#xff0c;尾结点的指针指向首结点的地址。 循环链表的逻辑关系图如下&#xff1a; 2、循环链表的设计实现 …

数据结构-使用链表实现栈

目录结构 Stack接口 package LinkedListStack;public interface Stack<E> {int getSize();boolean isEmpty();void push(E e); //向栈中添加元素E pop();//向栈中取出元素E peek();//查看栈顶的元素 }LinkedList类 package LinkedListStack;public class LinkedList<…

数据结构 | 链表的实现

目录 单链表双链表数组结构和链式结构的对比 线性表中&#xff0c;除了顺序表这一重要的结构&#xff0c;还有链式结构&#xff0c;而这也是我们常说的链表。 一般是通过定义结构体(类)的方式来表示链表的每一个结点。一般而言&#xff0c;链表的结点都会有数据域和地址域。数据…

Java数据结构之链表

目录 一.单链表 1.单链表的介绍和内存布局 2.单链表的添加和遍历 3.单链表的插入 4.单链表的删除 二.双向链表 1.添加节点 2.遍历节点 3.插入节点 4.删除结点 5.测试 三.单向环形链表 1.问题的引出 ​编辑 2.构建环形链表 1.创建结点 3.测试 3.约瑟夫问题代码的…

c++数据结构:链表

链表是一种物理存储单元上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点&#xff08;链表中每一个元素称为结点&#xff09;组成&#xff0c;结点可以在运行时动态生成。每个结点包括两个部分&#xff1a;一个是…

java数据结构-链表详解

文章目录 1.数据结构-链表详解1.1单链表1.1.1单链表节点的尾部添加1.1.2单链表节点的自动排序添加1.1.3单链表节点的修改1.1.4单链表节点的删除 1.2单链表面试题1.2.1单链表的有效节点个数1.2.2单链表倒数第k个结点1.2.3单链表反转1.2.4单链表逆序打印 1.3双向链表1.3.1双向链表…