前言
前幾天心血來潮,想把之前用 Python 寫的“金融數字格式化”程式碼(金融數值格式化:從右往左,每隔 3 位,加一個逗號分隔,看起來清晰),重新用 Golang 寫。
寫了 2 天,終於完工了。獻醜出來,懇請各位大佬指點高效的解決方案。
整體思路
假設所有的金融數值都是以字串儲存的。(暫時不考慮如何將數值轉換為字串,數值大的時候連 int64/float64
都能輕鬆溢位,需要使用 math/big
包下,像 big.NewInt()
這樣的型別來儲存大數值)。
假如說是 Web 前端傳到 Go 後端的值,那就更簡單了。AJAX 過來的值,都是字串,根本不需要再考慮用哪個型別去儲存數值。
實現方式
暴力遍歷法。儘可能多地考慮到現實情況(永遠不要相信使用者的輸入,萬一使用者亂輸入內容呢?)
1.清洗
將這個字串進行清洗,清除掉那些無意義的字元。比如:數值前面有多餘的正/負號 (-+123456)、多餘的 0 (0000123456),小數值後面多餘的 0 (123456.1230000)。
2.檢查符號
清洗完無意義符號後,檢查這個字串是否帶有符號,有符號則提取出這個符號,到最後用於拼接。
3.校驗是否為正常的數值
已經清洗完了多餘的字元,進行校驗,判斷這個字串是否為合法的數值。數值中最多隻能有一個小數點.
、不能出現非數值字元(不考慮科學計數以及 complex 型別)。
4.針對浮點數值的小數部分操作
如果這個字串是浮點數值,把小數部分,連帶小數點全部提取出來,到最後用於拼接。
5.執行核心功能
每隔 3 位,加一個單字元的逗號,
。(程式碼來源:《Goc程式設計語言》第 3.5.4, P54, gopl.io/ch3/comma)
6.最終的拼接
按順序將:正/負號、分隔完成的字串、小數部分,拼接成一個最終的完整字串。
我的實現程式碼
package main
import (
"fmt"
"strings"
"unicode"
)
//校驗這個字串是不是合法的數值
func validateDigit(s string) bool {
dotCount := 0 //統計小數點有幾個,只能出現一個小數點
for _, v := range s {
if v == '.' {
dotCount++
if dotCount > 1 { //只允許有一個小數點存在
return false
}
continue
}
if !unicode.IsDigit(v) {
return false
}
}
return true
}
//清除多餘字元的函式
func cleanUnneededChar(price string) string {
//統計頂頭是否有多餘的正/負號,頂頭只允許一個正/負號
symbolI, symbolJ := 0, 1
for {
if symbolJ == len(price)-1 {
break
}
if price[symbolI] == '-' || price[symbolI] == '+' {
if price[symbolJ] == '-' || price[symbolJ] == '+' {
symbolI++
symbolJ++
} else {
break
}
} else {
break
}
}
price = price[symbolI:] //裁剪掉頂頭多餘的正/負號
//檢查這個字串是否帶有一個正/負號。如果帶有符號,就把符號先單獨提取出來
symbolString := ""
if price[0] == '-' || price[0] == '+' {
symbolString = string(price[0])
price = price[1:]
}
if len(price) == 1 {
return symbolString + price
}
//統計頂頭有多少個多餘的 0
zeroI := 0
for {
if price[zeroI] != '0' {
break
} else {
zeroI++
}
}
//如果這個數值不是浮點數,那麼後面的0都是不允許動的
dotIndex := strings.Index(price, ".")
if dotIndex == -1 {
if zeroI > 0 { //zeroI 是統計頂頭是否有多餘的 0,如果這個值大於了 0,說明頂頭有多餘的 0
return symbolString + price[zeroI:] //裁剪掉頂頭多餘的 0,然後帶上符號並返回
}
return symbolString + price //頂頭沒有多餘的 0,沒必要裁剪了
}
//程式碼能走到這裡說明這個肯定是浮點數了,至少是帶有小數點了
//查詢一下小數點後面有沒有字元。如果沒有字元,說明只有孤零零的一個小數點字元,那就把這個多餘的小數點清除掉
if price[dotIndex:] == "." {
price = price[zeroI:dotIndex]
}
//只有為浮點數值了,才清除掉末尾多餘的0
end := len(price) //最末的下標,往前推
for {
if price[end-1] != '0' {
if price[end-1] == '.' { //判斷一下末尾是不是還有多餘的 '.'
end--
}
break
} else {
end--
}
}
return symbolString + price[zeroI:end]
}
//核心功能:從右向左,每隔 3 位,加一個單字元的逗號 ','
func comma(s string) string {
if len(s) <= 3 {
return s
}
return comma(s[:len(s)-3]) + "," + comma(s[len(s)-3:])
}
//將一個數值字串按金融化輸出(每隔 3 位加一個逗號)
/*
思路:暴力遍歷的方式
1.清理掉字串中多餘的正/負號、多餘的0
2.如果這個字串帶有符號,則提取出這個符號,到最後用於拼接
3.已經清洗完了多餘的字元,進行校驗,判斷這個字串是否為合法的數值
4.如果這個字串是浮點數值,把小數部分,連帶小數點全部提取出來,到最後用於拼接
5.執行核心功能,每隔 3 位,加一個單字元的逗號 ','
6.按順序將:正/負號、分隔完成的字串、小數部分,拼接成一個最終的完整字串
*/
func FormatFinancialString(price string) string {
//清理掉多餘的字元。比如:浮點數末尾的0、開頭的0、多餘的正/負號
price = cleanUnneededChar(price)
//檢查這個字串是否帶有正/負號。如果帶有符號,就把符號先單獨提取出來
symbolString := ""
if price[0] == '-' || price[0] == '+' {
symbolString = string(price[0])
price = price[1:]
}
//清洗完了多餘字元,開始校驗這個數值字串
if !validateDigit(price) {
return "非法的數值!請檢查您提供的數值是否正確!數值允許是浮點數,數字的前面一位允許帶有一個正/負號!"
}
//小數點前沒有寫0,就補一個0進去補齊,讓數字字串看起來更好看
if price[0] == '.' {
return "0" + price
}
//判斷這個數字是不是浮點數值
dotIndex, decimalString := strings.Index(price, "."), ""
if dotIndex != -1 {
decimalString = price[dotIndex:]
price = price[:dotIndex]
} else if dotIndex == -1 {
dotIndex = len(price)
}
return fmt.Sprintf("%s%s%s", symbolString, comma(price[:dotIndex]), decimalString)
}
func main() {
fmt.Println(FormatFinancialString("123456789"))
fmt.Println(FormatFinancialString("-123456789"))
fmt.Println(FormatFinancialString("+123456789"))
fmt.Println("------------- 測試一些頂頭有多餘符號的字串 -------------")
fmt.Println(FormatFinancialString("-++111222333"))
fmt.Println(FormatFinancialString("+-+-111222333"))
fmt.Println(FormatFinancialString("++-"))
fmt.Println(FormatFinancialString("++1"))
fmt.Println(FormatFinancialString("--1"))
fmt.Println(FormatFinancialString("+++++++2222"))
fmt.Println(FormatFinancialString("-------33333"))
fmt.Println("----------------------------")
fmt.Println(FormatFinancialString("12345678987654.12345"))
fmt.Println(FormatFinancialString("-12345678912345.12345"))
fmt.Println(FormatFinancialString("+12345678912345.12345"))
fmt.Println("------------- 混合測試 -------------")
fmt.Println(FormatFinancialString("0000001"))
fmt.Println(FormatFinancialString("-++-+----0000001"))
fmt.Println(FormatFinancialString("+---++0000001"))
fmt.Println(FormatFinancialString("00001597530"))
fmt.Println(FormatFinancialString(".789456123"))
fmt.Println(FormatFinancialString("321654987."))
fmt.Println(FormatFinancialString("1234567.0000000"))
fmt.Println(FormatFinancialString("-1234567.1530000"))
fmt.Println(FormatFinancialString("-++000000067.00001"))
fmt.Println("------------- 測試一些含有非法字元的字串 -------------")
fmt.Println(FormatFinancialString("+1234.567.12345"))
fmt.Println(FormatFinancialString("a1234567.12345"))
fmt.Println(FormatFinancialString("+123a4567.12345"))
fmt.Println(FormatFinancialString("+12304567.123a45"))
fmt.Println(FormatFinancialString("12304567.123a45"))
fmt.Println(FormatFinancialString("中12304567.12345"))
}
執行效果
帶貨幣符號的處理
程式碼中沒有考慮到帶貨幣符號的情況,後來在網上看了現成的輪子示例時,才意識到這個問題。
遇到帶貨幣符號的情況,我個人的思路:把貨幣符號的字串與格式化後的字串拼接在一起,貨幣符號的字串放在最前面。
現成輪子
網上找到了這個現成的輪子:accounting - money and currency formatting for golang
本作品採用《CC 協議》,轉載必須註明作者和本文連結