简体   繁体   English

c中的浮点错误

[英]floating point inaccuracies in c

I know floating point values are limited in the numbers the can express accurately and i have found many sites that describe why this happens. 我知道浮点值受到可以准确表达的数字的限制,而且我发现了许多描述这种情况发生的站点。 But i have not found any information of how to deal with this problem efficiently. 但我还没有找到任何有关如何有效处理此问题的信息。 But I'm sure NASA isn't OK with 0.2/0.1 = 0.199999. 但是我确信NASA不能接受0.2 / 0.1 = 0.199999。 Example: 例:

#include <stdio.h>
#include <float.h> 
#include <math.h>

int main(void)
{
    float number = 4.20;
    float denominator = 0.25;

    printf("number = %f\n", number);
    printf("denominator = %f\n", denominator);
    printf("quotient as a float = %f should be 16.8\n", number/denominator);
    printf("the remainder of 4.20 / 0.25 = %f\n", number - ((int) number/denominator)*denominator);
    printf("now if i divide 0.20000 by 0.1 i get %f not 2\n", ( number - ((int) number/denominator)*denominator)/0.1);
}

output: 输出:

number = 4.200000
denominator = 0.250000
quotient as a float = 16.799999 should be 16.8
the remainder of 4.20 / 0.25 = 0.200000
now if i divide 0.20000 by 0.1 i get 1.999998 not 2

So how do i do arithmetic with floats (or decimals or doubles) and get accurate results. 因此,我该如何使用浮点数(或小数或双精度数)进行算术运算并获得准确的结果。 Hope i haven't just missed something super obvious. 希望我不仅错过了一些非常明显的事情。 Any help would be awesome! 任何帮助都是极好的! Thanks. 谢谢。

The solution is to not use floats for applications where you can't accept roundoff errors. 解决方案是不要对不能接受舍入错误的应用程序使用浮点数。 Use an extended precision library (aka arbitrary precision library) like GNU MP Bignum . 使用扩展精度库(又称任意精度库),例如GNU MP Bignum See this Wikipedia page for a nice list of arbitrary-precision libraries. 有关任意精度库的详细列表,请参见此Wikipedia页面 See also the Wikipedia article on rational data types and this thread for more info. 另请参阅有关合理数据类型此线程的Wikipedia文章,以了解更多信息。

If you are going to use floating point representations ( float , double , etc.) then write code using accepted methods for dealing with roundoff errors (eg, avoiding == ). 如果要使用浮点表示形式( floatdouble等),则使用可接受的方法处理舍入错误(例如,避免== )编写代码。 There's lots of on-line literature about how to do this and the methods vary widely depending on the application and algorithms involved. 有关如何执行此操作的在线文献很多,并且方法会根据所涉及的应用程序和算法而变化很大。

Floating point is pretty fine, most of the time. 大多数时候,浮点数还不错。 Here are the key things I try to keep in mind: 这是我要牢记的关键事项:

  • There's really a big difference between float and double . floatdouble之间确实有很大的区别。 double gives you enough precision for most things, most of the time; 在大多数情况下, double可以为大多数事情提供足够的精度; float surprisingly often gives you not enough. 出人意料的float常常给你带来的不足。 Unless you know what you're doing and have a really good reason, just always use double . 除非您知道自己在做什么,并且有充分的理由,否则请始终使用double

  • There are some things that floating point is not good for. 有些事情浮点不好。 Although C doesn't support it natively, fixed point is often a good alternative. 尽管C本机不支持它,但定点通常是一个很好的选择。 You're essentially using fixed point if you do your financial calculations in cents rather than dollars -- that is, if you use an int or a long int representing pennies, and remember to put a decimal point two places from the right when it's time to print out as dollars. 如果您以美分而不是美元来进行财务计算,那么实际上就是在使用定点,也就是说,如果您使用的是int或美分的long int ,请记住在时间到时将小数点放在右边的两位以美元打印。

  • The algorithm you use can really matter. 您使用的算法确实很重要。 Naïve or "obvious" algorithms can easily end up magnifying the effects of roundoff error, while more sophisticated algorithms minimize them. 幼稚或“显而易见”的算法可以轻易地放大舍入误差的影响,而更复杂的算法则将其最小化。 One simple example is that the order you add up floating-point numbers can matter. 一个简单的例子是,您将浮点数相加的顺序可能很重要。

  • Never worry about 16.8 versus 16.799999. 不用担心16.8和16.799999。 That sort of thing always happens, but it's not a problem, unless you make it a problem. 这类事情总是会发生,但这不是问题,除非您将其视为问题。 If you want one place past the decimal, just print it using %.1f , and printf will round it for you. 如果要在小数点后一位,请使用%.1f打印,而printf会为您取整。 (Also don't try to compare floating-point numbers for exact equality, but I assume you've heard that by now.) (也不要尝试比较浮点数是否完全相等,但我认为您现在已经听说过。)

  • Related to the above, remember that 0.1 is not representable exactly in binary (just as 1/3 is not representable exactly in decimal). 与上述相关,请记住,0.1不能精确地用二进制表示(就像1/3不能精确地用十进制表示)。 This is just one of many reasons that you'll always get what look like tiny roundoff "errors", even though they're perfectly normal and needn't cause problems. 这只是您总是会看到微小的舍入“错误”的众多原因之一,即使它们是完全正常的并且不需要引起问题也是如此。

  • Occasionally you need a multiple precision (MP or "bignum") library, which can represent numbers to arbitrary precision, but these are (relatively) slow and (relatively) cumbersome to use, and fortunately you usually don't need them. 有时,您需要一个多精度(MP或“ bignum”)库,该库可以将数字表示为任意精度,但是使用这些函数(相对)较慢且(相对)麻烦,而且幸运的是,您通常不需要它们。 But it's good to know they exist, and if you're a math nurd they can be a lot of fun to use. 但是,很高兴知道它们的存在,如果您是一个数学家,那么使用它们会很有趣。

  • Occasionally a library for representing rational numbers is useful. 有时,用于表示有理数的库很有用。 Such a library represents, for example, the number 1/3 as the pair of numbers (1, 3), so it doesn't have the inaccuracies inherent in trying to represent that number as 0.333333333. 例如,这样的库将数字1/3表示为一对数字(1、3),因此在尝试将该数字表示为0.333333333时,它没有固有的准确性。

Others have recommended the paper What Every Computer Scientist Should Know About Floating-Point Arithmetic , which is very good, and the standard reference, although it's long and fairly technical. 其他人推荐了每篇计算机科学家应该知道的有关浮点算术的文章 ,尽管它是篇幅相当长且相当技术性的文章,但它还是很好参考书, 也是标准参考书。 An easier and shorter read I can recommend is this handout from a class I used to teach: https://www.eskimo.com/~scs/cclass/handouts/sciprog.html#precision . 我推荐的一个更简单,简短的阅读方法是我以前教过的一课的讲义: https : //www.eskimo.com/~scs/cclass/handouts/sciprog.html#precision This is a little dated by now, but it should get you started on the basics. 到目前为止,这还有些过时,但是应该可以帮助您入门。

There's isn't a good answer and it's often a problem. 没有一个好的答案,这通常是一个问题。

If data is integral, eg amounts of money in cents, then store it as integers, which can mean a double that is constrained to hold an integer number of cents rather than a rational number of dollars. 如果数据是整数,例如,钱的数量,则将其存储为整数,这意味着双精度数必须限制为容纳整数的美分而不是有理数的美元。 But that only helps in a few circumstances. 但这仅在少数情况下有用。

As a general rule, you get inaccuracies when trying to divide by numbers that are close to zero. 通常,当您尝试除以接近零的数字时会出现误差。 So you just have to write the algorithms to avoid or suppress such operations. 因此,您只需要编写算法来避免或禁止此类操作。 There are lots of discussions of "numerically stable" versus "unstable" algorithms and it's too big a subject to do justice to it here. 关于“数值稳定”算法和“不稳定”算法的讨论很多,这里有一个太大的话题不能公正对待。 And then, usually, it's best to treat floating point numbers as though they have small random errors. 然后,通常最好将浮点数视为具有很小的随机误差。 If they ultimately represent measurements of analogue values in the real world, there must be a certain tolerance or inaccuracy in them anyway. 如果它们最终代表了现实世界中模拟值的度量,则无论如何它们都必须具有一定的容忍度或不准确性。

If you are doing maths rather than processing data, simply don't use C or C++. 如果您是在做数学而不是在处理数据,请不要使用C或C ++。 Use a symbolic algebra package such a Maple, which stores values such as sqrt(2) as an expression rather than a floating point number, so sqrt(2) * sqrt(2) will always give exactly 2, rather than a number very close to 2. 使用诸如Maple之类的符号代数包,该包将诸如sqrt(2)之类的值存储为表达式而不是浮点数,因此sqrt(2)* sqrt(2)将始终给出正好2,而不是非常接近的数字至2。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM