Currency Formating - I have working code just want to show it off and hopefully get more ideas!

Godot Version

4.6.1

So I know this discussion has come up a few different times but I couldn’t find any scripts that formatted it the way I wanted for my game and I’m super inexperienced so trying to adapt them wasn’t working. My code looks clunky to me but it works for postives and negatives up to 999 trillion. After that the formatting breaks but I could easily update it to include even larger numbers if I decide my game needs it. All it does is take the total saved as the smallest incriment available (so cents in this case) and splits it up into their respective sections. I feel like someone else could come up with better but it works and makes me happy.

extends Node
#This script converts money from cents into something readable by the player for example
#new_total(15000) would result in $15.000
@export var current_cents : int
@export var current_hundreds : int
@export var current_thousands : int
@export var current_millions : int
@export var current_billions : int
@export var current_trillions : int
@export var current_total : String

func format_currancy(new_trillions : int, new_billions : int, new_millions : int, new_thousands : int, new_hundreds : int, new_cents : int):
	if new_trillions > 0:
		current_total = "$" + str(new_trillions) + "," + str(new_billions).pad_zeros(3) + "," + str(new_millions).pad_zeros(3) + "," + str(new_thousands).pad_zeros(3) + "," + str(new_hundreds).pad_zeros(3) + "." + str(new_cents).pad_zeros(3)
	elif new_billions > 0:
		current_total = "$" + str(new_billions) + "," + str(new_millions).pad_zeros(3) + "," + str(new_thousands).pad_zeros(3) + "," + str(new_hundreds).pad_zeros(3) + "." + str(new_cents).pad_zeros(3)
	elif new_millions > 0:
		current_total = "$" + str(new_millions) + "," + str(new_thousands).pad_zeros(3) + "," + str(new_hundreds).pad_zeros(3) + "." + str(new_cents).pad_zeros(3)
	elif new_thousands > 0:
		current_total = "$" + str(new_thousands) + "," + str(new_hundreds).pad_zeros(3) + "." + str(new_cents).pad_zeros(3)
	elif current_hundreds > 0:
		current_total = "$" + str(new_hundreds).pad_zeros(2) + "." + str(new_cents).pad_zeros(3)
	elif current_cents > 0:
		current_total = "$" + str(new_hundreds).pad_zeros(2) + "." + str(new_cents).pad_zeros(3)
	elif new_trillions < 0:
		current_total = "-$" + str(new_trillions - (new_trillions * 2)) + "," + str(new_billions - (new_billions * 2)).pad_zeros(3) + "," + str(new_millions - (new_millions * 2)).pad_zeros(3) + "," + str(new_thousands - (new_thousands * 2)).pad_zeros(3) + "," + str(new_hundreds - (new_hundreds * 2)).pad_zeros(3) + "." + str(new_cents - (new_cents * 2)).pad_zeros(3)
	elif new_billions < 0:
		current_total = "-$" + str(new_billions - (new_billions * 2)) + "," + str(new_millions - (new_millions * 2)).pad_zeros(3) + "," + str(new_thousands - (new_thousands * 2)).pad_zeros(3) + "," + str(new_hundreds - (new_hundreds * 2)).pad_zeros(3) + "." + str(new_cents - (new_cents * 2)).pad_zeros(3)
	elif new_millions < 0:
		current_total = "-$" + str(new_millions - (new_millions * 2)) + "," + str(new_thousands - (new_thousands * 2)).pad_zeros(3) + "," + str(new_hundreds - (new_hundreds * 2)).pad_zeros(3) + "." + str(new_cents - (new_cents * 2)).pad_zeros(3)
	elif new_thousands < 0:
		current_total = "-$" + str(new_thousands - (new_thousands * 2)) + "," + str(new_hundreds - (new_hundreds * 2)).pad_zeros(3) + "." + str(new_cents - (new_cents * 2)).pad_zeros(3)
	elif current_hundreds < 0:
		current_total = "-$" + str(new_hundreds - (new_hundreds * 2)).pad_zeros(2) + "." + str(new_cents - (new_cents * 2)).pad_zeros(3)
	elif current_cents < 0:
		current_total = "-$" + str(new_hundreds - (new_hundreds * 2)).pad_zeros(2) + "." + str(new_cents - (new_cents * 2)).pad_zeros(3)
	return current_total

func new_total(new_cents : int):
	var total_length : int = len(str(new_cents))
	if total_length > 3:
		@warning_ignore("integer_division")
		current_hundreds = new_cents / 1000
		current_cents = new_cents - (current_hundreds * 1000)
		var hundreds_length : int = len(str(current_hundreds))
		if hundreds_length > 3:
			@warning_ignore("integer_division")
			current_thousands = current_hundreds / 1000
			current_hundreds = current_hundreds - (current_thousands * 1000)
			var thousands_length : int = len(str(current_thousands))
			if thousands_length > 3:
				@warning_ignore("integer_division")
				current_millions = current_thousands / 1000
				current_thousands = current_thousands - (current_millions * 1000)
				var millions_length : int = len(str(current_millions))
				if millions_length > 3:
					@warning_ignore("integer_division")
					current_billions = current_millions / 1000
					current_millions = current_millions - (current_billions * 1000)
					var billions_length : int = len(str(current_billions))
					if billions_length > 3:
						@warning_ignore("integer_division")
						current_trillions = current_billions / 1000
						current_billions = current_billions - (current_trillions * 1000)
	else:
		current_cents = new_cents

	return format_currancy(current_trillions, current_billions, current_millions, current_thousands, current_hundreds, current_cents)

2 Likes

Wouldn’t it just be simpler to just insert thousands separators in the string itself? After formatting it as a number

2 Likes

I couldn’t help but have a little five minute play with this.

Here is my version.

func format_currency(cents: int) -> String:
	var sign_string: String = sign_group(cents)
	var dollars_string: String = dollars_group(cents)
	var cents_string: String = cents_group(cents)
	return sign_string + dollars_string + "." + cents_string


func sign_group(cents: int) -> String:
	if cents < 0:
		return "-$"
	return "$"


func dollars_group(cents: int) -> String:
	var dollars_value: int = abs(cents) / 100
	var raw_string: String = str(dollars_value)
	var formatted_string: String = ""
	while raw_string.length() > 3:
		var tail: String = raw_string.substr(raw_string.length() - 3, 3)
		formatted_string = "," + tail + formatted_string
		raw_string = raw_string.substr(0, raw_string.length() - 3)
	return raw_string + formatted_string


func cents_group(cents: int) -> String:
	var cents_value: int = abs(cents) % 100
	return str(cents_value).pad_zeros(2)

Thank you for the distraction. Back to my own bugs now.

4 Likes

You could also just use insert together with the length of the original string, counting from the right in multiples of 3, so result.insert(original_length - i * 3, ",") or similar for as long as the string is long enough, essentially from 1 to original_length / 3

So for example with 1234567890 you’d insert at the original length minus three, minus six, and minus nine, i.e. at 7, 4, and 1, which means 1,234,567,890

1 Like

Did you mean something like this:

func dollars_group(cents: int) -> String:
	var dollars_value: int = abs(cents) / 100
	var dollars_string: String = str(dollars_value)
	var remaining_length: int = dollars_string.length()
	while remaining_length > 3:
		remaining_length -= 3
		dollars_string = dollars_string.insert(remaining_length, ",")
		remaining_length += 1
	return dollars_string

Actually that is much easier to follow. Didn’t think about insert. Thank you.

Why do you need to add one at the end? That’ll mean adding at 7, 5, 3, and 1, not the correct places of 7, 4, and 1 for example, it will go as:
10 → 7 (insert) → 8 → 5 (insert) → 6 → 3 (insert) → 4 → 1 (insert)

Resulting in 1,23,45,67,890

(which is correct in Indian formatting granted, which I missed if that was the case as I misremembered it as being alternating threes and twos, i.e. 12,345,67,890)

2 Likes

Because I am adding the , to the string.

func _ready() -> void:
	var test1 = format_currency(110)
	var test2 = format_currency(1115)
	var test3 = format_currency(999999)
	prints(test1, test2, test3)

# Output
$1.10 $11.15 $9,999.99

So with 1115 cents, the remaining length is 4. So we take away 3, insert the comma remaining length in, which is 1 place into the string, add 1 to the remaining length.

OK that example wasnt great, let me try again.
99999999 cents.
999999 dollars.

Remaining length 6.
Take 3 from remaining length = 3.
Insert 3 from start a comma.
Add one to remaining length = 4

Remaining length 4.
Take 3 from remaining length = 1.
Insert 1 from start a comma.
Add one to remaining length = 2

Hmm. I think you are right! This is going to break isn’t it.

In fact my version broke with bigger numbers.

So corrected version:

func dollars_group(cents: int) -> String:
	var dollars_value: int = abs(cents) / 100
	var dollars_string: String = str(dollars_value)
	var remaining_length: int = dollars_string.length()
	while remaining_length > 3:
		remaining_length -= 3
		dollars_string = dollars_string.insert(remaining_length, ",")
	return dollars_string

Good spot and thanks. I think I was thinking because I had added to the length, but we are inserting from the left, and adding to the right. I should have tested it with a bigger number in the first place. My bad. Thank you for spotting that!

2 Likes

Ok final version and then I need to stop being distracted.

func dollars_group(cents: int) -> String:
	var dollars_value: int = abs(cents) / 100
	var dollars_string: String = str(dollars_value)
	var original_length: int = dollars_string.length()
	var comma_groups_count: int = int((original_length - 1) / 3.0)

	for i in range(1, comma_groups_count + 1):
		var insert_pos: int = original_length - (i * 3)
		dollars_string = dollars_string.insert(insert_pos, ",")

	return dollars_string

Testing:

func _ready() -> void:
	var test0 = format_currency(0)
	var test1 = format_currency(10)
	var test2 = format_currency(1115)
	var test3 = format_currency(1234567)
	var test4 = format_currency(999999999999999)
	prints(test0, test1, test2, test3, test4)

# Output
# $0.00 $0.10 $11.15 $12,345.67 $9,999,999,999,999.99

Again you were right, this is a much more readable approach. Thanks again.

3 Likes

Or just use regex and be done in 3 lines of code:

func to_currency(value: float, prefix: String = "$", separator: String = ",") -> String:
	var re := RegEx.create_from_string(r"\d(?=(\d{3})+(?!\d))")	
	var result := re.sub("%.2f"%abs(value), "$0" + separator, true)
	return ("-" if value < 0.0 else "") + prefix + result 

Test:

for i in 20:
	var value: float = [-1.0, 1.0].pick_random() * randf_range(0.0, 10.0 ** randi_range(0, 10))
	print("%f -> %s"%[value, to_currency(value)])
-933.841292 -> -$933.84
-54822770034.623680 -> -$54,822,770,034.62
-686756982086.887450 -> -$686,756,982,086.89
837779437.846055 -> $837,779,437.85
95.092971 -> $95.09
-9896125320869191700.000000 -> -$9,896,125,320,869,191,700.00
1496355176194.326900 -> $1,496,355,176,194.33
150456341374771170.000000 -> $150,456,341,374,771,170.00
0.780911 -> $0.78
1021317430.021002 -> $1,021,317,430.02
-10944878.437084 -> -$10,944,878.44
3437974975694934.000000 -> $3,437,974,975,694,934.00
18692693.359245 -> $18,692,693.36
3980328.682994 -> $3,980,328.68
98.882776 -> $98.88
-67916570163531006000.000000 -> -$67,916,570,163,531,006,000.00
-964873.570487 -> -$964,873.57
657390474028946.120000 -> $657,390,474,028,946.12
-593463.414739 -> -$593,463.41
-6271724.337250 -> -$6,271,724.34
4 Likes

Probably and I did try that. This is where me being incredibly inexperienced comes into play. I could not get insert to work nor could I understand the issue I was having with it enough to troubleshoot it. So I took what I consider a brute force approach as I do with most of my coding.

1 Like

Again really, really inexperienced, I’ve never seen that before just now.

Oh not meant to be criticism, asked because I didn’t want to assume you hadn’t tried something like that already

1 Like

Oh no worries I’m actually really appreciative of everyone’s comments you guys have been a lot nicer to me and my code than I anticipated. I knew someone with more experience would be able to do it better or at least have better ideas. I’ll definitely come back to this post when I move on from the prototype for this game and redo some of my scripts.

4 Likes

Welp instead of continuing to work on my game I fell down the regex rabbit hole and now my number_logic global script does everything I need it to in under 20 lines of code. Including but not limited to formatting currency and holding and formatting dates.

Huge shout out to normalized for pointing me in the direction of regex. Huge help and I ended up stealing their regex and well their code just made minor edits, but I understand it now and I didn’t two weeks ago so I’m happy with that. and because I like showing off here’s my current number_logic script:

extends Node

@export var current_date : int = Time.get_unix_time_from_datetime_string("1999-03-22")

func to_currency(value : float) -> String:
	var regex := RegEx.create_from_string(r"\d(?=(\d{3})+(?!\d))")
	return ("\u002D" if value < 0.0 else "") + ("\u00A2" if value < 1.0 and value > -1.0 else "\u0024") + regex.sub("%.3f"%abs(value), "$0" + "\u002C", true).pad_decimals(3)

func to_number(value : float) -> String:
	var regex := RegEx.create_from_string(r"\d(?=(\d{3})+(?!\d))")
	return ("\u002D" if value < 0.0 else "") + regex.sub("%.3f"%abs(value), "$0" + "\u002C", true).pad_decimals(3)

func add_days(days : int) -> int:
	current_date += days * (24 * 60 * 60)
	return current_date

func to_date(date : int) -> String:
	var regex := RegEx.create_from_string(r"\d{4}-\d{2}-\d{2}")
	return regex.search(Time.get_datetime_string_from_unix_time(date)).get_string()

#test

func _ready() -> void:
	print(NumberLogic.to_currency(500000000000000000000.99012))
	print(NumberLogic.to_number(050000.99012))
	print(NumberLogic.to_date(NumberLogic.current_date))
	NumberLogic.add_days(7)
	print(NumberLogic.to_date(NumberLogic.current_date))

#prints
#$500,000,000,000,000,000,000.000
#50,000.990
#1999-03-22
#1999-03-29

edit to add: if you’re questioning the unicode use I just really wanted the cent symbol to be used when it applied and I prefer to use one way of referring to something in my code so if the cent symbol had to be unicode it all had to be unicode idk just looked better to me then having one use of unicode

2 Likes

You were right. Learned regex and yeah the number logic script I used to have was +200 lines. Now after implementing regex it’s under 20 lines. So yeah huge help you’re awesome man.

2 Likes

Spending time in that rabbit hole won’t be in vain.
I consider regex to be the best human invention after fire and the wheel :smiley: It’s immensely useful and quite fun once you get over the initial learning hump.

2 Likes

Hard agree there. Not having to look at that massive block of text anymore feels so relieving. Will definitely be using it a lot more often in the future

2 Likes