Golang 格式化金融数字

功能描述

使用 Golang,对一个金融数字进行格式化显示,使其看起来易读。
在金融数据显示的时候,往往看到的是:一个数字,每隔3位用一个逗号隔开。
如:
1234567890987 -> 1,234,567,890,987
123456789.123 -> 123,456,789.123
-614616 -> -614,616
-1234.05 -> -1,234.05
+5201314 -> 5,201,314
+007530159 -> 7,530,159
-007530159123.56 -> -7,530,159,123.56


现成的轮子

网上找到了这个现成的轮子:accounting - money and currency formatting for golang


我个人实现的整体思路

1.数值的保存方式

假设所有的金融数值都是以字符串保存的。(暂时不考虑如何将数值转换为字符串,数值大的时候连 int64/float64 都能轻松溢出,需要使用 math/big 包下,像 big.NewInt() 这样的类型来保存大数值)。
假如说是 Web 前端传到 Go 后端的值,那就更简单了。AJAX 过来的值,都是字符串,根本不需要再考虑用哪个类型去保存数值。

2.实现方式

暴力遍历法。尽可能多地考虑到现实情况(永远不要相信用户的输入,万一用户乱输入内容呢?)。

2.1 清洗

将这个字符串进行清洗,清除掉那些无意义的字符。比如:数值前面有多余的正/负号 ("-+123456")、多余的 0 (“0000123456”),小数值后面多余的 0 (“123456.1230000”)。

2.2 检查符号

清洗完无意义符号后,检查这个字符串是否带有符号,有符号则提取出这个符号,到最后用于拼接。

2.3 校验是否为正常的数值

已经清洗完了多余的字符,进行校验,判断这个字符串是否为合法的数值。数值中最多只能有一个小数点.、不能出现非数值字符(不考虑科学计数以及 complex 类型)。

2.4 针对浮点数值的小数部分操作

如果这个字符串是浮点数值,把小数部分,连带小数点全部提取出来,到最后用于拼接。

2.5 执行核心功能

每隔 3 位,加一个单字符的逗号 ,。(代码来源:《Goc 程序设计语言》第 3.5.4, P54, comma 示例)

2.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.已经清洗完了多余的字符,进行校验,判断这个字符串是否为合法的数值。数值中最多只能有一个小数点`.`、数值中不能出现非数值字符(不考虑科学计数以及 complex 类型)。
4.如果这个字符串是浮点数值,把小数部分,连带小数点全部提取出来,到最后用于拼接。
5.执行核心功能,每隔 3 位,加一个单字符的逗号 `,`(代码来源:《Goc 程序设计语言》第 3.5.4, P54, gopl.io/ch3/comma)。
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"))
}

运行效果:
在这里插入图片描述
遇到带货币符号的情况,我个人的思路:把货币符号的字符串与格式化后的字符串拼接在一起,货币符号的字符串放在最前面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值