JAVA中取模的问题

## Java取模(%)运算
> [上篇文章](https://yebukong.com/article/1101070795486109697.html "上篇文章") 提到了java取模会出现负数的问题,发现自己对这块理解有偏差,这里记录一下。

### 介绍
取模运算,应该都用过,我之前的简单理解就是小学学过的取余运算,在参考了一些资料后,发现理解有偏差。这里放一段维基百科的介绍:
> 模除(又称模数、取模操作、取模运算等,英语:modulo 有时也称作 modulus)得到的是一个数除以另一个数的余数。

>给定两个正整数:被除数 a 和除数 n,a modulo n (缩写为 a mod n)得到的是使用欧几里德除法时 a/n 的余数。 举个例子:计算表达式 "5 mod 2" 得到 1,因为 5÷2=2...1(5 除以 2 商 2 余 1);而 "9 mod 3" 得到 0,因为 9÷3=3...0;注意:如果使用计算器做除法,不能整除时,你不会得到商,而是会得到一个小数,如:5÷2=2.5。

>虽然通常情况下 a 和 n 都是整数,但许多计算系统允许其他类型的数字操作,如:**对浮点数取模**。一个整数对 n 取模的结果范围为: 0 到 n − 1(a mod 1 恒等于 0;a mod 0 则是未定义的,在编程语言里可能会导致除零错误)。**当 a 和 n 均为负数时,通常的定义就不适用了,不同的编程语言对结果有不同的处理。**

可以看到编程语言中的取模操作和小学学过的取余并不是完全一样的,它对数学中的取余(我小学记忆中的)做了扩展,推广到了浮点数以及负数,所以**取模操作结果是会出现负数的**,上面还提到了在不同语言有不同处理,就导致不同语言下取模结果的不一致。

### 取整方式
在理解取模运算过程前,需要先看看取整的不同。当余数不为 0 的时候,取整(/)有几种不同处理方式:
- **向上取整**
向 +∞ 方向取最接近精确值的整数,也就是取比实际结果稍大的最小整数,也叫 Ceiling 取整,例如:`17 / 10 = 2`,`5 / 2 = 3`, `-9 / 4 = -2`;

- **向下取整**
向 -∞ 方向取最接近精确值的整数,也就是取比实际结果稍小的最大整数,也叫 Floor 取整,例如:`17 / 10 = 1`,`5 / 2 = 2`,`-9 / 4 = -3`;

- **向零取整**
向 0 方向取最接近精确值的整数,换言之就是舍去小数部分,因此又称截断取整(Truncate),例如:`17 / 10 = 1`,`5 / 2 = 2`, `-9 / 4 = -2`;

### 负数取模验证
不同编程语言中,对取模操作有truncate(截断除法)和floored(取底除法)等。truncate 除法用到了上面的向零取整,而floor除法用到了上面的向下取整。JAVA中使用的是truncate,而Python使用的是floor。下面我们验证一下:
- JAVA

```java
System.out.println( 5 %  3); // 2
System.out.println( 3 %  5); // 3
System.out.println(-5 %  3); //-2
System.out.println( 5 % -3); // 2
```
分析过程

`5 % 3 = 5 - trunc(5/3)*3 = 5 - 1*3 = 5-3 = 2`

`3 % 5 = 3 - trunc(3/5)*5 = 3 - 0*5 = 3-0 = 3`

`-5 % 3 = -5 - trunc(-5/3)*3 = -5 - (-1)*3 = -5-(-3) = -2`

`5 % -3 = 5 - trunc(5/-3)*(-3) = 5 - 1*(-3) = 5-(-3) = 2`

- Python

```python
print  5 %  3 ; # 2
print  3 %  5 ; # 3
print -5 %  3 ; # 1
print  5 % -3 ; #-1
```
分析过程

`5 % 3 = 5 - floor(5/3)*3 = 5 - 1*3 = 5-3 = 2`

`3 % 5 = 3 - floor(3/5)*5 = 3 - 0*5 = 3-0 = 3`

`-5 % 3 = -5 - floor(-5/3)*3 = -5 - (-2)*3 = -5-(-6) = 1`

`5 % -3 = 5 - floor(5/-3)*(-3) = 5 - (-2)*(-3) = 5-6 = -1`

### 浮点数取模
浮点数取模和整数计算方式是一样的,但是在Java中却有另一个问题:**精度**
```java
System.out.println(5.3 % 3); // 2.3
System.out.println(3 % 5.3); // 3.0
System.out.println(5.3 % 3.2); // 2.0999999999999996
```
分析

`5.3 % 3 = 5.3 - trunc(5.3/3)*3 = 5.3 - 1*3 = 5.3-3 = 2.3`

`3 % 5.3 = 3 - trunc(3/5.3)*5.3 = 3 - 0*5.3 = 3-0 = 3`

`5.3 % 3.2 = 5.3 - trunc(5.3/3.2)*3 = 5.3 - 1*3.2 = 5.3-3.2 = 2.1`

可以看到某种情况下的浮点数取模时,出现了精度问题,这里推荐使用`java.math.BigDecimal`类操作,且构造方法选择 `public BigDecimal(String val) `,具体取模使用`public BigDecimal[] divideAndRemainder(BigDecimal divisor)`方法,返回值为数组,[0]位置存放商,[1]存放余数;

```java
BigDecimal x = BigDecimal.valueOf(5.3);
BigDecimal y = BigDecimal.valueOf(3.2);
BigDecimal[] z = x.divideAndRemainder(y);
System.out.println(z[0]); // 1
System.out.println(z[1]); // 2.1
```

至于精度为何出现丢失,则又是一个坑了。


### 参考链接
>  https://zh.wikipedia.org/wiki/%E6%A8%A1%E9%99%A4
>  https://www.jianshu.com/p/452c1a5acd31
>  https://blog.csdn.net/zhige_me/article/details/80980566 

:bowtie:

------------
> 本文由 [叶不空](https://yebukong.com "叶不空") 创作,采用 [知识共享署名 4.0 国际许可协议](https://creativecommons.org/licenses/by/4.0/ "知识共享署名 4.0 国际许可协议")进行许可,转载请附上链接!
> 本文链接: [https://yebukong.com/article/1101707715186593793.html](https://yebukong.com/article/1101707715186593793.html "JAVA中取模的问题")
                        
(°ο°)评论插件未能完成加载!