Format Numbers?

Is there a way to format numbers with a comma separator for thousands? When a numeric value is say 10 thousand it shows the number as 10,000 instead of 10000.

Also, can extra characters, like a dollar sign, be added in a way other than {“$”+NumberProperty}?

There might be an easier way, but off the top of my head the best I can think of is to use a Calculated Property: "$" + (value >= 1000 ? value / 1000 + "," + value % 1000 : value).

Note that this only works for values less than a million; if you might go over a million, it gets more complicated.

1 Like

I was afraid such formatting would require a formula rather than some notation to apply a format like comma or currency like in a spreadsheet.

Oops. I just realized that won’t work properly if the value is between 1000 and 1099, inclusive. Out of time, I’ll think about it some more later.

We really need support for String.format() in BeanShell!

For now, values in my module will be less than 20,000, and mostly less than 10,000. So it’s not a big deal.

Perhaps another avenue of attack would be to treat the values as strings, determine the length (number of digits), and then use substring methods to insert the separators.

Ex: After finding that Value is 5 digits long and converting the number represented by Value to a string: {“$”+Value.substring(1,2)+“,”+Value.substring(3,5)}

For now I’ll live with no commas in the numbers.

"$" + (value >= 1000 ? value / 1000 + "," + (value % 1000 < 100 ? "0" + (value % 1000 < 10 ? "0" : "") : "") : "") + value % 1000

I think that mess will work properly with any value less than 1 million, but it sure ain’t pretty. :stuck_out_tongue:

If you prefer to use substring(), that would be "$" + (value >= 1000 ? value.substring(0,value.length() - 3) + "," + value.substring(value.length() - 3) : value). Not really any better, is it?

(And I’m not sure I got that right, and it also only works for values less than 1 million.)

If only we could use String.format(), the solution would be as simple as {String.format("$%,d",value)}. (I think I got that syntax right…)

(FYI, I edited that formula multiple times because I realized I’d screwed it up. :smiley: )

1 Like

Thanks for doing the heavy lifting on this.

Edit: My suggested revision immediately below is wrong. The expression by @jrwatts above is likely correct as is. I will be experimenting, and I will report the results.
I think there is one subtle error in the expression though. I’m thinking the part that is meant to return the last three digits should be value.substring(value.length() - 2). So I’m thinking the expression should be "$" + (value >= 1000 ? value.substring(0,value.length() - 3) + "," + value.substring(value.length() - 2) : value). As you said this would work for numbers less than one million, and I’m pretty sure that the highest value in an 18xx game will be less than 100,000. So this will work.

My original question was asking if there was something like the String.format() to use, but your quick response told me “not yet.”

What about the Number.tolocalstring() method? I saw that when searching for number formatting in javascript. The discussion indicated that using the parameter “en-US” in this method would put the digit separator in the number as it converted it to a string per the US convention, a comma between every set of three digits right to left.

I found this discussion interesting: https://code-boxx.com/add-comma-to-numbers-javascript/

I like to use multiple simple expressions rather than one big, complicated expression. So I was thinking to use a Calculated Property to convert the numeric value to a string and a second Calculated Property to apply the expression to imbed the currency symbol and digit separators. Then I could reference the second Calculated Property with the message format. If I need to put these Calculated Properties in multiple prototypes, I could define a prototype with just them and nest that prototype where needed.

The indexing for the start position in String.substring() is zero-based, so I think my version is correct, but I haven’t actually tried it; I could be wrong.

There are 2 problems with that:

  1. The actual method is Number.toLocaleString()
  2. It’s a JavaScript method, not Java! Completely different animal.

This doesn’t make any sense. Every VASSAL property is already stored as a string, it just gets treated as a number where it makes sense to do so.

1 Like

For a number of five digits, let’s say 12345, the first character would be at position zero and the second at position one. So the group before the comma would be 12 with 1 at position zero, defined in the expression as 0 and with 2 at position one, defined in the expression as value.length() - 3 (4 - 3 = 1). This part of the expression is straight from your post yesterday. The group after the comma would be 345 with 3 at position 2, defined in my revised expression as `value.length() - 2’ (4 - 2 = 2) and going to the end of the string.

Or am I misunderstanding where the substring stops when both start and stop parameters are given?

It’s screwy; the start position is zero-based, but the stop position is either one-based, or it’s zero-based but the first character to not include in the substring. And, value.length() - 3 is 5 - 3 = 2 for a 5-digit number.

1 Like

It just occurred to me that the substring() solution may require enabling the new “Preserve leading zeros in properties” Global Option to actually work correctly. You’ll have to experiment to be certain. I’m afraid that without that BeanShell will be too aggressive about converting “000” to just “0”.

A Java teaching site explained that the start position is the same but the stop position is one past where you really want it to stop. So if Value is “abcde”, then to get “ab”, you need to start at position zero and stop at position two. “a” is at position zero, "b’ is at position 1, and "c’ is at position two.

Concerning the trailing zeros, I’ll give it a try and see what happens.

Thanks again for your help.

Just remembered I already had a test module ready to go, so I plugged in the substring() formula and it works just fine (in 3.6.7) without messing with Global Options.

1 Like

And finally, reading that article about Number.toLocaleString() made me think of the most compact solution: {"$" + value.replaceAll("(\\d{1,3})(\\d{3})","$1,$2")}. Again, only works for values less than 1 million (theoretically, a more complicated version could be written that works for any arbitrary number, but that would require a lot more effort!).
This is using a regular expression pattern to look for 1 to 3 digits followed by 3 digits, then adding a comma between.

I tested this in my sample module and it works!

1 Like

Didn’t try yesterday’s expression, but the more compact expression works great!!! :heart_eyes:

Thanks again!

1 Like