作者:吳祐賓
多數歐洲國家的數字浮點數,會使用逗號「,」作為小數點基點。
這陣子接獲客戶反應他們開發的軟體遇上 "Invalid floating point operation" 的錯誤
雖然最後並沒有採用程式解法來撫平此問題,只是這解決過程太有趣,一定要和你分享!
現象和解法
現象很簡單,重現很困難,因為在客戶說在他的環境下無法重現此錯誤。
經過一段時間的了解,才知道應用程式是在越南使用。所以製作越南版 Windows 後就成功重現此問題。而作為曾經的法國殖民地,法國也是以逗號作為基點。
在這裡我們就進行基點的轉換來處理,以 GetLocaleInfo Win32 API 來查出基點浮號進行轉換,程式碼如下:
1 2 3 4 5 6 7 8 9 10 | // Delphi function GetLocaleDecimalMark: string; var pcLCA: array[0..18] of Char; begin if GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, LOCALE_SDECIMAL, pcLCA, 19) <= 0 then pcLCA[0] := #0; Result := string(pcLCA); end; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // C++ Builder // 取得本地端系統的小數點符號 // Function: GetLocaleDecimalMark // Return: 小數點符號 String __fastcall GetLocaleDecimalMark() { char pcLCA[19]; // 建立一個容納小數點符號的字元陣列 // 呼叫 GetLocaleInfo 函式取得系統預設的小數點符號 // LOCALE_SYSTEM_DEFAULT 表示使用本地端系統的設定 // LOCALE_SDECIMAL 是代表小數點符號的常數 // pcLCA 用來儲存取得的小數點符號 if (GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, LOCALE_SDECIMAL, pcLCA, 19) <= 0) { pcLCA[0] = '\0'; // 若取得失敗,將陣列第一個元素設為空字元 } return String(pcLCA); // 將字元陣列轉換為 String 並返回 } |
GetLocaleInfo 可以做得更多
Win32 API GetLocaleInfo 可以取得多樣化的系統資訊,經過包裝後可以使用以下程式:
1 2 3 4 5 6 7 8 9 10 11 12 | // Delphi // ShowMessage(GetLocaleInformation(LOCALE_SDECIMAL)); function GetLocaleInformation(iFlag: Integer): string; var pcLCA: array[0..18] of Char; begin if GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, iFlag, pcLCA, 19) <= 0 then pcLCA[0] := #0; Result := string(pcLCA); end; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // C++ Builder #include <System.SysUtils.hpp> // ShowMessage(GetLocaleInformation(LOCALE_SDECIMAL)); String __fastcall GetLocaleInformation(int iFlag) { char pcLCA[19]; // 建立一個容納結果的字元陣列 if (GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, iFlag, pcLCA, 19) <= 0) { pcLCA[0] = '\0'; // 若取得失敗,將陣列第一個元素設為空字元 } return String(pcLCA); // 將字元陣列轉換為 AnsiString 並返回 } |
iFlag 在 windows 單元有定義常數,可以依照自己的需求設定。常數列表如下:
LOCALE_NOUSEROVERRIDE { 不使用使用者自訂設定 } LOCALE_USE_CP_ACP { 使用系統ACP } LOCALE_ILANGUAGE { 語言代碼 } LOCALE_SLANGUAGE { 本地語言名稱 } LOCALE_SENGLANGUAGE { 語言的英文名 } LOCALE_SABBREVLANGNAME { 語言名稱縮寫 } LOCALE_SNATIVELANGNAME { 本地語言名稱 } LOCALE_ICOUNTRY { 國家代碼 } LOCALE_SCOUNTRY { 國家名 } LOCALE_SENGCOUNTRY { 國家的英文名稱 } LOCALE_SABBREVCTRYNAME { 國家名縮寫 } LOCALE_SNATIVECTRYNAME { 國家名 } LOCALE_IDEFAULTLANGUAGE { 預設語言代碼 } LOCALE_IDEFAULTCOUNTRY { 預設國家代碼 } LOCALE_IDEFAULTCODEPAGE { 預設OEM內碼表 } LOCALE_IDEFAULTANSICODEPAGE { 預設ANSI內碼表 } LOCALE_IDEFAULTMACCODEPAGE { 預設MAC頁 } LOCALE_SLIST { 列表項分隔符 } LOCALE_IMEASURE { 測量單位 0 = 公制, 1 = 英制 } LOCALE_SDECIMAL { 小數點符號 } LOCALE_STHOUSAND { 千位分隔符 } LOCALE_SGROUPING { 數字分組 } LOCALE_IDIGITS { 小數位數 } LOCALE_ILZERO { 小數前導零 } LOCALE_INEGNUMBER { 負數模式 } LOCALE_SNATIVEDIGITS { 本地ASCII數字 0-9 } LOCALE_SCURRENCY { 本地貨幣符號 } LOCALE_SINTLSYMBOL { 國際貨幣符號 } LOCALE_SMONDECIMALSEP { 貨幣小數點分隔符 } LOCALE_SMONTHOUSANDSEP { 貨幣千位分隔符 } LOCALE_SMONGROUPING { 貨幣分組 } LOCALE_ICURRDIGITS { 本地貨幣位數 } LOCALE_IINTLCURRDIGITS { 國際貨幣位數 } LOCALE_ICURRENCY { 正貨幣模式 } LOCALE_INEGCURR { 負貨幣模式 } LOCALE_SDATE { 日期分隔符 } LOCALE_STIME { 時間分隔符 } LOCALE_SSHORTDATE { 短日期字串 } LOCALE_SLONGDATE { 長日期字串 } LOCALE_STIMEFORMAT { 時間格式字串 } LOCALE_IDATE { 短日期格式順序 } LOCALE_ILDATE { 長日期格式順序 } LOCALE_ITIME { 時間格式指示符 } LOCALE_ITIMEMARKPOSN { 時間標記位置 } LOCALE_ICENTURY { 世紀格式指示符 (短日期) } LOCALE_ITLZERO { 時間欄位前導零 } LOCALE_IDAYLZERO { 日欄位前導零 (短日期) } LOCALE_IMONLZERO { 月欄位前導零 (短日期) } LOCALE_S1159 { 上午標誌 } LOCALE_S2359 { 下午標誌 } LOCALE_ICALENDARTYPE { 日曆類型指示符 } LOCALE_IOPTIONALCALENDAR { 附加日曆類型指示符 } LOCALE_IFIRSTDAYOFWEEK { 一星期的第一天指示符 } LOCALE_IFIRSTWEEKOFYEAR { 一年的第一周指示符 } LOCALE_SDAYNAME1 { 星期一的完整名稱 } LOCALE_SDAYNAME2 { 星期二的完整名稱 } LOCALE_SDAYNAME3 { 星期三的完整名稱 } LOCALE_SDAYNAME4 { 星期四的完整名稱 } LOCALE_SDAYNAME5 { 星期五的完整名稱 } LOCALE_SDAYNAME6 { 星期六的完整名稱 } LOCALE_SDAYNAME7 { 星期天的完整名稱 } LOCALE_SABBREVDAYNAME1 { 星期一的簡稱 } LOCALE_SABBREVDAYNAME2 { 星期二的簡稱 } LOCALE_SABBREVDAYNAME3 { 星期三的簡稱 } LOCALE_SABBREVDAYNAME4 { 星期四的簡稱 } LOCALE_SABBREVDAYNAME5 { 星期五的簡稱 } LOCALE_SABBREVDAYNAME6 { 星期六的簡稱 } LOCALE_SABBREVDAYNAME7 { 星期天的簡稱 } LOCALE_SMONTHNAME1 { 一月的完整名稱 } LOCALE_SMONTHNAME2 { 二月的完整名稱 } LOCALE_SMONTHNAME3 { 三月的完整名稱 } LOCALE_SMONTHNAME4 { 四月的完整名稱 } LOCALE_SMONTHNAME5 { 五月的完整名稱 } LOCALE_SMONTHNAME6 { 六月的完整名稱 } LOCALE_SMONTHNAME7 { 七月的完整名稱 } LOCALE_SMONTHNAME8 { 八月的完整名稱 } LOCALE_SMONTHNAME9 { 九月的完整名稱 } LOCALE_SMONTHNAME10 { 十月的完整名稱 } LOCALE_SMONTHNAME11 { 十一月的完整名稱 } LOCALE_SMONTHNAME12 { 十二月的完整名稱 } LOCALE_SMONTHNAME13 { 第十三個月的完整名稱 (若存在) } LOCALE_SABBREVMONTHNAME1 { 一月的簡稱 } LOCALE_SABBREVMONTHNAME2 { 二月的簡稱 } LOCALE_SABBREVMONTHNAME3 { 三月的簡稱 } LOCALE_SABBREVMONTHNAME4 { 四月的簡稱 } LOCALE_SABBREVMONTHNAME5 { 五月的簡稱 } LOCALE_SABBREVMONTHNAME6 { 六月的簡稱 } LOCALE_SABBREVMONTHNAME7 { 七月的簡稱 } LOCALE_SABBREVMONTHNAME8 { 八月的簡稱 } LOCALE_SABBREVMONTHNAME9 { 九月的簡稱 } LOCALE_SABBREVMONTHNAME10 { 十月的簡稱 } LOCALE_SABBREVMONTHNAME11 { 十一月的簡稱 } LOCALE_SABBREVMONTHNAME12 { 十二月的簡稱 } LOCALE_SABBREVMONTHNAME13 { 第十三個月的簡稱 (若存在) } LOCALE_SPOSITIVESIGN { 正數符號 } LOCALE_SNEGATIVESIGN { 負數符號 } LOCALE_IPOSSIGNPOSN { 正數符號位置 } LOCALE_INEGSIGNPOSN { 負數符號位置 } LOCALE_IPOSSYMPRECEDES { 貨幣符號在正數前面 } LOCALE_IPOSSEPBYSPACE { 貨幣符號和正數之間用空格分隔 } LOCALE_INEGSYMPRECEDES { 貨幣符號在負數前面 } LOCALE_INEGSEPBYSPACE { 貨幣符號和負數之間用空格分隔 } LOCALE_FONTSIGNATURE { 字型簽名 } LOCALE_SISO639LANGNAME { ISO 語言簡稱 } LOCALE_SISO3166CTRYNAME { ISO 國家簡稱 }
最後客戶採用解法
客戶最終採用強制指定符號顯示方式,結束這一回合。
結論
本案中透過程式技術上確實可以解決問題,只是解決過程曠日費時,而且專案修改上有一定程度上的困難,光是每次的運算處理就必須經過一道檢查,檢查點一多就難保有未改到或是有其它 Bug 產生的可能性。
在經過人的斡旋後,發現對方能接受調整基點的顯示方式,如此就能輕易解決程式難以處理的場合。誰說技術只限定在程式上?人和人之間的互動也是一門技術上的延伸。經過這次的經歷讓我了解到程式人員除了專研自身程式技術外,和人的互動也是一門必修的程式延伸課程。您說是吧?
和您分享。
沒有留言:
張貼留言