专栏:算法
上一篇:(一)基础算法
下一篇:(三)位运算
目录
1. 基本运算
1.1 浮点数取整
浮点数即经常说的小数,一个小数由整数部分、小数部分和小数点三部分组成。
浮点数转成整数是经常见到的运算,即小数转成整数。浮点数转成整数,最常用到的是近似取整,也就是常说的 “四舍五入”。但是浮点数取整不只是有近似取整这一种,还有向上取整、向下取整 和 向零取整,这些取整在C标准库都有各自对应函数,或者是内置运算。
浮点数取整运算 | 函数或运算 | 头文件 | 备注 |
---|---|---|---|
向零取整 | (int)a | — | 直接强制类型转换成 int,便是向零取整 |
向上取整 | ceil(a) | <math.h> | |
向下取整 | floor(a) | <math.h> | |
近似取整 | (int)(a + 0.5) | — | |
近似取整 | round(a) | <math.h> | C99标准 或C++11标准引入,之前不算标准库函数 |
1.1.1 向零取整 (小数部分丢弃)
向零取整是C/C++的内置浮点数取整运算,将一个浮点数强制转换为整数时,使用的便是向零取整。取整的结果向零靠拢,取靠近零方向遇到的第一个整数,小数部分被舍弃。
如果数值本身就是整数,那么取整后结果不变。
int a = (int)(4.6); //结果为4
int b = (int)(-4.7); //结果为-4
1.1.2 近似取整(四舍五入)
如果小数部分大于等于0.5则取整数部分加1的值,如果小数部分小于0.5,则取整数部分,小数部分舍去。相当于取最接近的整数值。
1.1.2.1 使用round()函数
- C/C++标准库中的
round( )
及其类型扩展函数(头文件 math.h)
int a = round(4.6); //结果为 5
int b = round(-3.6); //结果为-4
int c = round(4); //结果为 4
1.1.2.2 强转为int
在不使用
round()
函数的情况下,可以通过小数部分加减0.5再强制转换为int的方式达到近似取整的效果。
若为非负数,则加上0.5,再向零取整;若为负数,则减去0.5,再向零取整。
int roundValue(float a)
{
if (a > 0.0f)
return (int)(a + 0.5f);
else
return (int)(a - 0.5f);
}
1.1.2.3 使用floor()函数
floor()
函数是对浮点数进行向下取整,取不大于该数的最大整数。若要原数进行四舍五入,我们可以将原数加上0.5再进行向下取整。
int roundValue(float a)
{
return (int)floor(a + 0.5f);
}
1.1.3 向下取整
向下取整是取不大于该数的最大整数。
C/C++标准库对应的函数是floor()
,在头文件<math.h>中。
int a = floor(5.9); // 结果为 5
int b = floor(-5.1); // 结果为 -6
int c = floor(4); // 结果为 4
实现:
利用向零取整操作来进行,可以看到向下取整和向零取整在正数部分是一致的。所以如果是正数或整数,直接向零取整,是负数且不是整数的,向零取整后减1。
int floorValue(double a)
{
int b = (int)a;
if (a > 0 || a == b)
return b;
else
return b - 1;
}
1.1.4 向上取整
向上取整是取不小于该数的最小整数。
C/C++标准库对应的函数是ceil()
,在头文件<math.h>中。
int a = ceil(5.9); // 结果为 6
int b = ceil(-5.9); // 结果为 -5
int c = ceil(4); // 结果为 4
实现:
利用向零取整操作来进行,可以看到向上取整和向零取整在负数部分是一致的。所以如果是符数或整数,直接向零取整,是正数且不是整数的,向零取整后加1。
int ceilValue(double a)
{
int b = (int)a;
if (a < 0 || a == b)
return b;
else
return b + 1;
}
1.2 整数除法
两个整数相除,如果 被除数 刚好能被 除数 整除,直接相除就可以得到结果。如果不能被 除数 整除,那么整数除法得到的结果为小数除法的商 向 0 取整 。
整数除法中, 除数不可为 0, 否则引发除零异常,程序会崩溃。
int a = 5 / 2; 结果为 2 (实数商为2.5, 向零取整,得2)
int b = -7 / 3; 结果为 -2(实数商为-2.33333, 向零取整,得-2)
1.2.1 整数除法商的取整
C/C++的整数除法的商默认是向零取整,那如何实现其它另外三个取整呢?首先我们把向零取整与向下取整和向上取整进行对比,观察可以发现:
向零取整在正数范围内是向下取整,而在负数范围是向上取整。
也就是说,向零取整和其它两个取整是一半范围相同,一半范围不同。
1.2.1.1 转成浮点数计算
将原来的两个数转成浮点数再计算,就可以使用之前的浮点数取整函数来取整。这种是比较简单的方法,缺点是引入了浮点数,在一些嵌入式芯片上可能运算比较慢,并且在一些嵌入式开发,有时并不允许使用浮点计算。并且想要转成浮点数,需要浮点数的精度大于整数值的范围。
int型为32位二进制整数,double型可以存储52位二进制整数,而float型仅可以存储24位二进制整数。所以int类型的数据,需要用double存储,而对于64位的long long 型,可能需要使用long double,但是long double依赖于编译器实现,精度不一定就比long long 高。
如,整数除法向上取整:
int ceilDivide(int a, int b)
{
return (int)ceil((double)a / (double)b);
}
1.2.1.2 利用余数判断是否被整除
当不能被整除时,我们根据向零取整的取整方向,在于其取整方向不同的范围内将原本向零取整的结果加一或减一。
如向上取整和向零取整在商为非负数的范围内和向零取整的取整方向不同,当不能整除时,结果比向零取整多1,所以可得
int ceilDivide(int a, int b)
{
const int fixValue = a / b; //向零取整的商
const int remValue = a % b; //余数,判断是否被整除
if ((fixValue >= 0) && (remValue != 0)) // 商 >= 0 且余数不为0(不能整除)
return fixValue + 1;
else
return fixValue;
}
上面的方法缺点是调用了两次除法(依赖于编译器的优化, 一次除法运算指令实际上是同时得到商和余数)并添加了多个判断,效率比原来内置的除法运算低得多。
1.2.1.2 正整数除法
如果是两个正整数的除法,那么情况会简单得多,并且可以非常高效。在正整数除法中,向零取整和向下取整的结果是相同的。而向上取整则可以通过使用算式 ( a + ( b − 1 ) ) / b (a + (b - 1))/b (a+(b−1))/b 来获得:
int ceilDivide(int a, int b)
{
return (a + (b-1)) / b;
}
这种方法是利用正整数除法余数在
[
1
,
b
−
1
]
[1, b-1]
[1,b−1]之间,通过像被除数加
b
−
1
b-1
b−1,就能使得当余数不为0时,结果比向下取整的商多1。
同理,正整数除法的近似取整,可以用
(
a
+
b
/
2
)
/
b
(a + b/2) / b
(a+b/2)/b
int roundDivide(int a, int b)
{
return (a + b / 2) / b;
}
1.3 浮点数除法
浮点数除法即小数除法,但是受限于存储方面的原因,大多除法结果会稍微有点误差,double的精度高于float的精度。
double a = 1.0 / 2.0; // 等于0.5
double b = 4.0 / 3.0; // 结果为1.3333333333333333 ...
浮点数除法运算中,除数可以为 0.0:
- 当 被除数 > 0.0 时,结果为无穷大( inf ) 。
- 当 被除数 < 0.0 时,结果为 负无穷大(-inf) 。
- 当 被除数 = 0.0 时,结果为 非数(nan, not a number)。
除零的浮点数除法, 在某些编译器中,如果在编译期间检测到除零操作并且设置了安全检查,则可能会报错。
float b = 1.0f, c = 0.0f;
double a = 1.0 / 0.0; // 结果为 inf
double b = -1.0 / 0.0; // 结果为 -inf
double c = 0.0 / 0.0; // 结果为 nan
需要注意的是,下面属于整数除法,因为运算的是两个int型,整数除法的结果由 int型 转换成 double型。
double d = 5 / 2; // 这里则是右边先用整数除法得2, 所得的整数值再赋值给double 型, 得 2.0
1.4 取模运算和求余运算
1.4.1 整数运算
C/C++中的 %运算符 用于求余数,并且只能作用于两个整数类型变量,除数不能为0。
在整数除法运算中, 被除数 除数 = 商 ⋯ ⋯ 余数 \cfrac{被除数}{除数}=商\cdots\cdots余数 除数被除数=商⋯⋯余数 所以 余数 = 被除数 − 除数 × 商 余数=被除数-除数\times商 余数=被除数−除数×商 但是两个整数除法的商,在不同语言上有可能不同。当被除数不能被除数整除时,商如何取整?在有些编程语言中,是向下取整,而有些是向零取整,这就造成了得到的余数结果不同,分为 求余运算 和 取模运算 两种,这两种运算在除数和被除数异号时结果有所区别,区别如下:
运算 | 简记 | 整数除法的商 | 不能被整除时的结果符号 | |
---|---|---|---|---|
取模运算(Modulus Operation) | Mod | 商为向下取整 | 余数和除数同号 | Python |
求余运算(Remainder Operation) | Rem | 商为向零取整 | 余数和被除数同号 | C/C++ |
当被除数不能被除数整除时,结果和除数同号的, 为取模运算,结果和被除数同号的,为求余运算。
在C/C++中,整数除法的结果是向零取整, 所以 %是求余运算,结果与被除数同号。
int a = 13 % 3; //结果为1, 13 / 3 = 4....1(13除以3, 商4余1)
int b = -13 % 3; //结果为-1, -13 / 3 = -4 ...-1
等式:被除数 = 除数 * 商 + 余数
a = b * (a / b) + (a % b)
1.4.2 浮点数求余运算
C/C++中的求余运算符 %只能用于两个整数型, 不可用于浮点型,而且不可对零求模。如果希望对浮点数运算,可使用标准库中的
fmod()
函数。
C/C++中的fmod()
是浮点数求余运算,符号和被除数相同,结果为浮点数,如果除数为0,结果为 nan。
#include <math.h>
double a = -5.1, b = 3.2;
double mod = fmod(a, b);
1.5 浮点型的整数与小数
1.5.1 判断浮点型数是不是一个整数
如果浮点数不是一个整数,那么小数部分就不为0,取整后就和原来的值不相等,所以可以利用取整后的值和原值比较来判断。
//如果是整数
if (a == (int)a)
1.5.2 提取整数部分和小数部分
在C/C++标准库中有modf()
函数来将一个浮点数分为整数和小数部分(modf是 modulus and fraction),返回值为小数部分,整数部分通过参数返回。整数部分为原数向零取整。
double modulus; //保存整数部分
double fraction = modf(-5.3, &modulus); //小数部分: -0.3, 整数部分:-5
实现:
向零取整即可得到整数部分。
double a = 6.1245;
int result = (int) a;
小数部分 = 原数 - 整数部分
double a = 6.1245;
double result = a - (int)a;
专栏:算法
上一篇:(一)基础算法
下一篇:(三)位运算