最後結果放得下 不代表中間乘積放得下
同樣算 3×100÷8,先乘再除得到整數37,先算3÷8卻可能先變0,最後仍為0。數學上等價的式子,到了有限位寬與整數除法就不一定等價。這篇教你在每一步標上型別、範圍與捨棄資訊,讓比例計算不再只靠幾筆正常值判斷。
案例採有號整數,除法規則明訂為向零截斷;16位範圍為−32768~32767,32位範圍為−2147483648~2147483647。這是推算契約,不宣稱所有PLC運算指令都採相同暫存位寬。實作要先查目標CPU、指令與語言的中間結果規則。
| 寫法 | 中間結果 | 最後整數 | 差異原因 |
|---|---|---|---|
| (3×100)÷8 | 300 | 37 | 37.5在最後截斷 |
| (3÷8)×100 | 0 | 0 | 比例先被截掉 |
| 3×(100÷8) | 12 | 36 | 100÷8先失去0.5 |
這不是鼓勵所有式子都先乘。先乘通常減少過早截斷,卻增加中間溢位風險;你要同時控制精度與範圍。若比例必須保留37.5,可以改成放大10倍存375,但那也需要重新計算中間乘積,而不是只把畫面小數位加一。
把正負算例逐步攤開
先以寬型別的理想整數運算推導,再決定實作型別。負數不能直接套只針對正數的補0.5技巧,本篇一律向零截斷,讓−37.5得到−37。若需求是最近整數或向負無限大,應另定規格,不要混在比例公式裡。
| a | b | c | (a×b)÷c | (a÷c)×b |
|---|---|---|---|---|
| 3 | 100 | 8 | 37 | 0 |
| −3 | 100 | 8 | −37 | 0 |
| 7 | 10 | 3 | 23 | 20 |
| −7 | 10 | 3 | −23 | −20 |
| 8 | 100 | 8 | 100 | 100 |
最後一列兩種順序剛好相同,因此它不適合單獨拿來證明程式正確。測試資料要含有餘數的比例、負值以及上下界,不要只選可整除的漂亮數字。記錄的對象也要包含中間乘積,否則錯誤可能早已發生,最後一個暫存器只剩難以解釋的結果。
整數常數同樣影響型別。若編譯器把常數與輸入當作窄整數,中間乘法可能先在窄範圍計算,再指派到寬結果。把結果變數改成DINT不必然等於整個運算已使用DINT;應在乘法前明確擴寬運算元,並依平台文件確認。
一個結果很小但中間很大的案例
令 a=30000、b=100、c=1000。正確寬整數乘積是3000000,最後結果3000,完全落在16位範圍;但中間乘積超過32767。如果你的實作把乘積存在16位,資訊會先失真。用更大的目的變數接收最後3000,無法補救已經算壞的乘積。
| 檢查位置 | 預期值 | 需要的判斷 |
|---|---|---|
| a | 30000 | 可放16位 |
| b | 100 | 可放16位 |
| a×b | 3000000 | 不可放16位;可放32位 |
| 除以1000 | 3000 | 結果可放16位,但須先確認中間值 |
| 窄化回16位 | 3000 | 轉換前再次檢查範圍 |
取得輸入最大最小值,而不是只看目前監看值。
在乘法前把運算元轉到足夠寬的型別;中間暫存也用同樣位寬。
先檢查除數不為0,並處理最小負整數除以−1的溢位邊界。
做除法後依需求取整,最後才窄化輸出。
保存原值、乘積、商、餘數或錯誤旗標,讓診斷能指出失敗在哪一步。
32位也有上限。例如50000×50000=2500000000,超過32位有號最大值,即使之後再除50000仍只得到50000。選擇更寬型別前要確認平台支援,不能把「改成32位」當所有比例公式的終點。
何時選固定小數 何時選浮點
若資料解析度固定為0.1,可用整數單位表示十分之一,並把倍率放進欄位名稱或資料字典;輸入3、倍率1000、除數8,得到375,解讀成37.5。這種方式容易定義小數精度,但倍率會放大中間乘積。數值跨度很大或演算法有多段比例時,可以評估浮點,仍須接受二進位表示與取整邊界。
| 需求 | 可評估方法 | 不能忘記 |
|---|---|---|
| 固定一位小數 | 以0.1為整數單位 | 倍率、位寬和寫回格式 |
| 跨很大數值範圍 | REAL/LREAL | 有效位數與目標支援 |
| 比例常數可約分 | 先合法約分再乘 | 不能用近似除法任意重排 |
| 需完全可重現尾數 | 明訂固定小數與取整 | 每一步的溢位與負數行為 |
CODESYS相關官方說明提醒,中間結果可能按目標原生位寬計算,指派或顯式轉換時才截取到目標型別。因此「兩個16位數相乘一定以16位溢位」也不能泛稱所有平台;本篇16位失真案例是明確限制乘積暫存為16位的反例。你的驗收要以選定CPU與編譯器輸出為準。
參考:CODESYS Numeric constants 整數與浮點常數
把一個比例式拆成可驗收的運算鏈
拿到比例需求後,先寫原始單位與目標單位。例如原始計數要換成千分比,倍率就屬於公式的一部分,不是顯示設定。接著列出輸入最大值、最小值、乘數與除數範圍,逐段計算可能出現的最大絕對值。只有這些資料齊全,才能決定中間型別。
用本篇三萬乘一百再除一千的例子,在監看區分別放原值、擴寬後值、乘積與商。紙上預期乘積為三百萬,商為三千。如果原值正常而乘積已經異常,就先查乘法的運算型別與暫存位置;不要往後調整除數,因為那只會把錯誤重新包裝成另一個數字。
接著測不能整除的輸入。例如三乘一百再除八,最後有半個單位不能放在整數結果中。把這半個單位的去向寫進規格:直接捨去、依明訂規則取整,或以更小的整數單位保存。三種方式都能設計,但不能在不同分支使用不同方式卻沿用同一個欄位名稱。
負數測試要和正數成對。若需求允許反向流量或負溫度,就不能只測正值。本文明訂除法向零截斷,負的非整除結果會往零靠近。若實際平台使用其他規則,應先調整實作或規格,不能把符號差異當成感測器零點誤差而任意補償。
除數也可能由參數提供,因此每次採用新參數前都要檢查。除數為零時停止正常運算,保留原始資料並回報原因;不要先做除法才看結果是否合理。如果分母原本是很小的浮點數,雖然不等於零,也可能把結果放大到不可接受範圍,需另訂有效範圍。
完成紀錄時要同時保存公式版本與參數版本。只保存最後的商,之後很難分辨是公式順序改變、倍率修改,還是型別窄化造成差異。先把運算鏈在離線資料上逐段復算,再到目標工程軟體核對相同輸入;沒有執行的測試欄位應保持未測,不要填入推算值冒充實測。
完成結果與故障排查
完成後應能復算37、0、36三個順序結果,負數案例方向一致,30000×100÷1000得到3000,除數0則回報錯誤而不執行。將50000×50000列為寬度檢查案例,不要真的交給未保護的窄運算測試。
| 症狀 | 先查哪裡 |
|---|---|
| 比例突然全為0 | 是否在乘法前做整數除法 |
| 小值正常 大值變負數 | 中間乘積位寬與窄化位置 |
| 負數與正數不對稱 | 截斷/取整規則是否一致 |
| 只有某CPU結果不同 | 原生暫存寬度、常數型別與編譯版本 |
問:先乘一定較準嗎?它通常延後截斷,但可能先溢位,必須一併檢查。問:結果用DINT就夠嗎?要在運算前確保中間型別也足夠,不能只改接收欄位。問:除數0時回傳0好嗎?只有規格明訂且另帶無效旗標才可用,否則0會被當成正常結果。
適用型號與限制:本篇為一般整數比例設計,具體平台行為參考CODESYS/ABB官方文件。Q06UDVCPU的16位與32位乘除指令、目的裝置佔用字數及溢位狀態另依三菱手冊核對;本文沒有提供可直接下載的Q程式。