深入理解浮点数

xiaoxiao2021-02-28  15

深入理解浮点数

在实际项目中我们常用浮点数进行运算和比较,往往运算结果和自己心算的结果不一致,但相差很小,或者本应该相等的浮点数返回比较结果总是false.因此我们有必要深入了解下浮点数。

1.十进制和二进制互相转换

1.1 十进制转二进制

整数部分整除,小数部分短乘

举个栗子:

1.2 二进制转十进制

按照相应的指数求和

举个栗子:

2. 浮点数的表示

任意一个浮点数都有n 多种表达方式,比如 33.1415,可以表示为33.1415、3.31415 * 10 、0.331415 * 10^2 、331.1415 * 10 ^ -1 ….

为了方便统一表示,IEEE 754标准规定:

float单精度浮点数为32位:符号位 1bit、指数8bits、尾数23bits。

double双精度浮点数为64位:符号位 1bit、指数11bits、尾数52bits

3. 浮点数的陷阱

为了方便说明浮点数的陷阱,将IEEE 754标准 简化 浮点型 为5 位,4 位尾数,1位指数,进位规则为奇进偶舍(最后一位如果是奇数,则进位,偶数则舍弃)

3.1 0.2 * 3 != 0.6

0.2 的二进制表示为0.0011 0011 0011 …… 由于简化模型尾数为4位,并且第四位是奇数,所以 0.2 在简化模型中的表达是 0.010,乘以 3 后 表达为0.11 ,十进制值为0.75

0.6 的二进制表示为0.1001 1001 1001 …… 由于简化模型尾数为4位,并且第四位是偶数,所以0.6 在简化模型中的表达是 0.100,十进制值为0.5

3.2 不满足交换律 (a + b)+ c != a + ( b + c )

例如 (3 + 0.125) + 0.125 != 3 + (0.125 + 0.125)

3.3 不满足结合律 (a + b)*c != a*c + b*c

例如 (0.125 + 0.125)* 0.5 != 0.125* 0.5 + 0.125*0.5

4. java语言BigDecimal与数据库decimal是否能解决精度精确计算问题?

理论上能,坑很多,非常不建议以这种方式解决精度问题。即使java ok了,oc和js怎么办? 强烈建议取最小单位规格化成整数存储和运算,例如金额用分,土地面积用厘做单位。

4.1 BigDecimal的存储格式

实际是定点存储,即完全作为整数存,再附加一个小数点位置对应的负指数,因此才不丢精度。但是要非常注意,必须使用String初始化BigDecimal才可能不丢精度,所有算数运算也要通过BigDecimal操作才行,只要有中间值涉及到浮点数就会丢精度。

Java(JDK8)的 java.math.BigDecimal public class BigDecimal extends Number implements Comparable<BigDecimal> { private final BigInteger intVal; private final int scale; public BigDecimal(char[] in, int offset, int len, MathContext mc) { ...... char coeff[] = new char[len]; for (; len > 0; offset++, len--) { c = in[offset]; ...... if (dot) ++scl; ...... } ...... rb = new BigInteger(coeff, isneg ? -1 : 1, prec); ...... this.scale = scl; this.intVal = rb; } } public class BigInteger extends Number implements Comparable<BigInteger> { final int[] mag; BigInteger(char[] val, int sign, int len) { int numWords; if (len < 10) { numWords = 1; } else { long numBits = ((numDigits * bitsPerDigit[10]) >>> 10) + 1; if (numBits + 31 >= (1L << 32)) { reportOverflow(); } numWords = (int) (numBits + 31) >>> 5; } int[] magnitude = new int[numWords]; ...... while (cursor < len) { group = val.substring(cursor, cursor += digitsPerInt[radix]); // +=9 groupVal = Integer.parseInt(group, radix); destructiveMulAdd(magnitude, superRadix, groupVal); } ...... mag = trustedStripLeadingZeroInts(magnitude); } }

4.2 mysql decimal的存储格式

数据库decimal实际也是定点数存储,与BigDecimal的存储方式都是一样的。但是将浮点数存入decimal,或者将decimal读取成浮点数,都会损失精度,与用float/double无异。使用必须小心翼翼,在程序层面用BigDecimal,存储时将BigDecimal转String再cast成db decimal,从数据库读取时也是以String读出db decimal,再以String初始化BigDecimal。

mariadb-server-10.3\include\decimal.h /** intg is the number of *decimal* digits (NOT number of decimal_digit_t's !) before the point frac is the number of decimal digits after the point len is the length of buf (length of allocated space) in decimal_digit_t's, not in bytes sign false means positive, true means negative buf is an array of decimal_digit_t's */ typedef struct st_decimal_t { int intg, frac, len; my_bool sign; decimal_digit_t *buf; // typedef int32 decimal_digit_t; } decimal_t; mariadb-server-10.3\strings\decimal.c int internal_str2dec(const char *from, decimal_t *to, char **end, my_bool fixed) { ...... buf=to->buf+intg1; for (x=0, i=0; intg; intg--) { ...... if (unlikely(++i == DIG_PER_DEC1)) // #define DIG_PER_DEC1 9 { *--buf=x; x=0; i=0; } } ...... buf=to->buf+intg1; for (x=0, i=0; frac; frac--) { ...... if (unlikely(++i == DIG_PER_DEC1)) { *buf++=x; x=0; i=0; } } ...... }

5. 基础是否重要?

每个人都要独立面对问题,所以要有解决问题的能力,而非背诵答案的能力。知道浮点数不可精确比较,比不知道好,但是不知道原理,就难以规避浮点数的其他陷阱。

转载请注明原文地址: https://www.6miu.com/read-2629909.html

最新回复(0)