Scott Hutchinson
Scott Hutchinson

Reputation: 1721

How to use sprintf to format an integer with commas as thousands separators

How does one use sprintf to format an integer to display commas as thousands separators?

I know how to do it using String.Format, but I can't find a way to do it using sprintf.

EDIT: Based on Fyodor Soikin's comment below, I tried this code:

printf "%a" (fun writer (value:int) -> writer.Write("{0:#,#}", value)) 10042
let s = sprintf "%a" (fun writer (value:int) -> writer.Write("{0:#,#}", value)) 10042

The printf call works (but writes to standard output, whereas I want to get a string I can assign to the Text or Content property of a WPF control, for instance).

The sprintf call fails with error FS0039: The field, constructor or member 'Write' is not defined.

If I could fix this error, then this might be the most direct solution possible, and when combined with partial application would be as concise as a built-in version.

Upvotes: 11

Views: 9237

Answers (5)

Richard A Quadling
Richard A Quadling

Reputation: 4008

Purely on the grounds that my numbers I needed this facility on are ALWAYS integers, and I was doing this in Terraform, my example:

strrev(
    join(
        ",",
        [
            for chunk in chunklist(
                split(
                    "",
                    strrev(
                        var.value
                        )
                    )
                ),
                3
            ) : join("", chunk)
        ]
    )
)

So the following steps are taken (as a way to explain this).

  1. Take the value (1234567890).
  2. Reverse the value as a string ("0987654321").
  3. Split the string into a list of characters (["0", "9", "8", "7", "6", "5", "4", "3", "2", "1"]).
  4. Create sets of 3 from that ([["0", "9", "8"], ["7", "6", "5"], ["4", "3", "2"], ["1"]).
  5. Join each of the inner sets back to a simple string of 3 digits (["098", "765", "432", "1"]).
  6. Join them all together with a , ("098,765,432,1").
  7. Reverse the string ("1,234,567,890").

I wonder if this pattern is doable in other languages. 100% admit that it is a bit convoluted, but it achieved my goal, in the language I was using.

Upvotes: 0

Syntactics
Syntactics

Reputation: 401

In pure C...

char* StringFormatIntCommas(char* szDest, int64_t s64Value)
{
    char        szTemp[32];
    uint64_t    u64Value = ABS(s64Value);                           // Drop Signage

    if (szDest == NULL)                                             // Validate params
        return NULL;

    while (u64Value)
    {
        strcpy(szTemp, szDest);                                     // Preserve Back end
        if (u64Value >= 1000)
            sprintf(szDest, ",%03d", (int32_t)(u64Value % 1000));   // Next triple ',00n'
        else
            sprintf(szDest, "%d", (int32_t)(u64Value % 1000));      // Last triple 'n'
        u64Value = u64Value / 1000;                                 // Reduce
        strcat(szDest, szTemp);                                     // Append Back end
    }

    if (s64Value < 0)                                               // Pre-Append Negative Sign
    {
        strcpy(szTemp, szDest);                                     // Preserve Back end
        strcpy(szDest, "-");                                        // Negative sign up front
        strcat(szDest, szTemp);                                     // Append back end
    }

    return szDest;
}

Upvotes: 0

AMieres
AMieres

Reputation: 5014

Here is a function:

let thousands n =
    let v = (if n < 0 then -n else n).ToString()
    let r = v.Length % 3 
    let s = if r = 0 then 3 else r
    [   yield v.[0.. s - 1]
        for i in 0..(v.Length - s)/ 3 - 1 do
            yield v.[i * 3 + s .. i * 3 + s + 2]
    ]
    |> String.concat ","
    |> fun s -> if n < 0 then "-" + s else s

and here is another one with some helper extension members:

type System.String with
    member this.Substring2(from, n) = 
        if   n    <= 0           then ""
        elif from >= this.Length then ""
        elif from <  0           then this.Substring2(0, n + from)
        else this.Substring(from, min n (this.Length - from))
    member this.Left             n  = if n < 0 
                                    then this.Substring2(0, this.Length + n)
                                    else this.Substring2(0, n              )
    member this.Right            n  = this.Substring2(max 0 (this.Length - n), this.Length)

let thousands (n:int) =
    let rec thousands acc = 
        function
        | "" -> acc
        | x  -> thousands (x.Right 3 + if acc = "" then "" else "," + acc) (x.Left -3)
    if n < 0 then -n else n
    |> string
    |> thousands ""
    |> fun s -> if n < 0 then "-" + s else s

Upvotes: 0

Joel Mueller
Joel Mueller

Reputation: 28764

Since you can't do that with the Printf module, I do it like this:

/// Calls ToString on the given object, passing in a format-string value.
let inline stringf format (x : ^a) = 
    (^a : (member ToString : string -> string) (x, format))

And then call it like this:

someNumber |> stringf "N0"

Upvotes: 6

Guy Coder
Guy Coder

Reputation: 24996

This works but there should be a simpler way.

let thousands(x:int64) = 
    System.String.Format("{0:#,0}", x)

let s = sprintf "%s" (thousands(0L))
printfn "%s" s

let s = sprintf "%s" (thousands(1L))
printfn "%s" s

let s = sprintf "%s" (thousands(1000L))
printfn "%s" s

let s = sprintf "%s" (thousands(1000000L))
printfn "%s" s

0
1
1,000
1,000,000

A slightly better version

let printNumber (x : int) : string =
    System.String.Format("{0:N0}",x)

Upvotes: 3

Related Questions