理解浮点变量

3

有些问题,我无法理解。

请看这段代码:


<script type="text/javascript">
function math(x)
{
 var y;
 y = x*10;
 alert(y);
}

</script>
<input type="button" onclick="math(0.011)">

当我点击按钮后,需要注意什么? 我认为会弹出0.11,但实际上它弹出了0.10999999999999999。 请解释一下这种行为的原因。 提前致谢。


1
好的,我明白为什么会出现这个问题。但是如何解决呢? - Simon
1
你提出了一个需要解决的问题。 - lincolnk
4个回答

9

好的,让我来解释一下。

要记住的基本事实是:浮点数占用有限的位数,并试图使用二进制算术表示原始数字。

正如您所知,在二进制算术中,整数由它们包含的2的幂表示。因此,6将被表示为4 + 2,即在二进制中表示为110。

为了理解如何表示小数,您必须考虑我们在十进制系统中如何表示小数。数字的小数部分(例如0.11)表示为10的倒数的倍数(因为基数是10)。因此,0.11实际上是1/10 + 1/100。正如您所能理解的那样,在有限数量的数字中表示所有小数是不够强大的。例如,1/3将以永远无止境的方式成为0.333333....。如果我们只有32个数字空间来写下这个数字,我们最终只会得到一个接近原始数字的近似值,即0.33333333333333333333333333333333。例如,这个数字如果乘以3而不是1,将会给出0.99999999999999999999999999999999。

在二进制中情况类似。每个小数都将表示为2的倒数的倍数。因此,0.75(十进制中的)(即3/4)将被表示为1/2 + 1/4,这意味着0.11(在二进制中)。正如十进制不足以有限方式表示每个小数一样,给定有限空间的情况下,二进制也不能表示所有小数。

现在,尝试用二进制表示0.11;你从11/100开始,并尝试找到一个比这个数略小的2的倒数次幂。1/2不行,1/4和1/8也不行。1/16符合要求,所以你在小数点后面的第4位上打一个1,并从11/100中减去1/16。你剩下19/400。现在尝试找到下一个适合描述的2的幂。1/32似乎是那个,标记小数点后的第5位并从19/400中减去1/32,得到13/800。下一个是1/64,你剩下1/1600,因此下一个是1/2048等等。因此我们得到了0.00011100001,但它继续下去;你会发现总是有一部分是分数。现在,我没有计算整个过程,但是在小数点后放入32个二进制数字后,你仍然可能有一些分数剩余(这假设所有32位空间都用于表示小数部分,但实际上并非如此)。因此,我相信你能理解结果可能与其实际值有所不同。
在你的情况下,差异是0.00000000000000001,即1/100000000000000000 = 1/10^17,我相信你知道为什么会出现这种情况。

5
这是因为你正在处理浮点数,这是浮点数运算的预期行为。
你需要做的是对该数字进行格式化。
如果想知道为什么会发生这种情况,可以参考这篇Java解释,其中也适用于此处。
在JavaScript中,所有数字都表示为64位浮点数,所以你经常会遇到这种情况。
该文章的快速概述是,浮点数试图表示一个比64位更大的值范围,因此会有一些不精确的表示,这就是你看到的情况。

@mkoryak 我还没有,我只是不明白为什么当它表示为32位浮点数时数字会改变?我现在会去阅读,我想我会理解的 :) - Simon
在JavaScript中,所有数字都以64位浮点数表示。 - Stephen Canon

1
你可以尝试使用以下代码来修复小数位数:
// fl is a float number with some nr of decimals
// d is how many decimals you want
function dec(fl, d) {
  var p = Math.pow(10, d);
  return Math.round(fl*p)/p;
}

例:

var n = 0.0012345;
console.log(dec(n,6)); // 0.001235
console.log(dec(n,5)); // 0.00123
console.log(dec(n,4)); // 0.0012
console.log(dec(n,3)); // 0.001

它的工作原理是首先将浮点数乘以10^3(1000)用于三位小数,或者10^2(100)用于两位小数。 然后对其进行四舍五入并将其除回原始大小。
Math.pow(10, d)使10^d(这意味着d将给我们1000)。

在您的情况下,请执行alert(dec(y,2));,它应该可以工作。


不,它不起作用。它“吃掉”了第一个数字。 但是你可以帮我,我只需要使用这个,看一下: function dec(fl, d) { var p = Math.pow(10, d); var c = flp10/p; alert(c); } 当我调用math(0.011,3)时,它能够工作!!!谢谢 - Simon
这对你来说没有弹出“0.11”吗?奇怪。我这边可以正常工作 :)函数 math(x) { var y = x10; alert(dec(y,2)); } 函数 dec(fl, d) { var p = Math.pow(10, d); return Math.round(flp)/p; } math(0.011); - npup
你的函数返回了0.01,而且这是预期的结果:) - Simon
请原谅,但如果您输入(0.011,2),它会返回0.01。但是在您称为math的函数中,最终将其输入为0.11(10 * 0.011)和任何小数限制器(您说您想要2)。这将返回0.11。 我认为您有点混淆了调用原始“math”函数和我提供的函数。 - npup

1

使用浮点数,您可以获得您尝试编码的数字的表示。大多数情况下,它是一个非常接近原始数字的数字。有关编码/存储浮点数的更多信息,请单击此处

注意: 如果您显示x的值,它仍然显示0.011,因为JavaScript尚未确定变量类型x。但是,在将其乘以10之后,类型被设置为浮点数(这是唯一的可能性),并且显示了舍入误差。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接