← 所有文章

FIELD NOTES / PLC 程式與控制

字串數字轉換時怎麼保留原始輸入

PLC 程式與控制作者:站長預估 7 分鐘閱讀

以 D023 教學嚴格解析字串數字,保存原文、編碼與時間,區分語法、非有限值、範圍與整數捨入失敗。

本文目錄

一 先保存原字串再決定語法

收到的數字先當作 raw_text 保存,不要一讀入就轉成 0 或覆蓋原欄位。每筆資料記錄 raw_text、encoding、received_at、parser_version 與 parse_status;轉換成功才產生 numeric_value。這樣「123abc」被拒收時仍能追查來源,不能因轉換函式失敗就把設備畫面顯示成零。

本案例採嚴格 ASCII 語法:先依契約檢查原始字串長度 1 至 12 個 ASCII 字元(包含尚未 trim 的前後空白),再依 trim=true 移除前後 ASCII 空白;允許一個可選的 ASCII + 或 -,允許至少一位整數數字與可選的小數部分,且只允許一個 ASCII 小數點。小數點後不可完全沒有數字;不接受內嵌空白、逗點或區域小數逗號。

例如「 12.30 」在 trim=true 時先變成「12.30」,解析結果是 12.30,raw_text 與 trimmed_text 分開保存;若 trim=false,則整筆標 WhitespaceRejected。字串「−2.5」中的 − 是 Unicode U+2212,不是 ASCII -;本案例不做未記錄的正規化,因此標 SyntaxRejected,若日後接受必須增加明確 parser_version。

原字串 trim政策 結果 狀態
12.30 true 12.30 Valid
-2.5 true -2.5 Valid
true EmptyRejected
123abc true SyntaxRejected
1,234.5 true GroupingRejected
NaN / Inf true NonFiniteRejected

若輸入來自 HMI,還要區分使用者按下送出前的文字與 PLC 實際收到的文字。畫面可能自動插入千分位、Unicode 符號或本地小數逗號,測試時應抓取通訊原文,不能只看畫面顯示。對「+12」要明定是否保留正號;若接受,trimmed_text 可保留 +12,raw_text 仍保存原樣,不能把兩者都稱為 canonical。

二 小數 符號與範圍先後

本篇的半值遠離零是先取最接近整數,恰好相差0.5時才選遠離零者;向零截斷則直接去除小數。先以足夠寬的 Decimal 或等效數值解析,再依契約指定捨入規則,最後檢查目標 INT 範圍 -32768 至 32767。若採半值遠離零,32767.9 會變成 32768,因超上界應拒收;若採向零截斷,32767.9 得到 32767,檢查後可接受。不要在範圍檢查前先轉成窄型別。

本案例也測試負值:-2.5 向零截斷是 -2;若採半值遠離零,-2.5 是 -3。-32768.1 向零截斷是 -32768,半值遠離零也仍是 -32768,因此兩種規則在此例都可接受。原始小數仍要保留,並把 rounding_mode 寫入結果,避免無法說明轉換差異。

範圍檢查要在捨入後執行,但解析與捨入使用寬型別。先得到 wide_value,再套用 half_away_from_zero 或 toward_zero,確認整數結果落在 -32768 至 32767;若輸入「40000」,狀態應是 RangeRejected,不可轉成 16 位整數後再宣稱合法。

輸入 解析值 轉 INT 規則 預期
32767.9 32767.9 半值遠離零後檢查 拒收,結果32768越界
32767.9 32767.9 向零截斷後檢查 接受32767(需明訂)
-2.5 -2.5 向零截斷 接受-2(需明訂)
-32768.1 -32768.1 任一指定規則 接受-32768
40000 40000 先檢查範圍 RangeRejected

小數點數量也要全字串檢查。「12.3.4」不能被當成 12.3;「.5」與「5.」是否接受要在語法規格列出,否則不同解析器會產生差異。本案例只接受至少一位整數數字與可選的小數數字,因此 .5 與 5. 均標 SyntaxRejected,若日後放寬要增加 parser_version。

若目標是工程浮點值而非整數,仍要檢查最大可接受值、有限性與單位。不要因某平台浮點能表示 Inf 就把 Inf 當合法測量;NonFiniteRejected 應和 SyntaxRejected、RangeRejected 分開,方便現場判斷來源或轉換問題。

三 編碼 長度與時間追溯

字串不只包含數字。要保存 encoding 與原始位元組長度,因為同樣看似一個字元,UTF-8 的 Unicode U+2212 負號可能占用不同位元組,設備或閘道也可能送出不可解碼資料。解碼失敗時保留 raw bytes 的雜湊或安全表示,狀態標 EncodingRejected,不可用替代字元繼續解析。

長度限制應在解析前檢查,本案例接受原始 ASCII 字串 1 至 12 個字元,包含尚未 trim 的前後空白;trim 後仍須非空。若超過 12,保留 raw_text 與長度後停止解析,不再執行昂貴的數值診斷。若規格允許前導零,trimmed_text 保存去除空白後原貌,解析結果另存 numeric_value,兩者不混稱 canonical。

時間要至少分為 received_at 與 source_timestamp。來源可能在 09:10 產生數值,09:12 才抵達;解析結果應以收到的 parser_version 與兩個時間欄位追溯。若同一 raw_text 因修訂解析器得到不同數值,不要覆寫原 numeric_value,而是建立 derived_result 並註明版本。

檢查 輸入例 狀態 保留內容
編碼 UTF-8 ASCII 12.30 Valid raw與encoding
Unicode負號 −2.5 依規格拒收 原字串與bytes
長度 13位數字 LengthRejected raw_text/長度
來源延遲 source09:10 receive09:12 Valid或Stale 兩個時間
解析器升版 同raw新規則 DerivedVersion2 舊結果不覆寫

長度與編碼檢查失敗時,狀態不能被後續的數值轉換覆蓋。先建立 ParseRecord,再依固定順序寫入第一個主原因與必要診斷旗標;NaN、Inf 在 trim 後可辨識為特殊 token,統一標 NonFiniteRejected,不當作一般語法數字。超長輸入在長度檢查後停止解析並保留 raw 與長度。

若輸入含換行或 tab,先依通訊契約判斷是分隔符還是非法字元;不能用一般 trim 把中間控制字元悄悄刪掉。刪除與拒收都要記錄 policy。

驗收資料庫至少要能回答四個問題:原文是什麼、何時收到、用哪版解析、為何接受或拒絕。只保存轉換後的數字,將無法區分設備送錯、編碼錯、語法錯與範圍錯,也不能重現一次異常批次。

四 故障判斷與測試矩陣

可照做的測試順序是:先送有效「 12.30 」、再送「-2.5」,確認 raw_text 未被 trim 覆蓋;接著送空字串、123abc、1,234.5、NaN、Inf,確認每筆都拒收且 numeric_value 為未定義;最後送 32767.9、-32768.1、40000,分別核對半值遠離零、向零截斷與範圍順序。

若顯示 123abc 變成 123,代表解析器接受前綴數字,應改為全字串匹配。若「1,234.5」在不同電腦得到不同結果,代表地域格式未固定;將 locale 依賴移出核心解析,明確只接受 ASCII 小數點或建立另一個有名稱的地域規則。

若 -2.5 變成正 2.5,先查符號語法與無號型別;若 32767.9 變成負數,先查窄型別轉換是否在範圍檢查前發生。若失敗值顯示 0,查 UI 是否把 NULL 或 error code 格式化成 0,並改為顯示 Invalid 與 reason。

測試群組 輸入 應檢查 合格條件
語法 12.30、-2.5 value與status 均Valid
空/尾碼 空字串、123abc 不得前綴接受 均Rejected
特殊值 NaN、Inf 有限性 NonFiniteRejected
界線 32767.9、-32768.1 捨入後範圍 前者依規則、後者可接受
追溯 不同encoding與延遲 raw/time/version 欄位完整

驗收記錄可用表格列出 raw bytes、解碼結果、語法狀態、工程值、目標型別、rounding_mode 與轉換後值。對 32767.9 分別跑半值遠離零和向零截斷,預期前者拒收、後者得到 32767;對 -32768.1 兩者都得到可接受的 -32768。

若同一批同時有兩種單位,先依資料契約拒收或逐筆轉換,不能把 12.30 °C 與 12.30 bar 放進同一數值欄。轉換結果要保存 source_unit、target_unit 與 conversion_rule;規則缺失時標 UnitUnknown,避免數字看似合理卻沒有物理意義。

本文提供解析規格與數值案例。實作人員仍需依目標平台的字串長度、編碼支援、數值型別與資料庫介面核對;不能把 Python 的轉換結果當作特定 PLC 指令保證。

五 驗收 FAQ 與來源

本題基準是「 12.30 」在明訂 trim=true 時得到 12.30,「-2.5」得到 -2.5;空字串、123abc、U+2212 負號、NaN、Inf 與不允許的逗點格式均拒收。32767.9 先以寬型解析,再依半值遠離零或向零規則轉 INT;前者越過上界拒收,後者得到 32767。-32768.1 依兩種規則都得到可接受的 -32768。

解析器輸出應至少包含 value、status、reason、raw_text。不要只回傳一個數值,因為 0 可能是真實測量,也可能是失敗替代值。若輸入是通訊封包文字,還要保存來源站號、訊息序號與時間;parser_version 改變時,後續重算可與舊結果分開。

FAQ1:解析失敗可以回傳 0 讓 PLC 繼續嗎?答:不可以把 0 當一般數值。應回傳未定義值與明確 reason,另依流程決定保持最後有效值或停用資料。

FAQ2:「 12.30 」是否必然合法?答:只有在 trim=true 且語法允許前後空白時才合法;若規格要求嚴格無空白,就應標 WhitespaceRejected。

FAQ3:32767.9 或 -32768.1 可以直接存成 INT 嗎?答:先以寬型解析,明訂半值遠離零或向零截斷,再檢查目標範圍;32767.9 半值遠離零成 32768 必須拒收,向零得到 32767;-32768.1 兩種規則都可得到 -32768。

FAQ4:為何要保留 encoding 與時間?答:同一可見文字可能來自不同編碼或延遲封包;raw、encoding、received_at 與 parser_version 才能重現拒收原因。

參考:Python decimal 官方文件:十進位精度、捨入與特殊值參考;非 PLC API。

參考:Python Unicode HOWTO:文字與編碼處理參考;非設備通訊格式保證。

延伸閱讀