程序运算小数时为什么会出错?

下面代码是将 0.1 累加 100 次,然后将结果输出到显示器上的 JavaScript语言程序。

首先把 0 赋值给变量i,然后在此基础上累加 100 次 0.1。

i+ = 0.1; 表示为现在的i值加 0.1。

for(k= 0;k< 100;k++){...} 表示将 {} 内包含的处理重复 100 次。

最后将累加 100 次 0.1 后的变量i的值输出到显示器上。


心算一下就能知道,0.1 累加 100 次后的结果是 10。但是,代码经过计算机程序运行后,显示器上显示的结果并不是 10。

程序没错,计算机也没有发生故障,当然,JavaScript 语言(包括其他编程语言)也没有什么问题。可为什么会出现这样的结果呢?

这时,如果考虑一下计算机处理小数的机制,就讲得通了。那么,计算机内部是如何处理小数的呢?

由于计算机内部所有的信息都是以二进制数的形式来处理的,因此在这一点上,整数和小数并无差别。不过,使用二进制数来表示整数和小数的方法却有很大的不同。

要知道计算机为何在计算时会出错,要先了解下计算机处理数据的过程,也就是cup如何进行数据运算的。下面通过二进制数转十进制数的例子简单感受下数字的转换过程:

把 1011.0011 这个有小数点的二进制数转换成十进制数。

小数点前面部分的转换只需将各数位数值和位权相乘,然后再将相乘的结果相加即可实现。那么,小数点后面的部分要如何进行转换呢?其实,它的处理和整数是一样的,将各数位的数值和位权相乘的结果相加即可。

下图就是将二进制数1011.0011转换为10进制数的过程,最后转换的结果为11.1875

二进制数小数点前面部分的位权,第 1 位是 2 的 0 次幂、第 2 位是 2 的 1 次幂 …… 以此类推。小数点后面部分的位权,第 1 位是 2 的-1 次幂、第 2 位是 2 的-2 次幂,以此类推。0 次幂前面的位的位权按照 1 次幂、2 次幂……的方式递增,0 次幂以后的位的位权按照-1 次幂、-2 次幂……的方式递减。

这一规律并不仅限于二进制数,在十进制数和十六进制数中也同样适用。既然二进制数的小数点后第 3 位是 2 的-3 次幂(0.125),第 4 位是 2 的-4 次幂(0.0625),那么小数点以后的 .0011 转换成十进制数就应该是 0.125+0.0625 = 0.1875。此外,由于整数部分的 1011 转换成十进制数是 11。因此,二进制数 1011.0011 转换成十进制数就是 11 + 0.1875 = 11.1875。

计算机运算出错的原因

计算机之所以会出现运算错误,是因为“有一些十进制数的小数无法转换成二进制数”。

例如,十进制数 0.1,就无法用二进制数正确表示,小数点后面即使有几百位也无法表示。

小数点后 4 位用二进制数表示时的数值范围为 0.0000~0.1111(因为二进制数只有0和1组成 )。因此,这里只能表示 0.5、0.25、0.125、0.0625 这四个二进制数小数点后面的位权组合而成(相加总和)的小数。将这些数值组合后能够表示的数值,即为下图中所示的无序的十进制数。

也就是说二进制数小数点后 4 位能够用二进制数表示的数值二进制数是连续的,十进制数是非连贯的。

十进制数 0 的下一位是 0.0625。因此,这中间的小数,就无法用小数点后 4 位数的二进制数来表示。

同样,0.0625 的下一位数一下子变成了 0.125。这时,如果增加二进制数小数点后面的位数,与其相对应的十进制数的个数也会增加,但不管增加多少位,2 的-x次幂怎么相加都无法得到 0.1 这个结果。

实际上,十进制数 0.1 转换成二进制后,会变成 0.00011001100…(1100 循环)这样的循环小数。这和无法用十进制数来表示 1/3 是一样的道理。1/3 就是 0.3333…,同样是循环小数。

因为无法正确表示的数值,最后都变成了近似值。计算机这个功能有限的机器设备,是无法处理无限循环的小数的。

因此,在遇到循环小数时,计算机就会根据变量数据类型所对应的长度将数值从中间截断或者四舍五入。我们知道,将 0.3333…这样的循环小数从中间截断会变成 0.333333,这时它的3倍是无法得出 1 的(结果是 0.999999),计算机运算出错的原因也是同样的道理。

遇到这样小数结果精度损失的问题,也是有解决方法的,下面提供两种方法:

解决方法一

js小数合计值精度损失的处理

使用文章中的方法,对0.1相加100次:

该算法的原理是先把两个数字的小数位数的最大长度取到,然后把每个数字都扩大10的n次方倍,这时候原来的小数都转为整数,再将两个整数相加,最后再将整数结果除以10的n次幂,得到最终结果。

解决方法二

还有一种处理方法就是利用编程语言提供的对数字四舍五入的相关方法进行处理:

以下是一个简单的可以小数运算,带优先级的四则运算计算器的C代码: ``` #include <stdio.h> #include <stdlib.h> #include <ctype.h> #define MAX_EXPR_LEN 100 double factor(void); double term(void); double expr(void); char expr_str[MAX_EXPR_LEN + 1]; int index = 0; int main(void) { printf("请输入一个四则运算表达式(带小数):\n"); fgets(expr_str, MAX_EXPR_LEN, stdin); printf("%s = %g\n", expr_str, expr()); return 0; } double factor(void) { double result; char curr_char = expr_str[index]; if (isdigit(curr_char) || curr_char == '.') { result = atof(&expr_str[index]); while (isdigit(expr_str[index]) || expr_str[index] == '.') { index++; } } else if (curr_char == '(') { index++; result = expr(); if (expr_str[index] == ')') { index++; } else { fprintf(stderr, "右括号缺失\n"); exit(EXIT_FAILURE); } } else if (curr_char == '-') { index++; result = -factor(); } else { fprintf(stderr, "无效字符: %c\n", curr_char); exit(EXIT_FAILURE); } return result; } double term(void) { double left = factor(); while (expr_str[index] == '*' || expr_str[index] == '/') { char op = expr_str[index]; index++; double right = factor(); if (op == '*') { left *= right; } else { left /= right; } } return left; } double expr(void) { double left = term(); while (expr_str[index] == '+' || expr_str[index] == '-') { char op = expr_str[index]; index++; double right = term(); if (op == '+') { left += right; } else { left -= right; } } return left; } ``` 该程序首先使用`fgets()`函数从标准输入中读入一个表达式字符串,然后使用三个函数`factor()`、`term()`和`expr()`分别计算该表达式的因子、项和表达式的值。 `factor()`函数用于计算表达式中的因子,返回一个`double`类型的值。如果当前字符是数字或小数点,那么就使用`atof()`函数将其转换为`double`类型的数值,并将`index`指向下一个非数字字符。如果当前字符是左括号,则递归调用`expr()`函数计算括号内的表达式,并确保右括号存在。如果当前字符是负号,则将其后的因子计算出来并取反。 `term()`函数用于计算表达式中的项,返回一个`double`类型的值。首先计算左边的因子,然后使用一个循环计算乘法和除法。如果当前字符是乘号,则计算右边的因子并与左边的因子相乘,否则计算右边的因子并与左边的因子相除。循环直到没有乘法或除法运算符为止。 `expr()`函数用于计算表达式的值,返回一个`double`类型的值。首先计算左边的项,然后使用一个循环计算加法和减法。如果当前字符是加号,则计算右边的项并与左边的项相加,否则计算右边的项并与左边的项相减。循环直到没有加法或减法运算符为止。 该程序使用了递归下降解析器,可以处理带优先级的四则运算表达式,包括小数计算。但该程序对输入表达式的有效性检查非常有限,如果输入的表达式格式不正确,可能导致程序出错
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值