[关闭]
@Otokaze 2018-09-05T02:34:49.000000Z 字数 5376 阅读 86

小数如何存储

笔记

整数和小数

在计算机中,整数和小数的存储是不一样的。整数的个数比小数的个数少得多,1 到 2 之间的小数就有无穷个,何况其它的呢。所以小数的精度比整数的精度差的很多,因为容量有限,不可能表示所有的小数。

科学记数法

对于任意进制的实数 V 都可以表示为:V = (-1)S * M * 基数E。其中:

M 的取值范围:[1, 基数),比如十进制的为 [1, 10),二进制的为 [1, 2)
E 表达式的底是对应的基数,比如十进制的为 10,八进制的为 8,二进制的为 2。

在计算机中,一切都是二进制,所以我们主要讨论 V = (-1)S * M * 2E。而又因为 M 总是 1.xxx,为了节省空间,IEEE 规定不存储开头的 1,而是只存储 xxx,读取时再自己加上这个 1。为了与正常的有效数字区分,IEEE 标准将这里的 M 称为尾数,因为没有开头的 1.。还有注意,V 也是二进制数字。

比如十进制数字 5,对于的二进制数字为 101,使用科学记数法表示为 (-1)0 * 1.01 * 22,对于的 S = 0,M = 1.01,E = 2。这里提示一个小技巧,指数的数值其实就是有效数字的移动位数(不足或新增的补 0),如果 E 为正数,则向右移,如果 E 为负数,则向左移。不论是二进制还是十进制都一样。

IEEE 754

IEEE 二进制浮点数算术标准(IEEE 754)是 20 世纪 80 年代以来最广泛使用的浮点数运算标准,为许多 CPU 与浮点运算器所采用。这个标准定义了表示浮点数的格式(包括负零 -0)与反常值(denormal number),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”;它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。

IEEE 754 规定了四种表示浮点数值的方式:单精确度(32位)双精确度(64位)延伸单精确度(43比特以上,很少使用)延伸双精确度(79比特以上,通常以80位实现)。只有 32 位模式有强制要求,其他都是选择性的。大部分编程语言都有提供 IEEE 浮点数格式与算术,但有些将其列为非必需的。例如,IEEE 754 问世之前就有的 C 语言,现在有包括 IEEE 算术,但不算作强制要求(C 语言的 float 通常是指 IEEE 单精确度,而 double 是指双精确度)。

这里仅以使用广泛的 float 和 double 为例,讲解“精度”的问题。所谓的精度其实就是能准确存储的小数(十进制)的有效数字的位数,比如精度为 5,则说明能准确的存储 5 个有效数字的十进制小数,比如 1.2345,3.1415。但是 3.1415926 就无法精确保存了,超过精度的部分会被四舍五入处理。因此 3.1415926 在只有精度为 5 的环境中实际存储的只是 3.1416(这就导致了精度的丢失)。

IEEE 单精度浮点数 32 bit

符号 Sign 指数 Exponent 尾数 Mantissa
1 bit 8 bit 23 bit

IEEE 双精度浮点数 64 bit

符号 Sign 指数 Exponent 尾数 Mantissa
1 bit 11 bit 52 bit

因为尾数中隐含了 1 bit 的 1,所以单精度浮点数的有效数字长度为 24 bit,双精度的为 53 bit。

有效数字怎么看:从左边起的第一个非 0 的数字到最后一个数字都属于有效数字。

注意:即使你存储在 float、double 中的数值的有效位在 7 或 15 内,但是仍有很大的可能导致精度的丢失,其根本原因是因为小数从十进制转换为二进制期间会发生精度的丢失(很多十进制非无限循环小数用二进制表示时都是无限循环的或者小数位非常长的),比如经典的 0.1 + 0.2 = 0.30000000000000004 问题(double双精度浮点数)。因为 0.1 和 0.2 都无法使用有限长度的二进制数表示(它们都是无限循环的二进制小数),计算机不得不进行舍入操作,导致了精度的丢失。

浮点/定点

什么是定点数、浮点数?
首先我们要认清一个概念,定点数不一定是整数浮点数不一定是小数。正如其名,浮点数和定点数的区别就在于浮点和定点上,点就是指小数点。浮点数的小数点是浮动的,定点数的小数点是固定不动的。

在计算机发展历史中,出现了两种主流的小数表示/存储方法:定点数和浮点数。浮点数我们上面已经详细讲解了,优点是灵活,节省空间,表示的范围比较大,缺点是容易丢失精度,单精度浮点数只能保证 7 个有效数字,双精度浮点数只能保证 15 个有效数字。如果要处理的小数不在这些范围内,那么就必然避免不了精度的丢失。而定点数顾名思义就是小数点在数字序列中的位置是固定的,浮点数只适用于工程计算、科学计算等精度要求不高的场景,而诸如银行账户、货币大小这些必须使用定点数来存储和处理。定点数的优点就是精度高,缺点是需要的空间更多,运算也比浮点数更复杂,更慢。

MySQL 中的小数数据类型:

MySQL 中的 DECIMAL 和 NUMERIC 是同一个实现,所以它们是完全相同的数据类型。
decimal 的声明方式为 decimal(M, D),其中 M 是精度(总位数),D 为小数位数。

Java 中的大数/高精度类型:

BigInteger

构造函数
public BigInteger(String val):radix = 10。常用构造函数。
public BigInteger(String val, int radix):指定进制(不要出现 0b、0、0x 等前缀)。
new BigInteger("100000000")new BigInteger("5F5E100", 16) 的大小是一样的。

静态成员
public static final BigInteger ZERO:常数 0
public static final BigInteger ONE:常数 1
public static final BigInteger TEN:常数 10
public static BigInteger valueOf(long val):可缓存常用值,优先使用。

加减乘除
public BigInteger add(BigInteger val):加
public BigInteger subtract(BigInteger val):减
public BigInteger multiply(BigInteger val):乘
public BigInteger divide(BigInteger val):除
public BigInteger remainder(BigInteger val):取余

位运算符
public BigInteger and(BigInteger val):按位与
public BigInteger or(BigInteger val):按位或
public BigInteger xor(BigInteger val):按位异或
public BigInteger not():按位非
public BigInteger shiftLeft(int n):左移
public BigInteger shiftRight(int n):右移

关系运算符
public int compareTo(BigInteger val):比较数值是否相等(建议使用此方法)
public boolean equals(Object x):两个对象是否严格相等(可理解为字符串相等)

BigDecimal

构造函数
public BigDecimal(BigInteger val):将 BigInteger 转换为 BigDecimal
public BigDecimal(double val):请确保有效数字长度不大于 15,否则会丢失精度
public BigDecimal(int val):将 int 整型转换为 BigDecimal
public BigDecimal(long val):将 long 长整型转换为 BigDecimal
public BigDecimal(String val):使用字符串构造 BigDecimal(推荐方式)
public BigDecimal(char[] in):将字符数组(比 String 高效)转换为 BigDecimal
public BigDecimal(char[] in, int offset, int len):同上,指定偏移量、提取的长度

静态成员
public static BigDecimal valueOf(long val):可缓存常用值,优先使用
public static BigDecimal valueOf(double val):可缓存常用值,优先使用

public static final BigDecimal ZERO:常量 0
public static final BigDecimal ONE:常量 1
public static final BigDecimal TEN:常量 10

public static final int ROUND_UP:向远离 0 的方向舍入
public static final int ROUND_DOWN:向接近 0 的方向舍入
public static final int ROUND_CEILING:向右边的方向舍入
public static final int ROUND_FLOOR:向左边的方向舍入
public static final int ROUND_HALF_UP:“四舍五入”
public static final int ROUND_HALF_DOWN:“五舍六入”
public static final int ROUND_HALF_EVEN:前一位:奇数则入位,偶数则舍去
public static final int ROUND_UNNECESSARY:断言此操作具有精确的结果,不需要舍入。如果断言失败,则抛出 ArithmeticException 异常

JDK1.5 起,优先使用 java.math.RoundingMode 枚举类,常量名没有 ROUND 前缀。

加减乘除
public BigDecimal add(BigDecimal augend):加
public BigDecimal subtract(BigDecimal subtrahend):减
public BigDecimal multiply(BigDecimal multiplicand):乘
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode):除,其中 scale 是保留多少位小数,roundingMode 则是舍入模式(final常量,过时)
public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode):除,其中 scale 是保留多少位小数,roundingMode 则是舍入模式(enum 枚举常量)
public BigDecimal divide(BigDecimal divisor, int roundingMode):scale = this.scale(),默认是 0。
public BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode):scale = this.scale(),默认是 0。
public BigDecimal divide(BigDecimal divisor):scale = this.scale() - divisor.scale()
public BigDecimal divideToIntegralValue(BigDecimal divisor):向下取整,scale = this.scale() - divisor.scale()
public BigDecimal remainder(BigDecimal divisor):取余(注意不是取模)

关系运算符
public boolean equals(Object x):严格相等,它认为 2.0 与 2.00 是不相同的
public int compareTo(BigDecimal val):优先考虑,认为 2.0 与 2.00 是相同的

精度相关属性
public int scale():返回要保留的小数位数
public int precision():返回当前定点数的有效位数(scale + 1)
public BigDecimal setScale(int newScale, RoundingMode roundingMode):设置新的小数位数以及舍入模式,返回新对象
public BigDecimal setScale(int newScale, int roundingMode):同上,但这是一个过时的方法

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注