【问题标题】:Advantages and disadvantages of single numeric (float) data type [closed]单个数字(浮点)数据类型的优缺点 [关闭]
【发布时间】:2016-04-01 15:54:54
【问题描述】:

为什么我们在编程语言中使用各种数据类型?为什么不到处使用 float ?我听说过一些论点,比如

  1. int 上的算术运算更快(但为什么?)
  2. 存储浮点数需要更多内存。 (我明白了。)

使用各种类型的数值数据类型有哪些额外的好处?

【问题讨论】:

    标签: math floating-point int computer-science precision


    【解决方案1】:

    传统上,整数运算速度更快,因为它是一种更简单的运算。它可以在逻辑门中实现,如果设计得当,整个事情可以在一个时钟周期内完成。

    在大多数现代 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) 将不再有任何效果。

    【讨论】:

    • 哇。谢谢你的回答。
    • 这是非常误导的;只要结果的绝对值不超过 2^53,浮点数的整数运算就是精确的。你在这里比较苹果和橘子。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-06
    • 1970-01-01
    • 2011-06-10
    • 2012-10-26
    相关资源
    最近更新 更多