简体   繁体   English

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

[英]How does MATLAB's colon operator work?

As noted in this answer by Sam Roberts and this other answer by gnovice , MATLAB's colon operator ( start:step:stop ) creates a vector of values in a different way that linspace does. 正如Sam Roberts的 回答和gnovice的其他答案所述 ,MATLAB的冒号运算符( start:step:stop )以与linspace不同的方式创建值向量。 In particular, Sam Roberts states: Sam Roberts特别指出:

The colon operator adds increments to the starting point, and subtracts decrements from the end point to reach a middle point. 冒号运算符将增量添加到起始点,并从结束点减去减量以达到中间点。 In this way, it ensures that the output vector is as symmetric as possible. 这样,它确保输出向量尽可能对称。

However, offical documentation about this from The MathWorks has been deleted from their site. 但是,有关The MathWorks的官方文档已从其网站中删除。

If Sam's description is correct, wouldn't the errors in the step sizes be symmetric? 如果Sam的描述是正确的,步长中的错误不会是对称的吗?

>> 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

Interesting things to note about the colon operator: 关于冒号运算符的有趣事项:

  • Its values depend on its length: 它的值取决于它的长度:

     >> 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 
  • It can generate repeated values if they are rounded: 如果它们被舍入,它可以生成重复值:

     >> 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 
  • There is a tolerance for the last value, if the step size creates a value just above the end, this end value is still used: 最后一个值有一个容差,如果步长在末尾创建一个值,则仍然使用此结束值:

     >> 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 

The deleted page referred to by Sam's answer is still archived by the Way Back Machine . Sam回答中提到的删除页面仍由Way Back Machine归档 Luckily, even the attached M-file colonop is there too. 幸运的是,即使附加的M文件colonop也存在。 And it seems that this function still matches what MATLAB does (I'm on R2017a): 似乎这个函数仍然匹配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

I'll replicate here what the function does for the general case (there are some shortcuts for generating integer vectors and handling special cases). 我将在这里复制函数对一般情况的作用(有一些生成整数向量和处理特殊情况的快捷方式)。 I'm replacing the function's variable names with more meaningful ones. 我正在用更有意义的变量替换函数的变量名。 The inputs are start , step and stop . 输入是startstepstop

First it computes how many steps there are in between start and stop . 首先,它计算startstop之间的步数。 If the last step exceeds stop by more than a tolerance, it is not taken: 如果最后一步超过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

This explains the last observation mentioned in the question. 这解释了问题中提到的最后一个观察结果。

Next, it computes the value of the last element, and makes sure that it does not exceed the stop value, even if it allowed to go past it in the previous computation. 接下来,它计算最后一个元素的值,并确保它不超过stop值,即使它允许在先前的计算中超过它。

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

This is why the lasat value in the vector A in the question actually has the stop value as the last value. 这就是为什么问题中向量A中的lasat值实际上具有stop值作为最后一个值的原因。

Next, it computes the output array in two parts, as advertised: the left and right halves of the array are filled independently: 接下来,它按两个部分计算输出数组,如公布的那样:数组的左半部分和右半部分是独立填充的:

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

Note that they are not filled by incrementing, but by computing an integer array and multiplying it by the step size, just like linspace does. 请注意,它们不是通过递增来填充,而是通过计算整数数组并将其乘以步长来填充,就像linspace一样。 This exaplains the observation about array E in the question. 这使得在问题中关于阵列E的观察成为可能。 The difference is that the right half of the array is filled by subtracting those values from the last value. 不同之处在于通过从last值中减去这些值来填充数组的右半部分。

As a final step, for odd-sized arrays, the middle value is computed separately to ensure it lies exactly half-way the two end points: 作为最后一步,对于奇数大小的数组,中间值是单独计算的,以确保它恰好位于两个端点的中间位置:

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

The full function colonop is copied at the bottom. 完整函数colonop复制在底部。


Note that filling the left and right side of the array separately does not mean that the errors in step sizes should be perfectly symmetric. 请注意,分别填充数组的左侧和右侧并不意味着步长中的错误应该是完全对称的。 These errors are given by roundoff errors. 这些错误由舍入误差给出。 But it does make a difference where the stop point is not reached exactly by the step size, as in the case of array A in the question. 但是,如果没有完全按步长来达到stop点,那么确实会产生差异,就像问题中数组A的情况一样。 In this case, the slightly shorter step size is taken in the middle of the array, rather than at the end: 在这种情况下,稍微缩短的步长是在数组的中间,而不是在结尾:

>> 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

But even in the case where the stop point is reached exactly, some additional error accumulates in the middle. 但即使在精确到达stop点的情况下,也会在中间累积一些额外的误差。 Take for example the array C in the question. 以问题中的数组C为例。 This error accumulation does not happen with linspace : 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')

上面的代码输出


colonop : colonop

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

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

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