zoukankan      html  css  js  c++  java
  • js 小数运算出现误差的原因

    问题

    在javascript使用小数点 +-*/ 运算会出现误差,比如:

    0.1 + 0.2 == 0.30000000000000004
    0.2 + 0.4 == 0.6000000000000001
    19.9 * 100 == 1989.9999999999998

    其实,不仅仅只有javascript,还有java、python等都会有类似问题,因为浮点数IEEE754是被普遍使用的标准


    浮点数

    浮点数是相对于定点数来说的。

    计算机中小数的表示法,有定点和浮点两种。

    定点,即小数点固定,比如:302876512411.25 小数点固定在数字的个位数右边
    浮点,即小数点不固定(浮动),3.028e+11 小数点不固定在个位数和小数之间,而是根据指数值进行前后浮动

    可以这么理解,科学记数法就是浮点数的表示方式

    那么,我们为什么要使用浮点数呢?

    我们可以先考虑下,为什么要使用科学记数法?

    302876512411.25
    3.028e+11

    科学记数法的核心就是:通过移动小数点,只在小数点前保留一位数字,其他都算作小数,并使用指数记录小数点移动的位数

    好处是,通过指数就可以很直观的看出数值的大小(而不用个十百千万的数)

    其实它还有另一个好处,如果省略小数点后几位的话,它会显得很廋,不像原值那么臃肿(缺点是精度丢失)


    计算机中的浮点数

    因为计算机中,数值的存储是有大小限制的,比如

    单精度浮点数 float -- 4Byte -- 32bit
    双精度浮点数 double -- 8Byte -- 64bit

    问题: 如何在有限的存储空间内容,尽可能的表示更多的数值?

    使用定点数(原值),虽然保留了精度,但是能够表示的数值范围有限
    使用浮点数(科学记数法),能够表示的数值范围变广了,但是精度也丢失了

    也就是说,相同位数下

    范围和精度是不可兼得

    两害相权取其轻,微小的精度 没有 数值表示范围 显得更重要

    所以计算机中表示小数的方式就是使用了浮点数,也就有了 IEEE754标准


    二进制的定点和浮点

    定点数: 以32位存储为例 (124 + 1*2e-2)

    img
    图片来源: https://www.zhihu.com/question/19848808

    浮点数:(1.01)2 = 1.25 即 1.25 * 2e-3

    img
    图片来源: https://www.zhihu.com/question/19848808

    阶码=阶码真值+127。 (127是单精度浮点的偏移量,即 0111 1111)


    IEEE 754

    浮点数的存储格式,一般按照标准IEEE 754

    IEEE 754 规定,浮点数的表示方法为:

    img

    最高的 1 位是符号位 s,(表示正负)

    接着的 8 位是阶码真值E,(补码,计算真值需要加上偏移量)

    剩下的 23 位为尾数 M。(原码)

    float: 1 + 8 + 23 = 32

    double: 1 + 11 + 52 = 64

    例如:0.125 DEC = 1/8 = 0.001 BIN = 1 x 2^-3

    = 0 + 0111 1100 + 0000 0000 0000 0000 0000 000 = 00111110000000000000000000000000

    IEEE754换算工具: http://www.binaryconvert.com/convert_double.html


    浮点数加减乘除运算出现误差的原因

    0.125 = 1/8

    0.0625 = 1/16

    0.03125 = 1/32

    0.015625 = 1/64

    0.0078125 = 1/128

    0.00390625 = 1/256

    0.1 = 0/2 + 0/4 + 0/8 + 1/16 + 1/32 + 0/64 + 0/128 + 1/256 + 1/512 + ...

    0.1 = 0.0001 1001 1001 ...

    0.2 = 0/2 + 0/4 + 1/8 + 1/16 + 0/32 + 0/64 + 1/128 + 1/256 + 0/512 + ...

    0.1 = 0.0011 0011 0011 ...

    原因: 一个能准确表示的十进制小数,而二进制却是循环小数

    解决方法

    避免使用会清除小数的换算方式

    Math.floor
    Math.ceil

    对于整数,一般不会出错
    对于小数,出错的概率较高,可以先变为整数,再缩为小数 (0.1*10 + 0.2*10) / 10 == 0.3
    小数的加减乘除运算可以封装方法: https://blog.51cto.com/xzllff/831241

  • 相关阅读:
    javascript定义
    JavaScript学习笔记
    PostgreSQL数据库配置
    python 时间戳转时间 timestamp to time
    WebGIS:Vue+Flask+PostGIS+Geoserver开发环境配置
    GeoServer环境配置
    Vue前端环境配置
    Flask后端环境配置
    1.顺序表
    汇编语法--AT&T VS. intel
  • 原文地址:https://www.cnblogs.com/nangezi/p/15035185.html
Copyright © 2011-2022 走看看