MATLAB: 你不知道的12個基礎知識,經驗分享


MATLAB: 你不知道的12個基礎知識,經驗分享

1.

MATLAB: 你不知道的12個基礎知識,經驗分享

我們知道,

MATLAB: 你不知道的12個基礎知識,經驗分享

是一個小數點後無限位的無理數,計算機是無法精確表示的。所以,在MATLAB中, pi只是一個近似值,3.141592653589793,精確到小數點後15位

<code>pi == 3.141592653589793
% ans = 1/<code>

2. sin(pi)≠0

因為


MATLAB: 你不知道的12個基礎知識,經驗分享

,導致的一個問題是,sin(pi)並不精確的等於0。事實上,所有的sin(n*pi)都不為0,且互不相等。

<code>sin(pi)
% ans = 1.224646799147353e-16
sin(2*pi)
% ans = -2.449293598294706e-16
sin(3*pi)
% ans = 3.673940397442059e-16/<code>

甚至可以看到,當n足夠大時,sin(n*pi)會接近於±1.

<code>sin(4e16*pi)
% ans = -0.999478704149319/<code>

正確的計算sin(n*pi)的方法是,利用sinpi函數。這是一個R2018b推出的新函數,能夠精確計算形如sin(pi*x)的值。

<code>sinpi(1) == 0
% ans = 1
sinpi(4e16) == 0
% ans = 1
sinpi(1/4) == sqrt(2)/2
% ans = 1/<code>

如果是早期版本的MATLAB,就只能將弧度制轉換成角度制後,用sind函數計算了。

<code>sind(180) == 0
% ans = 1
sind(180*4e16) == 0
% ans = 1
sind(45) == sqrt(2)/2
% ans = 1/<code>

3. 0.7/0.1≠7 但0.2/0.1=2

<code>0.7/0.1 == 7
% ans =0/<code>

這是由於浮點數的表示機制決定的,計算機是二進制的,在表示某些小數時,並不是精確表示的。由於二進制的限制,計算機無法精確的表示0.7。

<code>format long
0.7/0.1
% ans = 6.999999999999999/<code>

事實上,計算機中,所有的數不是連續的,而是離散的,每個浮點數和下一個更大的浮點數之間都存在一個較小的間隔,這個間隔可以用eps函數來計算。

例如,1234567890.0123456789最後一位的9是無效的,因為即使是雙精度double也無法精確到最後一位。

<code>1234567890.0123456789 == 1234567890.012345678
% ans = 1
% 左邊的數比右邊的數多了最後一位的9,但仍然是相等的。/<code>

那麼,如果遇到類似的問題時,如何輸出正確的判斷結果呢?一種可行的方法是全部轉換為整數

<code>(0.7*10)/(0.1*10) == 7
% ans = 1/<code>

或者,求兩個數的差值,看差值的絕對值是否足夠小。如果精度要求不高的話,可以自己設定一個較小的閾值

<code>abs(0.7/0.1 - 7) <= 1e-6
% ans = 1/<code>

如果精度要求高的話,可藉助於eps函數

<code>abs(0.7/0.1 - 7) <= eps(7)
% ans = 1/<code>

4.矩陣轉置.' 與' 的區別

大多數教程、網上博客、甚至書籍裡面都會提到,MATLAB的矩陣轉置運算是" ' "符號。事實上,這是一個很大的誤區。A'是矩陣A的共軛轉置,只不過大多數情況下,A虛部為0,無法體現出共軛。

<code>A = [1, 2; 3, 4];
A'
% ans = [1, 3; 2, 4]/<code>

但是,如果A是複數矩陣,就會出現共軛。

<code>A = [1+2i, 3+4i; 5+6i, 7+8i];
A'
% ans = [1-2i,5-6i; 3-4i,7-8i];/<code>

正確的轉置符號是" .' "

<code>A = [1+2i, 3+4i; 5+6i, 7+8i];
A.'
% ans = [1+2i,5+6i; 3+4i,7+8i];/<code>

5. 每次生成相同的隨機數

在知乎上面有很多類似的問題,如何保證每次生成的隨機數相同。我看到很多回答都是說,將生成的隨機數save成本地mat文件,然後在下次要用的時候,用load函數讀取mat文件中的值。這種方法也不是不可以,但是效率比較低,文件讀寫可能會花費大量的時間。

事實上,MATLAB生成的隨機數都是偽隨機數,也就是說,按照某種特定的算法生成的。這意味著,隨機數的生成過程是可以復現的,可以通過控制隨機數生成器的"種子",確保每次生成相同的隨機數。

rng函數用於控制隨機數的生成

<code>% 保存當前隨機數生成器的狀態("種子") 

scurr = rng();
% 生成隨機數
A = randn([1,4])
% A = [-0.4611 0.1132 1.3442 -0.4842];

% 每次想要生成相同的隨機數時
% 先用rng函數將生成器的狀態置為上次保存的scurr
rng(scurr);
A = randn([1,4])
% A = [-0.4611 0.1132 1.3442 -0.4842];/<code>

6. 如何生成類似於A1,A2,....,A100的變量

在知乎上也有很多類似的問題,紛紛表示用手一個一個的敲進去太累了。大部分的回答是,用eval、feval之類的函數。但是eval函數的效率很低,而且代碼的可讀性會很差。

事實是,絕大部分情況下,並不需要生成A1,A2,...,A100這樣的變量。真正需要做的是,改變自己的編程思路,給數組A增加一個額外的維度,用這個維度來表示A1到A100。

<code>% 例如A1到A100,
% An每個變量的值為[n,n+1,...,n+99]
% 用eval函數可以完成上述賦值
for ii = 1:100
eval(['A' num2str(ii) '=' num2str(ii) ':' num2str(ii+99)]);
end

% 事實上,只需要給A多一個維度就可以了
% 這樣當你想要使用A1變量時,只需要調用A(1,:)就可以了

for ii = 1:100
A(ii,:) = ii:ii+99;
end/<code>

如果A1,A2,...A100每個變量的長度不一樣,不能放到一個矩陣裡面怎麼辦?改用cell數組

<code>% 例如A1到A100,
% An每個變量的值為[n,n+1,...,100]
% 用eval函數可以生成完成上述賦值
for ii = 1:100
eval(['A' num2str(ii) '=' num2str(ii) ':' num2str(100)]);
end

% 使用cell元胞數組完成類似的賦值
% 當你想使用A1變量時,只需要調用A{1}
for ii = 1:100
A{ii} = ii:100;
end/<code>

7. i 和j都是MATLAB內置函數(built-in function)

在上面的例子,我在for循環裡面的循環變量用的ii,而不是常用的i,這是為什麼呢?因為在MATLAB中,i是一個內置函數,代表的是虛數單位(j也是),用於輸入複數。

<code>% 確保當前工作區沒有i,j變量
clear i j
i == j
% ans = 1/<code>

當然可以將i和j重載成變量,但是重載內置函數不是一個好的編程習慣,同時也會帶來運行速度上的降低。而且,一旦程序中涉及輸入複數,就可能會出現錯誤。

<code>% 下面的代碼中,想實現的是複數1+2i
% 但實際上,A = [3,5,7,9,11]
a = 1;
b = 2;
for i = 1:5
A(i) = a + b*i;
end/<code>

那麼,為了避免這個問題,一種良好的編程習慣就是,將循環變量i,j改成ii,jj。在寫虛數單位時,用1i,1j代替i,j.

<code>for ii = 1:5
A(ii) = a + b*1i;
end/<code>

8. dbstop if error真的很好用

當我作為一個MATLAB beginner第一次知道這個命令時,感覺彷彿迎來了春天,給我帶來的震撼不亞於第一次接觸bsxfun。

在調試MATLAB程序時,如果有錯誤,我們經常需要在出錯的那一行加上斷點,然後重新運行腳本或函數。這樣程序會運行到斷點前,我們就可以觀察出錯前各個變量的值,進而找到bug。這一過程需要經歷運行,出錯,加斷點,再運行這一過程。

如果在MATLAB命令行輸入dbstop if error,然後再運行程序,這樣程序會自動停在出錯的那一行,這時可以直接觀察各個變量的值,省去了自己加斷點的過程。而且,不需要每次運行程序前都輸入dbstop if error,只需要輸入一次就OK。

如果想要在每次warning前暫停程序,也可以在命令行輸入dbstop if warning

9. 調試程序時在不同的函數之間傳遞值

我們知道,如果想在想讓一個值在各個函數都使用,可以將其設為全局變量(global),此時聲明瞭該全局變量的函數,可以互相傳遞這個值。

那麼,如果是在調試時,程序已經運行了一半,停在了某個斷點前,想將函數內當前的某個值傳遞到上一個函數,或者直接傳遞到基礎工作區,(用於後續對變量值的仔細分析)怎麼辦?(基礎工作區就是,MATLAB的主函數空間,當程序運行完後的工作區)

解決方法是使用assignin函數。

assignin函數能夠在基礎工作區('base'),或者調用當前函數的函數工作區('caller')內,產生一個新的變量,該變量的值等於當前函數空間內的某個值。(比較繞口,仔細看下面例子吧)

例如,下面的函數myfun, 函數本身是用於畫圖的,不會返回任何值。

<code>function myfun()
a = randn([1,1e4]);
plot(a);
end/<code>

第3行前有一個斷點,程序停在斷點前。這時,我們可以看到a的值,但我們希望將a的值導入到基礎工作區,進行後續的分析。(一旦我們退出調試,或者繼續運行程序,都無法得到a的值。當然,可以修改myfun,給它添加一個返回值a,然後重新運行程序。但是,如果是一個複雜的程序,花了很多時間程序才運行到這個位置,重新開始意味著前功盡棄。)

這時,我們可以在調試時,在MATLAB命令行中輸入下列命令

<code>% 在基礎工作區生成一個變量a_debug
% a_debug的值與a的值相等
assignin('base','a_debug',a)/<code>

這樣,在退出當前調試後,能夠在基礎工作區看到一個a_debug變量,它的值等於myfun函數中a的值。

10.退出MATLAB時自動保存當前工作空間的變量

如果當前路徑中存在finish.m文件,matlab在退出前,會自動運行finish.m文件。通過編寫finish文件,即可實現對當前工作空間變量的保存。

<code>save 'C:\\Users\\matlab.mat' /<code>

新建腳本文件,將上述命令複製過去,保存為finish.m文件。這樣當你退出MATLAB時,就會自動將當前工作區內的變量全部保存到C:\\Users\\matlab.mat文件中。

注意:

  • 如果想要當前文件夾位於任何文件夾時均能觸發finish.m腳本的運行,將其保存在MATLABPATH文件夾中,具體請使用path命令查看
  • 退出matlab時,右上角的"×",命令行輸入quit, exit均能自動觸發finish.m腳本的運行。
  • 命令行輸入quit force時,強制退出matlab,不會執行finish.m腳本

11. 啟動MATLAB時自動讀取上次保存的工作空間變量

在MATLAB默認的工作文件夾,編寫startup.m腳本,MATLAB在啟動時會自動運行該腳本。

<code>load 'C:\\Users\\matlab.mat' /<code>

startup.m文件必須位於MATLAB啟動後的默認文件夾內

注意,

  • MATLAB的默認文件夾可以更改

12. 使用pcode對MATLAB代碼進行模糊處理

有時,我們希望自己的代碼別人能夠使用,但不能看到源代碼,這是可以通過pcode命令簡單實現這一操作。

MATLAB在運行.m程序文件時,事實上是先將.m文件(文本文件)轉換成.p文件(二進制文件)。p文件一般是隱藏文件,在文件夾中是無法看到的。我們可以通過pcode命令將m文件轉換成p文件。將p文件轉給第三方使用,但對方無法輕易看到源代碼。

<code>% 下列命令會在當前文件夾內生成myfun.p
% myfun.p與myfun.m具有相同的功能

pcode myfun/<code>

注意:

  • p文件的優先級高於m文件,如果函數名相同,matlab會優先使用p文件。
  • p文件不夠安全,不能將其視為加密,只是一個簡單的模糊處理。

更新:

  1. p文件是可以被逆向的,至少MathWorks就可以。
  2. 網上流傳過p文件逆向的小工具,這些工具要麼被刪,要麼已不適用現在的版本。
  3. 上古版本的MATLAB可以對p文件進行斷點調試,某種程度上可以推測出源碼。
  4. MATLAB軟件本身也使用了很多p文件,說明MathWorks認為p文件沒有那麼容易被破解。
  5. 如果代碼涉及知識產權或商業機密,p文件是不安全的,妥善的方法是將代碼部署在遠程服務器上。

感謝點贊、關注、收藏,您的支持是我回復的最大動力~哦


分享到:


相關文章: