Por qué 0.1+0.2≠0.3

Todos en la computadora saben que los datos se almacenan en forma binaria y todos los números se convierten en una serie de números 0 y 1.

decimal a binario

Se cree que el método de conversión de decimal a binario es familiar para todos. Si dice que aún no lo sabe, creo que todavía es demasiado modesto. Tal vez lo olvidó. Incluso si realmente lo olvidó, no tenga miedo, recordémoslo juntos.

Para el sistema decimal mayor a 1, el resto se divide por 2. Por ejemplo, el proceso de convertir el número 8 a binario es el siguiente:

Snipaste_2023-07-09_11-42-49.png

Para la conversión de números decimales menores a 1 a binario, se utiliza el método de redondeo de multiplicación por 2 , la parte decimal se multiplica por 2 como un bit binario, y luego la parte decimal se multiplica por 2 como el siguiente bit hasta que ya no existe decimal

Snipaste_2023-07-09_00-31-08.png

Si usamos el mismo método para convertir 0.1a binario, el proceso es el siguiente:

2023-07-09_003442

Se puede encontrar que la representación binaria de 0.1 es un bucle infinito.

Dado que los recursos de la computadora son limitados, no hay forma de representar con precisión 0.1 en binario. Solo se puede representar por "valor aproximado", es decir, bajo la condición de precisión limitada, el número binario cercano a 0.1 se maximiza. lo que provocará la pérdida de precisión Caso.

¿Cómo almacenan las computadoras los decimales binarios?

Las computadoras usan números de punto flotante para almacenar decimales . La comprensión popular de "punto flotante" significa que el punto decimal puede flotar

Por ejemplo 1000.101, este número binario se puede expresar como 1.000101 x 2^3, similar a la notación científica en matemáticas

Algunos estudiantes pueden haber pasado mucho tiempo desde la escuela secundaria y pueden haber olvidado qué es la notación científica.

Por ejemplo, si hay un gran número decimal 1230000, también podemos expresarlo 1.23 x 10^6como notación científica.

Este método tiene solo un número a la izquierda del punto decimal, y este tipo de número sin un 0 inicial en la parte entera se llama normalización . Por ejemplo 1.0 x 10^(-9), es notación científica normalizada, mientras que 0.1 x 10^(-9)y 10.0 x 10^(-9)no son

Por lo tanto, si la notación científica se va a usar en binario y normalizarse al mismo tiempo, entonces no solo la base debe ser 2, sino que también hay solo 1 dígito a la izquierda del punto decimal, y debe ser 1.

Por lo tanto, este tipo de número binario suele 1000.101expresarse de forma normalizada 1.000101 x 2^3, entre ellos, los más importantes son 000101 y 3, que pueden contener toda la información de este número binario:

  • 000101Conocido como la mantisa , el número después del punto decimal;
  • 3Llamado exponente , especifica la posición del punto decimal en los datos;

Los números de coma flotante utilizados por la mayoría de las computadoras ahora generalmente adoptan el estándar internacional formulado por IEEE. La forma estándar es la siguiente:

estándar IEEE

El significado de estas tres partes importantes es el siguiente:

  • Bit de signo : indica si el número es positivo o negativo, 0 indica un número positivo y 1 indica un número negativo;
  • Bit de exponente : especifica la posición del punto decimal en los datos. El exponente puede ser negativo o positivo. Cuanto más largo sea el bit de exponente, mayor será el rango de expresión del valor ;
  • Mantisa : el número en el lado derecho del punto decimal, es decir, la parte decimal, como binario 1.0011 x 2^(-2), la mantisa es 0011, y la longitud de la mantisa determina la precisión del número , por lo que si desea representar un decimal con mayor precisión, es necesario aumentar la longitud de la mantisa;

32 位来表示的浮点数,则称为单精度浮点数,也就是我们编程语言中的 float 变量,而用 64 位来表示的浮点数,称为双精度浮点数,也就是 double 变量,它们的结构如下:

flotar
  • double 的尾数部分是 52 位,float 的尾数部分是 23 位,由于同时都带有一个固定隐含位(这个后面会说),所以 double 有 53 个二进制有效位,float 有 24 个二进制有效位,所以所以它们的精度在十进制中分别是 log10(2^53) 约等于 15.95log10(2^24) 约等于 7.22 位,因此 double 的有效数字是 15~16 位,float 的有效数字是 7~8 位,这些有效位是包含整数部分和小数部分;
  • double 的指数部分是 11 位,而 float 的指数位是 8 位,意味着 double 相比 float 能表示更大的数值范围;

那二进制小数,是如何转换成二进制浮点数的呢?

我们就以 10.625 作为例子,看看这个数字在 float 里是如何存储的

imagen-20230709105220726

首先,我们计算出 10.625 的二进制小数为 1010.101。

然后把小数点,移动到第一个有效数字后面,即将 1010.101 右移 3 位成 1.010101,右移 3 位就代表 +3,左移 3 位就是 -3。

float 中的「指数位」就跟这里移动的位数有关系,把移动的位数再加上「偏移量」,float 的话偏移量是 127,加3后就是指数位的值了,即指数位这 8 位存的是 10000010(十进制 130),因此你可以认为「指数位」相当于指明了小数点在数据中的位置。

1.010101 这个数的小数点右侧的数字就是 float 里的「尾数位」,由于尾数位是 23 位,则后面要补充 0,所以最终尾数位存储的数字是 01010100000000000000000

在算指数的时候,你可能会有疑问为什么要加上偏移量呢?

前面也提到,指数可能是正数,也可能是负数,即指数是有符号的整数,而有符号整数的计算是比无符号整数麻烦的,所以为了减少不必要的麻烦,在实际存储指数的时候,需要把指数转换成无符号整数

float 的指数部分是 8 位,IEEE 标准规定单精度浮点的指数取值范围是 -126 ~ +127,于是为了把指数转换成无符号整数,就要加个偏移量,比如 float 的指数偏移量是 127,这样指数就不会出现负数了。

比如,指数如果是 8,则实际存储的指数是 8 + 127(偏移量)= 135,即把 135 转换为二进制之后再存储,而当我们需要计算实际的十进制数的时候,再把指数减去「偏移量」即可。

细心的朋友肯定发现,移动后的小数点左侧的有效位(即 1)消失了,它并没有存储到 float 里。

这是因为 IEEE 标准规定,二进制浮点数的小数点左侧只能有 1 位,并且还只能是 1,既然这一位永远都是 1,那就可以不用存起来了

于是就让 23 位尾数只存储小数部分,然后在计算时会自动把这个 1 加上,这样就可以节约 1 位的空间,尾数就能多存一位小数,相应的精度就更高了一点

那么,对于我们在从 float 的二进制浮点数转换成十进制时,要考虑到这个隐含的 1,转换公式如下:

fórmula flotante

举个例子,我们把下图这个 float 的数据转换成十进制,过程如下:

flotar a binario ejemplo.png

搞清楚了计算机是如何存储小数的我们就来看看0.1和0.2转为二进制到底是多少?

0.1 的二进制浮点数转换成十进制的结果是 0.100000001490116119384765625

0.1 número de coma flotante a decimal binario

0.2 的二进制浮点数转换成十进制的结果是 0.20000000298023223876953125

0.2 Conversión de punto flotante

这两个结果相加就是 0.300000004470348358154296875

0.1+0.2!]

所以,你会看到在计算机中 0.1 + 0.2 并不等于完整的 0.3

这主要是因为有的小数无法可以用「完整」的二进制来表示,所以计算机里只能采用近似数的方式来保存,那两个近似数相加,得到的必然也是一个近似数

我们在 JavaScript 里执行 0.1 + 0.2,你会得到下面这个结果:

imagen-20230709113009348

 console.log(0.1 + 0.2 == 0.3)  //false

我们检查 0.10.2 的总和是否为 0.3,我们会得到 false

我擦!想象一下,你创建了一个电子购物网站,如果访问者将价格为 ¥ 0.10¥ 0.20 的商品放入了他的购物车。订单总额将是 ¥ 0.30000000000000004。这会让任何人感到惊讶

解决方案

最可靠的方法是借助方法 toFixed(n) 对结果进行舍入:

let sum = 0.1 + 0.2;
alert( sum.toFixed(2) ); // "0.30"

请注意,toFixed 总是返回一个字符串。它确保小数点后有 2 位数字。如果我们有一个电子购物网站,并需要显示 ¥ 0.30,这实际上很方便。对于其他情况,我们可以使用一元加号将其强制转换为一个数字:

let sum = 0.1 + 0.2;
alert( +sum.toFixed(2) ); // 0.3

我们可以将数字临时乘以 100(或更大的数字),将其转换为整数,进行数学运算,然后再除回。当我们使用整数进行数学运算时,误差会有所减少,但仍然可以在除法中得到:

alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3
alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001

因此,乘/除法可以减少误差,但不能完全消除误差

总结

十进制小数怎么转成二进制?

十进制整数转二进制使用的是「除 2 取余法」,十进制小数使用的是「乘 2 取整法」。

计算机是怎么存小数的?

计算机是以浮点数的形式存储小数的,大多数计算机都是 IEEE 754 标准定义的浮点数格式,包含三个部分:

  • 符号位:表示数字是正数还是负数,为 0 表示正数,为 1 表示负数;
  • 指数位:指定了小数点在数据中的位置,指数可以是负数,也可以是正数,指数位的长度越长则数值的表达范围就越大;
  • 尾数位:小数点右侧的数字,也就是小数部分,比如二进制 1.0011 x 2^(-2),尾数部分就是 0011,而且尾数的长度决定了这个数的精度,因此如果要表示精度更高的小数,则就要提高尾数位的长度;

用 32 位来表示的浮点数,则称为单精度浮点数,也就是我们编程语言中的 float 变量,而用 64 位来表示的浮点数,称为双精度浮点数,也就是 double 变量。

0.1 + 0.2 == 0.3 吗?

不是的,0.1 和 0.2 这两个数字用二进制表达会是一个一直循环的二进制数,比如 0.1 的二进制表示为 0.0 0011 0011 0011… (0011 无限循环),对于计算机而言,0.1 无法精确表达,这是浮点数计算造成精度损失的根源。

因此,IEEE 754 标准定义的浮点数只能根据精度舍入,然后用「近似值」来表示该二进制,那么意味着计算机存放的小数可能不是一个真实值。

0,1 + 0,2 no es igual al 0,3 completo. Esto se debe principalmente a que estos dos decimales no se pueden expresar en binario "completo", y solo se pueden redondear de acuerdo con la precisión, por lo que la computadora solo puede usar números aproximados para guardarlos. sumamos dos números aproximados, el resultado también debe ser un número aproximado.

Supongo que te gusta

Origin juejin.im/post/7253453908038746173
Recomendado
Clasificación