Math 浮点数学坏了吗?

Math 浮点数学坏了吗?,math,language-agnostic,floating-point,floating-accuracy,Math,Language Agnostic,Floating Point,Floating Accuracy,考虑以下代码: 0.1+0.2->0.300000000004 为什么会出现这些不准确的情况?二进制数学是这样的。在大多数编程语言中,它基于。问题的关键是,数字在这种格式中表示为整数乘以二的幂;分母不是二的幂的有理数(如0.1,即1/10)不能精确表示 对于标准binary64格式的0.1,表示可以完全按照 0.10000000000000055115123125782702118158340451015625十进制,或 0x1.9999999999 AP-4in 相反,有理数0.1,也

考虑以下代码:

0.1+0.2->0.300000000004
为什么会出现这些不准确的情况?

二进制数学是这样的。在大多数编程语言中,它基于。问题的关键是,数字在这种格式中表示为整数乘以二的幂;分母不是二的幂的有理数(如
0.1
,即
1/10
)不能精确表示

对于标准
binary64
格式的
0.1
,表示可以完全按照

  • 0.10000000000000055115123125782702118158340451015625
    十进制,或
  • 0x1.9999999999 AP-4
    in
相反,有理数
0.1
,也就是
1/10
,可以精确地写成

  • 0.1
    十进制,或
  • 0x1.9999999999999…p-4
    类似于C99六进制浮点表示法,其中
    ..
    表示9的无休止序列
程序中的常数
0.2
0.3
也将近似于其真实值。与
0.2
最近的
double
大于有理数
0.2
,但与
0.3
最近的
double
小于有理数
0.3
0.1
0.2
之和最终大于有理数
0.3
,因此与代码中的常量不一致

需要对浮点算术问题进行相当全面的处理。有关更易于理解的解释,请参阅

旁注:所有位置(基数N)数字系统都存在此问题

普通的十进制(以10为基数)数字也有同样的问题,这就是为什么像1/3这样的数字最终会变成0.333

您刚刚偶然发现了一个数字(3/10),它恰好很容易用十进制表示,但不适合二进制。它也是双向的(在某种程度上):1/16在十进制(0.0625)中是一个丑陋的数字,但在二进制中它看起来就像10000次在十进制(0.0001)中一样整洁)**-如果我们在日常生活中习惯使用基数为2的数字系统,你甚至会看到这个数字,本能地理解你可以通过将某个数字减半来达到这个目的,一次又一次地减半

**当然,浮点数存储在内存中的方式并不完全相同(它们使用一种科学符号)。然而,它确实说明了一点,即二进制浮点精度错误往往会突然出现,因为我们通常感兴趣的“现实世界”数字往往是十的幂,但这仅仅是因为我们每天都使用十进制数字系统。这也是为什么我们会说71%而不是“7取5”(71%是一个近似值,因为5/7不能用任何十进制数精确表示)

因此,答案是否定的:二进制浮点数并没有被破坏,它们只是碰巧和其他任何N进制数字系统一样不完美:)

侧注:在编程中使用浮动

实际上,这种精度问题意味着您需要使用舍入函数将浮点数舍入到您感兴趣的小数位数,然后再显示它们

您还需要将相等性测试替换为允许一定公差的比较,这意味着:

如果(x==y){…}

而是做
if(abs(x-y)


其中,
abs
是绝对值<代码>MyTolerance值
需要为您的特定应用程序选择,这与您准备允许的“摆动空间”有很大关系,以及您要比较的最大数值可能是多少(由于精度损失问题)。注意你所选择的语言中的“epsilon”风格常量。这些值不能用作公差值。

浮点舍入错误。由于缺少5的基本因子,0.1在基数-2中不能像在基数-10中那样准确地表示。正如1/3在十进制中表示为无限位数,但在基数3中为“0.1”,0.1在基数2中表示为无限位数,而在基数10中不表示为无限位数。计算机没有无限的内存。

浮点舍入错误。发件人:

将无限多个实数压缩成有限位数需要近似表示。尽管有无限多个整数,但在大多数程序中,整数计算的结果可以存储在32位中。相反,给定任何固定的位数,大多数实数计算将产生无法用那么多位数精确表示的量。因此,浮点计算的结果必须经常四舍五入,以适应其有限表示形式。这种舍入误差是浮点计算的特征


它的破译方式与你在小学时学习的十进制(以10为基数)符号的破译方式完全相同,只是以2为基数


要理解,请考虑将1/3表示为十进制值。这是不可能做到的!同样,1/10(十进制0.1)不能用基数2(二进制)精确表示为“十进制”值;小数点后的重复模式将永远持续下去。该值不精确,因此不能使用普通浮点方法进行精确的数学运算。

< P>除了其他正确答案外,还可以考虑缩放值以避免浮点运算的问题。 例如:

var result = 1.0 + 2.0;     // result === 3.0 returns true
。。。而不是:

var result = 0.1 + 0.2;     // result === 0.3 returns false
表达式
0.1+0.2===0.3
在JavaScript中返回
false
,但幸运的是在flo中返回整数算术
var result = 0.1 + 0.2;     // result === 0.3 returns false
function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}
 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                    else { return n * 0.1 + 0.000000000000001 ;}    
0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)
0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)
public static string BinaryRepresentation(double value)
{
    long valueInLongType = BitConverter.DoubleToInt64Bits(value);
    string bits = Convert.ToString(valueInLongType, 2);
    string leadingZeros = new string('0', 64 - bits.Length);
    string binaryRepresentation = leadingZeros + bits;

    string sign = binaryRepresentation[0].ToString();
    string exponent = binaryRepresentation.Substring(1, 11);
    string mantissa = binaryRepresentation.Substring(12);

    return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}
0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010
0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
0.1 => 2^-3 *  0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 *  1.1001100110011001100110011001100110011001100110011010
sum =  2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397  = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794  = 0.200000000000000011102230246251565404236316680908203125
sum =  2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875
sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)
    = 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875
a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
  = 2^-2  * 1.0011001100110011001100110011001100110011001100110011

x = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)

b = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
  = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
    = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
0.3 => 2^-2  * 1.0011001100110011001100110011001100110011001100110011
    =  2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3       => 0:01111111101:0011001100110011001100110011001100110011001100110[011]
0.1 + 0.2 => 0.300000000000000044408920985006...
0.3       => 0.299999999999999988897769753748...
>>> 0.1 + 0.2 == 0.3
False
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
SIGN EXPONENT FRACTION
#include <stdio.h>
#include <limits.h>

void
xx(float *x)
{
    unsigned char i = sizeof(*x)*CHAR_BIT-1;
    do {
        switch (i) {
        case 31:
             printf("sign:");
             break;
        case 30:
             printf("exponent:");
             break;
        case 23:
             printf("fraction:");
             break;

        }
        char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
        printf("%d ", b);
    } while (i--);
    printf("\n");
}

void
yy(float a)
{
    int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
    int fraction = ((1<<23)-1)&(*(int*)&a);
    int exponent = (255&((*(int*)&a)>>23))-127;

    printf(sign?"positive" " ( 1+":"negative" " ( 1+");
    unsigned int i = 1<<22;
    unsigned int j = 1;
    do {
        char b=(fraction&i)!=0;
        b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
    } while (j++, i>>=1);

    printf("*2^%d", exponent);
    printf("\n");
}

void
main()
{
    float x=-3.14;
    float y=999999999;
    printf("%lu\n", sizeof(x));
    xx(&x);
    xx(&y);
    yy(x);
    yy(y);
}
-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872
999999999.999999446351872
parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
0.22 + 0.7 = 0.9199999999999999
(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9
function floatify(number){
           return parseFloat((number).toFixed(10));
        }
    
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
1/3 + 2 / 3 == 1
0.33333333 + 0.66666666 = 0.99999999
0.0001100 + 0.0011001 = 0.0100101
0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17
error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1
>>> (2**53) - int(float(2**53))
0
0.125
0.001
0.3
0.33
0.333
0.0001100110011001100110011001100110011001100110011 ...
0.00011001100110011001100110011001100110011001100110011010
2.67499999999999982236431605997495353221893310546875
>>> 0.1 + 0.2
0.30000000000000004
1/10 ~ = J / (2 ** N)
J ~ = 2 ** N / 10
>>> q + 1
7205759403792794
7205759403792794/72057594037927936
>>>. 1 * 2 ** 56
7205759403792794.0
>>> 7205759403792794 * 10 ** 30 // 2 ** 56
100000000000000005551115123125L
0.1b = 0.5d
0.01b = 0.25d
0.001b = 0.125d
0.0001b = 0.0625d
0.00001b = 0.03125d
if(a==b) ...
delta = 0.0001; // or some arbitrarily small amount
if(a - b > -delta && a - b < delta) ...