你如何处理Matlab中的精度问题?

时间:2016-05-26 21:12:20

标签: matlab floating-point precision

我正在尝试在Matlab中编写一个生日问题计算器,但是有一个精度问题(1 - 非常小的浮点数= 1)。

我目前的问题是,我想看看在网站上猜测UUID需要多少次尝试,其中有23,000,000个活动会话令牌,其中有128位可能的唯一值,因此猜测有效的几率令牌超过50%。

我首先通过以下方式模拟过程:

  1. 我将success_rate设置为(23,000,000 /(2 ^ 128))
  2. 我将failure_rate设置为(1 - success_rate)
  3. 但后来我发现这个值是1。

    更糟糕的是,将(1 - 23000000/(2^128))^n > 0.5输入Wolfram Alpha并没有提供有用的答案。

    我的第一个想法是彻底抛弃Matlab并在Java中创建我自己的库,它根本不使用浮点值,而是将比率存储为BigDecimal对象的对,这将通过仅计算来消除所有精度问题在最后一点,并将此计算存储为一对最小 - 最大值,以将结果显示为解决方案所在的范围(其中精确解不存在,因为浮点除法会导致错误和无法使用的值表示指定精度的浮点,但可以通过指定实际比率来表示精确答案,因为从不对其应用除法,而是显示比率。

    有没有办法在不必发明这样的系统的情况下处理这类问题,或者使用浮点系统无法解决这些问题?

5 个答案:

答案 0 :(得分:2)

  

......使用浮点系统本身无法解决这些问题吗?

简短说明:

嗯,默认情况下在MATLAB中是,如果在MATLAB中使用符号工具箱,则为否。

在MATLAB中,您绝对可以用双精度浮点数表示非常小的数字。但是,您遇到的问题与操作双精度浮点数有很大关系,这些浮点数相互之间的数量级太多 - 在执行计算时,您受到MATLAB计算精度的限制。

值得庆幸的是,有一个工具箱可以以符号工具箱和variable-precision arithmetic的形式缓解此问题。如果您在执行1 - (small_value)时想要获得1以外的其他内容,请注意这一点。

更长的说明:

http://www.mathworks.com/help/matlab/matlab_prog/floating-point-numbers.html#f2-98720

MATLAB中的双精度浮点数具有非常令人印象深刻的最大精度-1.79769e+308 to -2.22507e-308 and 2.22507e-308 to 1.79769e+308。但是,MATLAB仅计算最大精度为53位:精度为9.007199255×10 15。

这是我对如何产生你遇到的结果的解释(1 - small_value = 1):

数字1.234e12的精确度大约为1e16,这意味着MATLAB可以对此数字进行操作,误差大约为1e-4。 Simliarly,2.345e-7的计算误差大约为1e-23。因此,添加这两个数字将具有1e-4的误差,因此在MATLAB执行的计算误差中丢失的数字较小。

如果您不介意等待与在大于53位的数字上执行操作相关的较长计算时间,那么我强烈建议您在MATLAB中使用符号工具箱(即vpa函数)

如果我的答案不适合您,也许您可​​以在MATLAB论坛中查看此answer to a related question。我从这个答案中获取了部分样本编号。

快乐的编码,我希望这有帮助!

答案 1 :(得分:1)

轻松解释:

使用:

   eps(double(1))

在Matlab中,您会发现1(最大精度=双精度)与执行数学运算时可以区分的下一个浮点数之间的最小差距。在这种情况下,间隙等于 2.2204e-016

自:

success_rate = (23,000,000 / (2^128))

将返回6.7591e-032,并且在执行1 - 6.7591e-032时,它比上面介绍的间隙小得多.Matlab理解从1减去0,因此你总是得到1作为答案。希望它有所帮助。

答案 2 :(得分:0)

其他答案解释了为什么您无法根据所使用数字的大小差异执行所需的计算。但是,正如我在评论中提到的,您可以尝试使用较小的数字来显示趋势。我们称之为“预计”值size_of_key_space / (2 * number_of_keys)。这对于获得50%的成功概率来说是一种天真的预期价值。为了证明这是在球场,我为许多不同的键组和关键空间运行了模拟。一切都很大,稀疏程度不同:

function sparse_probability()

num_keys = logspace(2, 5, 15);  % number of keys varies from 1e2 to 1e5
key_spaces = logspace(6, 12, 15);  % size of key space varies from 1e6 to 1e12
% so p_sucess varies from 1e-4 to 1e-7

num_experiments = length(num_keys);

results = zeros(1,num_experiments);
proportions = zeros(1,num_experiments);

for i = 1:num_experiments
    num_objs = num_keys(i);
    size_of_key_space = key_spaces(i);
    p_success = num_objs/size_of_key_space;
    p_fail = 1 - p_success;

    total_fail = 1;
    num_trials = 0;
    while (total_fail > 0.5)
        total_fail = total_fail * p_fail;
        num_trials = num_trials + 1;
    end


    results(i) = num_trials;
    proportions(i) = num_trials/(size_of_key_space/(2*num_objs));
    fprintf('p_success = %f, num_trials = %d, ratio = %f, num_keys = %e; size key_space = %e\n', 1 - total_fail, num_trials, proportions(i), num_objs, size_of_key_space);
end

由于密钥集和密钥空间的大小差异很大,我计算了上面“预计”值的比率,以及实现50%概率所需的实际试验次数。上述函数的输出是:

p_success = 0.500044, num_trials = 6932, ratio = 1.386400, num_keys = 1.000000e+02; size key_space = 1.000000e+06
p_success = 0.500010, num_trials = 11353, ratio = 1.386293, num_keys = 1.637894e+02; size key_space = 2.682696e+06
p_success = 0.500006, num_trials = 18595, ratio = 1.386292, num_keys = 2.682696e+02; size key_space = 7.196857e+06
p_success = 0.500008, num_trials = 30457, ratio = 1.386309, num_keys = 4.393971e+02; size key_space = 1.930698e+07
p_success = 0.500004, num_trials = 49885, ratio = 1.386300, num_keys = 7.196857e+02; size key_space = 5.179475e+07
p_success = 0.500001, num_trials = 81706, ratio = 1.386294, num_keys = 1.178769e+03; size key_space = 1.389495e+08
p_success = 0.500001, num_trials = 133826, ratio = 1.386297, num_keys = 1.930698e+03; size key_space = 3.727594e+08
p_success = 0.500002, num_trials = 219193, ratio = 1.386298, num_keys = 3.162278e+03; size key_space = 1.000000e+09
p_success = 0.500001, num_trials = 359014, ratio = 1.386295, num_keys = 5.179475e+03; size key_space = 2.682696e+09
p_success = 0.500001, num_trials = 588027, ratio = 1.386296, num_keys = 8.483429e+03; size key_space = 7.196857e+09
p_success = 0.500000, num_trials = 963125, ratio = 1.386295, num_keys = 1.389495e+04; size key_space = 1.930698e+10
p_success = 0.500000, num_trials = 1577496, ratio = 1.386294, num_keys = 2.275846e+04; size key_space = 5.179475e+10
p_success = 0.500000, num_trials = 2583771, ratio = 1.386294, num_keys = 3.727594e+04; size key_space = 1.389495e+11
p_success = 0.500000, num_trials = 4231943, ratio = 1.386295, num_keys = 6.105402e+04; size key_space = 3.727594e+11
p_success = 0.500000, num_trials = 6931472, ratio = 1.386294, num_keys = 1.000000e+05; size key_space = 1.000000e+12

如果您要绘制比率列与关键空间的大小,您将得到一条直线。同样,只要键组和键空间相隔几个数量级,该比率基本上是恒定的。请注意,稀疏度会有所不同,但这不会影响比率。这是典型的这类稀疏概率问题。因此,通过这个简单的实验,您可以非常自信地说,在2.3e7的关键空间中2^128 = 3.4e38个密钥所需的猜测数量是{{1}以上的比率限制的乘积总计

的预计值
1.386294

猜测有效UUID的几率为50%所需的猜测。

每秒猜测1万亿次,需要3250亿年才能进行多次猜测。换句话说,你是安全的。 :)

答案 3 :(得分:0)

正如其他人所解释的那样,(1 - 23000000/2 ^ 128)太靠近一个要在双精度浮点值的53位尾数中表示,所以(1 - 230000000/2 ^ 128)^ n无法计算。

其他软件包(python + sympy,mathematica,...)可以执行任意精度计算,并且有一个可用于matlab的多精度计算工具箱。这将允许您直接执行计算。

您可以将等式重新排列为二项式扩展:

(a + b)^n = a^n + C(1,n)a^(n-1)b + C(2,n)a^(n-2)b^2 + ...

其中C(k,n)是从大小为n的池中选择k个项目的方式的数量。由于b^k对于较大的k来说很小,因此请忽略这些术语,并将其近似为:

(1 - b)^n = 1 - n b + O(b^2)

b = 23000000/2^38。求解1 - n b = 0.5的{​​{1}}会产生其他人给出的近似值n

Herbie有时可以帮助您重写方程式以提高数值稳定性。

另一个最喜欢的技巧是在你想要近似的值附近执行泰勒展开,给出一个可以在一系列输入上使用的多项式。可以使用多精度库确定多项式度和有效范围,以便您知道您的值在整个范围内精确到机器精度。 Wolfram Alpha提供在线泰勒系列计算器。

更多细节可以在以下书籍中找到:

  1. Higham NJ。数值算法的准确性和稳定性:第二版。暹; 2002。

答案 4 :(得分:0)

正如所有其他答案所指出的那样,问题是r = 3000000/(2^128) < eps(1)/2,所以1 + r == 1

最简单的方法是重新排列表达式,并在此过程中利用其他一些功能。重写:

(1 - 23000000/(2^128))^n = exp(n*log(1- 23000000/(2^128))

现在,这仍然会遇到同样的问题,但是有一个log1p函数可以准确地计算log(1+x)。所以改为使用:

exp(n*log1p(-23000000/(2^128)))