冒号运算符在MATLAB中是如何工作的?

xggvc2p6  于 2023-01-21  发布在  Matlab
关注(0)|答案(1)|浏览(172)

this answer by Sam Robertsthis other answer by gnovice中所述,MATLAB的冒号运算符(start:step:stop)创建值向量的方式与linspace不同。
冒号运算符将增量加到起始点,并从结束点减去减量以到达中间点。通过这种方式,它确保输出向量尽可能对称。
然而,官方文件有关这一点从数学工作已被删除,从他们的网站。
如果萨姆的描述是正确的,那么步长的误差不是对称的吗?

>> step = 1/3;
>> C = 0:step:5;
>> diff(C) - step
ans =
   1.0e-15 *
  Columns 1 through 10
         0         0    0.0555   -0.0555   -0.0555    0.1665   -0.2776    0.6106   -0.2776    0.1665
  Columns 11 through 15
    0.1665   -0.2776   -0.2776    0.6106   -0.2776

关于冒号运算符需要注意的有趣事项:

  • 其值取决于其长度:
>> step = 1/3;
>> C = 0:step:5;
>> X = 0:step:3;
>> C(1:10) - X
ans =
   1.0e-15 *
         0         0         0         0         0   -0.2220         0   -0.4441    0.4441         0
  • 如果对重复值进行四舍五入,则会生成重复值:
>> E = 1-eps : eps/4 : 1+eps;
>> E-1
ans =
   1.0e-15 *
   -0.2220   -0.2220   -0.1110         0         0         0         0    0.2220    0.2220
  • 最后一个值存在公差,如果步长创建的值刚好高于终点,则仍使用该终点值:
>> A = 0 : step : 5-2*eps(5)
A =
  Columns 1 through 10
         0    0.3333    0.6667    1.0000    1.3333    1.6667    2.0000    2.3333    2.6667    3.0000
  Columns 11 through 16
    3.3333    3.6667    4.0000    4.3333    4.6667    5.0000
>> A(end) == 5 - 2*eps(5)
ans =
  logical
   1
>> step*15 - 5
ans =
     0
r1zk6ea1

r1zk6ea11#

Sam's answer引用的被删除的页面仍然是archived by the Way Back Machine,幸运的是,连附带的M文件colonop也在,而且看起来这个函数仍然符合MATLAB的功能(我在R2017a上):

>> all(0:step:5 == colonop(0,step,5))
ans =
  logical
   1
>> all(-pi:pi/21:pi == colonop(-pi,pi/21,pi))
ans =
  logical
   1

这里我将重复函数在一般情况下的作用(生成整数向量和处理特殊情况有一些快捷方式),我将用更有意义的变量名替换函数的变量名,输入为startstepstop
首先,它计算startstop之间有多少步。如果最后一步超出stop的距离大于容差,则不执行该步:

n = round((stop-start)/step);
tol = 2.0*eps*max(abs(start),abs(stop));
sig = sign(step);
if sig*(start+n*step - stop) > tol
  n = n - 1;
end

这解释了问题中最后一项观察结果。
接下来,它计算最后一个元素的值,并确保它不超过stop值,即使在前面的计算中允许它超过该值。

last = start + n*step;
if sig*(last-stop) > -tol
   last = stop;
end

这就是为什么问题中向量A中的lasat值实际上具有stop值作为最后一个值。
接下来,它分两部分计算输出数组,正如所宣传的那样:独立地填充阵列的左半部分和右半部分:

out = zeros(1,n+1);
k = 0:floor(n/2);
out(1+k) = start + k*step;
out(n+1-k) = last - k*step;

注意,它们不是通过递增来填充的,而是通过计算一个整数数组并将其乘以步长来填充的,就像linspace一样。这解释了问题中对数组E的观察。不同之处在于数组的右半部分是通过从last值中减去这些值来填充的。
最后一步,对于奇数大小的数组,将单独计算中间值,以确保它正好位于两个端点的中间:

if mod(n,2) == 0
   out(n/2+1) = (start+last)/2;
end

完整的函数colonop复制在底部。
请注意,分别填充数组的左侧和右侧并不意味着步长中的误差应该完全对称。这些误差由舍入误差给出。但是,如果步长没有精确到达stop点,则会产生差异,如问题中的数组A。在这种情况下,在阵列的中间而不是在末端采用稍短的步长:

>> step=1/3;
>> A = 0 : step : 5-2*eps(5);
>> A/step-(0:15)
ans =
   1.0e-14 *
  Columns 1 through 10
         0         0         0         0         0         0         0   -0.0888   -0.4441   -0.5329
  Columns 11 through 16
   -0.3553   -0.3553   -0.5329   -0.5329   -0.3553   -0.5329

但是,即使在stop点正好到达的情况下,中间也会累积一些额外的误差,以问题中的数组C为例,这种误差累积不会发生在linspace中:

C = 0:1/3:5;
lims = eps(C);
subplot(2,1,1)
plot(diff(C)-1/3,'o-')
hold on
plot(lims,'k:')
plot(-lims,'k:')
plot([1,15],[0,0],'k:')
ylabel('error')
title('0:1/3:5')
L = linspace(0,5,16);
subplot(2,1,2)
plot(diff(L)-1/3,'x-')
hold on
plot(lims,'k:')
plot(-lims,'k:')
plot([1,15],[0,0],'k:')
title('linspace(0,5,16)')
ylabel('error')

一个月十九个月:

function out = colonop(start,step,stop)
% COLONOP  Demonstrate how the built-in a:d:b is constructed.
%
%   v = colonop(a,b) constructs v = a:1:b.
%   v = colonop(a,d,b) constructs v = a:d:b.
%
%   v = a:d:b is not constructed using repeated addition.  If the
%   textual representation of d in the source code cannot be
%   exactly represented in binary floating point, then repeated
%   addition will appear to have accumlated roundoff error.  In
%   some cases, d may be so small that the floating point number
%   nearest a+d is actually a.  Here are two imporant examples.
%
%   v = 1-eps : eps/4 : 1+eps is the nine floating point numbers
%   closest to v = 1 + (-4:1:4)*eps/4.  Since the spacing of the
%   floating point numbers between 1-eps and 1 is eps/2 and the
%   spacing between 1 and 1+eps is eps,
%   v = [1-eps 1-eps 1-eps/2 1 1 1 1 1+eps 1+eps].
%
%   Even though 0.01 is not exactly represented in binary,
%   v = -1 : 0.01 : 1 consists of 201 floating points numbers
%   centered symmetrically about zero.
%
%   Ideally, in exact arithmetic, for b > a and d > 0,
%   v = a:d:b should be the vector of length n+1 generated by
%   v = a + (0:n)*d where n = floor((b-a)/d).
%   In floating point arithmetic, the delicate computatations
%   are the value of n, the value of the right hand end point,
%   c = a+n*d, and symmetry about the mid-point.

if nargin < 3
    stop = step;
    step = 1;
end

tol = 2.0*eps*max(abs(start),abs(stop));
sig = sign(step);

% Exceptional cases.

if ~isfinite(start) || ~isfinite(step) || ~isfinite(stop)
   out = NaN;
   return
elseif step == 0 || start < stop && step < 0 || stop < start && step > 0
   % Result is empty.
   out = zeros(1,0);
   return
end

% n = number of intervals = length(v) - 1.

if start == floor(start) && step == 1
   % Consecutive integers.
   n = floor(stop) - start;
elseif start == floor(start) && step == floor(step)
   % Integers with spacing > 1.
   q = floor(start/step);
   r = start - q*step;
   n = floor((stop-r)/step) - q;
else
   % General case.
   n = round((stop-start)/step);
   if sig*(start+n*step - stop) > tol
      n = n - 1;
   end
end

% last = right hand end point.

last = start + n*step;
if sig*(last-stop) > -tol
   last = stop;
end

% out should be symmetric about the mid-point.

out = zeros(1,n+1);
k = 0:floor(n/2);
out(1+k) = start + k*step;
out(n+1-k) = last - k*step;
if mod(n,2) == 0
   out(n/2+1) = (start+last)/2;
end

相关问题