2016-03-31 38 views
3

以下Arduino的(C++)代碼C++模數需要在兩個* un *符號字節之間進行減法運算,爲什麼?

void setup() 
{ 
    Serial.begin(115200); 
    byte b1 = 12; 
    byte b2 = 5; 
    const byte RING_BUFFER_SIZE = 64; 

    byte diff = b2 - b1; 
    byte diff2 = (byte)(b2 - b1) % RING_BUFFER_SIZE; //<---NOTE HOW THE (byte) CAST IS *REQUIRED* TO GET THE RIGHT RESULT!!!! 

    Serial.println(b1); 
    Serial.println(b2); 
    Serial.println(RING_BUFFER_SIZE); 
    Serial.println(diff); 
    Serial.println(diff2); 
} 

void loop() 
{  
} 

產生預期:

12 
5 
64 
249 
57 //<--correct answer 

而沒有 「(字節)」 鑄造如下所示:

void setup() 
{ 
    Serial.begin(115200); 
    byte b1 = 12; 
    byte b2 = 5; 
    const byte RING_BUFFER_SIZE = 64; 

    byte diff = b2 - b1; 
    byte diff2 = (b2 - b1) % RING_BUFFER_SIZE; //<---(byte) cast removed 

    Serial.println(b1); 
    Serial.println(b2); 
    Serial.println(RING_BUFFER_SIZE); 
    Serial.println(diff); 
    Serial.println(diff2); 
} 

void loop() 
{  
} 

它產生:

12 
5 
64 
249 
249 //<--wrong answer 

爲什麼區別?爲什麼模運算符只能使用顯式類型轉換?

注: 「字節」= 「uint8_t」

+3

另請參閱:整數促銷 – milleniumbug

+0

我會研究它。你是否看到完全預期的行爲?還是它一直困擾程序員? –

+0

你能告訴我們沒有代碼請你。 –

回答

3

5 - 12給出-7(一個int)。所以你的代碼是-7 % 64

在數學上,我們預計這會給57。但是,在C和C++中,負數的%不符合數學上的預期。相反,它滿足以下公式:

(a/b) * b + a%b == a 

現在,(-7)/640因爲C和C++使用截斷向零負陽性的整數除法。因此-7 % 64評估爲-7

最後,將-7轉換爲uint8_t給出249

當你寫(byte)-7 % 64你實際上在做249 % 64給出預期的答案。


關於b2 - b1行爲:所有整數運算中至少int精度進行;對於-的每個操作數,如果它是比int更窄的整數類型,則首先將其提升爲int(使值保持不變)。如果促銷後類型不同(可能不是這種情況),則可能會發生進一步的轉換。

在代碼中,b2 - b1表示(int)b2 - (int)b1產生int;沒有辦法指定以較低精度執行操作。

+2

因爲'b2'和'b1'都是無符號的,所以我期待一個正確的「下溢」,就像你在uint8_t 255加1時溢出到零一樣。我想減法規則不同於加法,即使這兩個數字都是* un *簽名? –

+0

@GabrielStaples增加了一節解釋 –

+0

原理也許是歷史性的:在第一個C實現中,'a-b'它只是將值加載到字大小的寄存器中並調用CPU指令進行減法。而不是跳過箍環來模擬硬件中不支持的低精度操作。後來的標準化不能打破依賴於這種行爲的程序。 –

2

算術運算想要在int或更大。所以,你的byte在被扣除之前被提升爲整數 - 而且,你很可能得到了實際的int,因爲C/C++可以保存整個範圍byte

如果減法的結果回落到byte,它會給你預期的溢出行爲。但是,如果您在diff2的計算中省略了演員陣容,則可以通過負數int執行模數。而且,由於C/C++簽署的分數輪次爲零,所以有符號模數與股息具有相同的符號。

這裏的第一個失誤是期望減法直接作用於你的byte類型,或您的簽名byte轉換成一個無符號int。級聯的問題是忽略C++簽名分區的行爲(如果你不知道你應該首先期待簽名算術是一個問題,這是可以理解的)。

請注意,如果您的RING_BUFFER_SIZE不是2的冪次,那麼對於像這樣的情況,分割將無法正確工作。並且,由於它是2的冪,所以請注意:

(b2 - b1)&(RING_BUFFER_SIZE-1) 

應該能夠正常工作。

最後(如評論所說),做一個環形緩衝區減去正確的方式將確保b1 < RING_BUFFER_SIZE(這對於一個環行緩衝器動作感),並使用類似:

(b2>b1)? b2 - b1 : RING_BUFFER_SIZE + b2 - b1 
+2

'b2

+0

是的,類似的東西是做環緩衝區減法的首選方法。 – comingstorm

+0

將M.M的評論版本添加到答案中 – comingstorm

相關問題