Printf with variable format string

Due to @printf being a macro, it does not accept a format string that has been passed as a variable.

myvec = randn(4)
n = length(myvec)
outstring = "Beginning of string with some numbers:  "
for i = 1:n
   outstring = outstring*"%-12.3g"
end
outstring = outstring*"\n"
@printf(outstring, myvec...)

Is there currently a way to print using format strings without using a macro, or maybe a much smarter way I have failed to understand? The solution provided in

Changes the output for all Float64 which is undesirable in my case.

Thanks in advance!

1 Like

https://github.com/JuliaIO/Formatting.jl perhaps. Seems that https://github.com/JuliaIO/Formatting.jl/pull/30 is needed for 0.6 though.

3 Likes

That seems to solve the problem nicely, thank you :slight_smile:

It’s been a long-standing issue and I hope it is resolved one day. I’ve been calling libc’s printf directly sometimes. In other situations, the definition

print_formatted(fmt, args...) = @eval @printf($fmt, $(args...))

comes in handy but is very slow. You can get an idea of how performance is affected here: https://gist.github.com/dpo/11000433

However, if you’re going to do lots of printing with one computed format string, @eval may be worthwhile. I use this approach when I know a format will be used many times, e.g., https://github.com/JuliaSparse/HarwellRutherfordBoeing.jl/pull/6/files#diff-ebdd0a248bebc40afece10a0d03009daR20.

2 Likes

Thanks, in my intended application I will happily pay the performance penalty as long as it’s less than about 0.1s. Your solution seems to solve the problem just fine without the need for an additional package, thanks!

I think that a native function would be preferable instead of a package or the @eval solution.

2 Likes

I am personally not very fond of Formatting.jl - not enough lean

A short (and clean i hope) solution can be to gen closure with a macro according to the classical show signature

import Printf

macro gprintf(fmt::String)
    :((io::IO, arg) -> Printf.@printf(io, $fmt, arg))
end

# can be used like this
const fi = @gprintf "%d"
const f3p = @gprintf "%.3f"
const f6p = @gprintf "%.6f"
jit_printf(fmt) = @eval @gprintf($fmt)

using Test
@testset "printf_functionalization" begin
    @test sprint(f3p, 3.14) == "3.140"
    @test sprint(f6p, 3.14) == "3.140000"
    @testset for i in 3:8
        fmt = string("%.", i, "f")
        fjp = @eval @gprintf($fmt) # <=> fjp = jit_printf(fmt)
        @test sprint(fjp, 3.14) == "3.14" * repeat('0', i-2)
    end
end

PS: BTW @RaulDurand that fix your point, no external package, no eval

4 Likes