【发布时间】:2016-04-01 15:54:54
【问题描述】:
为什么我们在编程语言中使用各种数据类型?为什么不到处使用 float ?我听说过一些论点,比如
- int 上的算术运算更快(但为什么?)
- 存储浮点数需要更多内存。 (我明白了。)
使用各种类型的数值数据类型有哪些额外的好处?
【问题讨论】:
标签: math floating-point int computer-science precision
为什么我们在编程语言中使用各种数据类型?为什么不到处使用 float ?我听说过一些论点,比如
使用各种类型的数值数据类型有哪些额外的好处?
【问题讨论】:
标签: math floating-point int computer-science precision
传统上,整数运算速度更快,因为它是一种更简单的运算。它可以在逻辑门中实现,如果设计得当,整个事情可以在一个时钟周期内完成。
在大多数现代 PC 上,浮点支持实际上非常快,因为已经投入了大量时间来使其变得更快。仅在低端处理器(如 Arduino 或某些版本的 ARM 平台)上,浮点会受到严重影响,或者 CPU 完全不存在。
浮点数包含一些不同的数据:符号位、尾数和指数。要将这三个部分放在一起以确定它们所代表的值,您可以执行以下操作:
value = sign * mantissa * 2^exponent
比这要复杂一点,因为浮点数优化了它们存储尾数的方式(例如,尾数的第一位被假定为 1,因此第一位实际上不需要存储。 .. 但这也意味着零必须以特定方式存储,并且有各种“特殊值”可以存储在浮点数中,例如“非数字”和无穷大,在使用浮点数时必须正确处理)
因此,要存储数字“3”,您的尾数为 0.75,指数为 2。(0.75 * 2^2 = 3)。
但是要将两个浮点数相加,首先必须对齐它们。例如,3 + 10:
m3 = 0.75 (stored as binary (1)1000000... the first (1) implicit and not actually stored)
e3 = 2
m10 = .625 (stored as binary (1)010000...)
e10 = 4 (.625 * 2^4 = 10)
你不能把 m3 和 m10 加在一起,因为你会得到错误的答案。您首先必须将 m3 移动几位以使 e3 和 e10 匹配,然后您可以将尾数相加并将结果重新组合成一个新的浮点数。当然,具有良好浮点实现的 CPU 将为您完成所有这些工作,而且速度很快。
那么为什么您不想对所有内容都使用浮点值呢?嗯,对于初学者来说,存在精确性 的问题。如果您将两个整数相加或相乘以得到另一个整数,只要您不超过整数大小的限制,您得到的答案将是完全正确的。浮点不是这种情况。例如:
x = 1000000000.0
y = .0000000001
for (cc = 0; cc < 1000000000; cc++) { x += y; }
从逻辑上讲,您希望 (x) 的最终值为 1000000000.1,但这几乎肯定不是您将要得到的。当您将 (y) 添加到 (x) 时,对 (x) 尾数的更改可能非常小,以至于它甚至不适合浮点数,因此 (x) 可能根本不会改变。即使不是这样,(y) 的值也不准确。没有两个整数 (a, b) 使得 (a * 2^b = 10^-10)。实际上,对于许多常见的十进制值来说都是如此。即使是像 0.3 这样简单的值也不能存储为二进制浮点数中的精确值。
所以 (y) 不完全是 10^-10,它实际上偏离了一小部分。对于 32 位浮点数,它会偏移大约 10^-26:
y = 10^-10 + error, error is about 10^-26
那么如果你把 (y) 加在一起一百亿倍,误差也被放大了大约一百亿倍,所以你的最终误差大约是 10^-16
一个好的浮点实现会尽量减少这些错误,但它并不总是正确的。这个问题是如何存储数字的根本问题,并且在某种程度上是不可避免的。因此,例如,即使将货币值存储在浮点数中似乎很自然,但最好将其存储为整数,以确保该值始终准确。
“精确性”问题还意味着,当您测试浮点数的值时,一般来说,您不能使用精确比较。例如:
x = 11.0 / 500
if (x * 50 == 1.1) { ... It doesn't!
for (float x = 0.0; x < 1.0; x += 0.01) { print x; }
// prints 101 values instead of 100, the last one being 0.9999999...
测试失败是因为 (x) 不是我们指定的值,而 1.1 在编码为浮点数时也不是我们指定的值。它们都接近,但不精确。所以你必须做不精确的比较:
if (abs(x - expected_value) < small_value) {...
选择正确的“small_value”本身就是一个问题。这可能取决于您对这些价值观所做的事情,以及您想要实现的行为类型。
最后,如果你看到“它需要更多内存”的问题,你也可以反过来考虑一下你使用的内存得到什么。
如果您可以使用整数数学来解决您的问题,那么 32 位无符号整数可以让您使用 0 到大约 40 亿之间的(精确)值。
如果您使用 32 位浮点数而不是 32 位整数,则可以存储大于 40 亿的值,但您仍然受到表示形式的限制:在这 32 位中,一个用于符号位,尾数为 8,因此您得到 23 位(有效地为 24)尾数。一旦 (x >= 2^24),您就超出了整数“精确”存储在该浮点数中的范围,因此 (x+1 = x)。所以像这样的循环:
float i;
for (i = 1600000; i < 1700000; i += 1);
永远不会终止:(i) 将达到 (2^24 = 16777216),并且其尾数的最低有效位将大于 1,因此将 1 加到 (i) 将不再有任何效果。
【讨论】: