Golang 对金融数字的格式化
前言
前几天心血来潮,想把之前用 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, 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 协议》,转载必须注明作者和本文链接
推荐文章: