Looking for ONE package, to do BASIC number formatting for console-text-output

Hi,

I’ve recently explored a lot of packages to get more of an idea, of what kind of functionality is being covered in the julia-ecosystem, as it applies to my needs. While there are some very nice and powerful packages (for visualization, especially, which is very useful, to me), I’m still on the quest for ONE package to handle all my simple output of anything, that is just a single number, on the console.

I could come up with a huge list, of nice-to-haves (like padding, overall-precision, dates, times with annotations of units, etc.), but have now limited my expectations to being able to do just the very basic formatting of floats & integers (up to 64-bit size, only).

Specifically, just having 1000s-separators for ints and limiting precision (# digits) for floats likely already covers 90% of my needs (and I’m happy to come up with custom functions for the remaining 10%).

Please understand, I’m going to be 50 yrs. old, soon, and the julia-ecosystem is already a bit overwhelming, for me, and I’d rather just do those very limited formats, but have ONE reliable package for it, than being flooded with 10 packages, of which each does a portion of what I actually need, to perfection, but I have to use a different package, as soon as I don’t want to output a float, but an int, this time. :sweat_smile:

This kinda sums up, my currently best options of Packages (but is rather disappointing, as both are failing to do 1000s-separators)…

  • Formatting (python-style)
  • Printf (C-style)
julia> testFormats()
--------------------------------------------------------------
no formatting applied...
float: 123456.78901234567, integer: 1234567890123456789 (Base.print)
--------------------------------------------------------------
testing Packages...
--------------------------------------------------------------
float: 123456.789 (Formatting.printfmt)
float: 123456.789 (Printf.@printf)
integer: 1234567890123456789 (Formatting.printfmt)
integer: 1234567890123456789 (Printf.@printf)
==============================================================
best, so far...
--------------------------------------------------------------
float: 123456.789 & integer: 1234567890123456789 (Formatting.printfmt)
float: 123456.789 & integer: 1234567890123456789 (Printf.@printf)
"(note: Both packages failing to display 1000s-separators)"
==============================================================
min-target:  float:  123456.789 & integer: 12,345,678,901,234,567
nice-2-have: float: 123,456.789 & integer: 12,345,678,901,234,567
--------------------------------------------------------------

Please note, one additional hard requirement: It’s gotta be possible, to call ONE function of ONE package, to output several variables with individual datatypes & formats in ONE line (of code). :wink:

in addition to Printf, Formatting & Base, I’ve looked at Format, but might have not found other relevant packages or even missed options / functions in those packages, I listed. Does anyone have further pointers to explore (or even a solution)?

This kind of sums up my testing (including some of the fails)…

function testFormats()
    xFloat  = 123456.78901234567::Float64
    xInt    = 1234567890123456789::Int64
    println("--------------------------------------------------------------")
    println("no formatting applied...")
    print("float: ", xFloat,", integer: ",xInt, " (Base.print)\n")
    println("--------------------------------------------------------------")
    println("testing Packages...")
    println("--------------------------------------------------------------")
    # =====================================================================================
    # use case #1: Displaying FLOATS, limiting precision
    # only nice-to-have: rounding, 1000s-separators, padding, ...
    # =====================================================================================
    # python-style...
    printfmt("float: {:,.3f} (Formatting.printfmt)\n", xFloat)    # works
    # C-style...
    @printf("float: %5.3f (Printf.@printf)\n", xFloat)            # works
    # alternatives ???
    # =====================================================================================
    # use case #2: Displaying INTs with 1000s-separators...
    # only nice-to-have: padding, etc.
    # =====================================================================================
    # ...should be displayed as: 12,345,678,901,234,567 (don't care which 1000s-sep, but need ONE)
    #
    # python-style...
    printfmt("integer: {:,} (Formatting.printfmt)\n", xInt)       # no error, but 1000s separator not working
    # C-style...
    #@printf("integer: %'d\n", xInt)                              # error, ' not supported, in julia
    @printf("integer: %d (Printf.@printf)\n", xInt)               # no error, but no 1000s separator
    # alternatives ???
    # =====================================================================================
    # mixing use cases #1 & #2 should alsow work in one line, with one call, like this...
    println("==============================================================")
    println("best, so far...")
    println("--------------------------------------------------------------")
    printfmt("float: {:,.3f} & integer: {:,} (Formatting.printfmt)\n", xFloat, xInt)
    @printf("float: %5.3f & integer: %d (Printf.@printf)\n", xFloat, xInt)
    println("==============================================================")
    println("target:      float:  123456.789 & integer: 12,345,678,901,234,567")
    println("nice-2-have: float: 123,456.789 & integer: 12,345,678,901,234,567")
    println("--------------------------------------------------------------")
    # =====================================================================================
end

I have this in my startup.jl file. It does one thing you want, but not all.

using Printf
Base.show(io::IO, f::Float64) = @printf(io, "%1.5e", f)
Base.show(io::IO, f::Float32) = @printf(io, "%1.5e", f)
Base.show(io::IO, f::Float16) = @printf(io, "%1.5e", f)

You can tune this by changing 1.5e to whatever you like and/or adding Int datatypes to the list.

1 Like

thx, but not sure, what exactly it does, that I’m looking for & is not covered by what I have, already?

min-target:  float:  123456.789 & integer: 12,345,678,901,234,567

…I already have (by 2 packages)…

float: 123456.789 & integer: 1234567890123456789 (Formatting.printfmt)
float: 123456.789 & integer: 1234567890123456789 (Printf.@printf)

addendum: I don’t really care, which kind of 1000s-separator (for ints, at least) is available, stuff I’ve seen in other languages, over the years: , _ ´ |

It does this

julia> exp(1.0)
2.71828e+00

So you don’t suffer with the 16 digit mantissa.

Thx, got it. But I don’t really care for it, tbh. :man_shrugging:

Humanize.jl could help with thousands separator for integers, but I don’t think it handles floats out of the box (except in the specific case of data sizes):

julia> using Humanize
julia> Humanize.digitsep(123456)
"123,456"

Also see these threads dealing with the same topic:

Could you show what is wrong with Formatting.jl?

The examples below seem to work correctly:

using Formatting
sprintf1("%10.3'f", π*1e9)      # "3,141,592,653.590"
sprintf1("%'d", π*1e9)          # "3,141,592,654"

FYI:

julia> import Humanize: digitsep
julia> digitsep(12345678, seperator= "_")  # Note 12_345_678 is a legal value in Julia source code, Meta.parse will parse such, while parse doesn't.
"12_345_678"

It seems with Humanize you need to be explicit, ask for , or _, but note in some countries, Europe/Iceland, we use . (and , for the fractional point). It’s annoying for computing… (and (then) ; for separator, as in Excel), but if you or others (Humanize.jl ) want to support localization, then it seems less supported (also for sorting), than in other languages. At least _ (and ', I believe C++ allows it now) works globally…

You wanted “limiting precision”, then I’m confused by “don’t really care for it, tbh”.

I just turned 50 last month, and I can tell you, not much is going to change (the ecosystem feels the same before and after, yes, I started with Julia years ago, it was overwhelming then, still is to some degree, though I have a great feel for it now, even with the ecosystem much larger).

The C-style formatting (withing Formatting.jl) is indeed pretty rich in features, including the thousands separator. One disadvantage is that it accepts only one format string and value per call. So to quickly print multiple things in one line, you have to do something like:

 println("float: $(sprintf1("%5.3'f", xFloat)) & integer: $(sprintf1("%'d", xInt)) (Formatting.sprintf1)")

(This can be dealt with using some helper function/macro, of course, to make it more convenient.)

Could you show what is wrong with Formatting.jl?
The examples below seem to work correctly: […]

Awesome, thank you so much, for this pointer!!!

And there is absolutely nothing wrong, with that function, I just did not even consider, that one format-spec "%'d" might work in one function (sprintf1), when it didn’t work in @printf (which I just happened to test, first), so I didn’t even try that in sprintf1. It’s a bit more cumbersome, this way, than it could be (requiring multiple function-calls). But at this point, I’m happy, finding ONE package, supporting everything I need, at all, tbh. :sweat_smile:

I consider this solved. :+1:

==============================================================
target: float: 123456.789 & integer: 12,345,678,901,234,567
--------------------------------------------------------------
solved...
--------------------------------------------------------------
float: 123456.789 & integer: 1,234,567,890,123,456,789
(Printf.@printf + sprintf1)
--------------------------------------------------------------
by this code...
--------------------------------------------------------------
@printf("float: %5.3f & integer: %s (Printf.@printf + sprintf1)\n", xFloat, sprintf1("%'d",xInt) )
==============================================================

You wanted " limiting precision", then I’m confused by “don’t really care for it, tbh”.

Sorry, didn’t mean to step on your foot.
But for what I was looking for, just see my target, above - I don’t think I can specify that anymore clearly. :wink:

I just turned 50 last month, and I can tall you, not much is going to change (the ecosystem feels the same before and after, yes, I started with Julia years ago, it was overwhelming then, still is to some degree, though I have a great feel for it now, even with the ecosystem much larger).

hehe, yeah, I kinda like the challenge, to learn a (yet another) new language, tbh, and I feel I have put the bar quite high with julia (for me, personally), as I’m used to industrial standardization, a lot, and the julia ecosystem seems to be dominated by creative and capable, but not necessarily very organized or even governed people, who could be forced to write code in any canonical way and adhere to tedious standards and conventions. :innocent:

1 Like

It allows broadcasting, like this:

sprintf1.(["%.3'f","%'d"], [π, 2]*1e9)

or like this:

sprintf1.("%.3'f", [π, exp(1), 1, 0]*1e9)

I guess one could get more creative with helper functions and/or macros.

1 Like

Thank you, also!!!

I think I like this even better, than what I patched together with @printf + 1x sprintf, as just using the Base.print + sprintf1 seems more canonical & flexible, atm. This way, I get the 1000s - separator, for floats, also, easily.

==============================================================
solved x2...
2nd solution preferred, by me, as it seems more canonical AND even more flexible (AFAICT).
--------------------------------------------------------------
float: 123456.789 & integer: 1,234,567,890,123,456,789 (Printf.@printf + sprintf1)
float: 123,456.789 & integer: 1,234,567,890,123,456,789 (Base.println + 2x sprintf1)
==============================================================

Humanize.jl could help with thousands separator for integers, but I don’t think it handles floats out of the box (except in the specific case of data sizes):

Thx for this, also! :+1:

I don’t think, I’ll be using it for primitive data-types, free of units, as the Printf-package seems to handle everything I need, concisely, BUT I did find the general idea for unit-specific functions extremely appealing!

Just (sadly!) that package’s understanding of humans seems to be based on the assumption, that we don’t care much about precision, even when it wouldn’t make outputs any more complicated…

julia> timedelta(7199)

is just 1 sec. short of 2h, but gives me…

"an hour"

:grimacing:

That is a bit… …let’s say coarse for my needs. :innocent:

1 Like

There might be a better way, but the following approach seems preferable:

using Formatting
fm1 = generate_formatter("%5.3'f")
fm2 = generate_formatter("%'d")
x, y = π*1e9, 3141592654
printfmt("float: {1} and integer: {2} (Formatting.sprintf1)", fm1(x), fm2(y))

# Result:
float: 3,141,592,653.590 and integer: 3,141,592,654 (Formatting.sprintf1)

[…] the following approach seems preferable:

This (with a few global const - patterns, like your fm1, fm2) is gonna go into my preferred pattern-collection, for julia, thank you! :+1:

You can suggest a change, by making a (breaking) PR. But I’m not sure rounding is wanted as a default, e.g. “2 hours” ago, when only 1.5 hours ago (or rounding to “2 years ago”, for 1.5). I think it may though be valuable, as a non-default option.

But I’m not sure rounding is wanted as a default, […]

…there is no way, inferring that, from the package.
It’s a bit astonishing, just how little specified a significant part of julia-package-culture seems to be.
But I also like the free-spirit and careless, optimistic attitude.
Let’s humanize this tedious IT - who am I, wanting to change that?! :laughing:

1 Like