Julia: function to change numeric output format

Created on 10 Apr 2014  ·  23Comments  ·  Source: JuliaLang/julia

I want change output format for numbers (stand alone floats, float matrix, BigFloats). I can do it in IPython by using %precision magic function. I didn't find any equivalent in Julia.

I know that I can use round but it actually returns different number. It's also cumbersome to convert arrays with combination map + round. One another problem: I didn't get how to change real or output precision of BigFloat: set_bigfloat_precision changes precision for all new BigFloats, copy and deepcopy don't help (they copy value irrespective of new default BigFloat precision), naive precision change via BigFloat.prec corrupts it.

I know, that I handle lot's of different topics in this question and I as a programmer understand that lot's of difficulties lays in output formats. But my friend, mathematician asked me: "Why can I just type %precision in my IPython notebook but not in Julia? Isn't output format change essential feature?".

REPL display and printing good first issue

Most helpful comment

@OliverEvans96 the issue with a macro arises from long-term support. A macro gets the job done, but it quickly turns to a headache for future code maintainers. Couple this with the fact that macros exist in isolation and it's easy to see that they will be orphaned and bit rot on a long-enough horizon.

I think the desire is to have something officially supported so that it's a tier one feature.

All 23 comments

_See also_ MATLAB's format console commands.

This is more appropriate for the IJulia repository. IPython magics were last discussed in JuliaLang/IJulia.jl#12 but the issue was closed due to lack of interest.

I think there's a scope question--is this something we'd like to support in the native REPL, or only in IJulia?

I would say there is a deeper issue that needs to be handled in the base system in order for the support to be well knitted together. IJulia can not rewrite all the printing routines in order for %precicion to work. See also: https://github.com/JuliaLang/julia/issues/5709 https://github.com/JuliaLang/julia/issues/6117

I don't think this is IJulia-specific at all. This is something we need to address generally for all kinds of UIs. It's fine to have a global setting that changes the way things are printed interactively, but I think this is rather bad practice for anything non-interactive.

If the question turn into printing in general... See also: http://docs.sympy.org/dev/modules/printing.html
They have pretty impressive console output, LaTeX and html support.

I don't think this is IJulia-specific at all. This is something we need to address generally for all kinds of UIs. It's fine to have a global setting that changes the way things are printed interactively, but I think this is rather bad practice for anything non-interactive.

An exception is code producing documents automatically from a mix of text content and Julia code, like IJulia but for LaTex, ODF, etc. You likely want to specify once for all how numbers should be printed in that case. That probably can be considered as "interactive", since you immediately get to see the result.

Is there currently any workaround to universally change the format of numeric output?

Can something be done here with the new IOContext stuff?

It is certainly technically possible, although I'm not sure that it is necessarily the right solution. At some level, this moves from printing of a value, to formatting of the value. That suggests (to me at least) that the initial output maybe should some sort of rich document rather than a string, then combine that with a style-sheet to make the final output. I think that level of detail would be more appropriate for a package however. That's a yak-shave I've considered pursuing further, but more pressing issues have been holding my attention. So I'll certainly go along with it if someone wants to add this to IOContext, for lack of any solid alternative to propose right now.

There's NumericIO.jl which provides some of this features and maybe could help.

write vs print vs show vs display:

Sorry for doing this again, but I have gone through alot of conversations on the subject, and would like to make a little synopsis before moving forward. I realize some of what I say probably does not match the accepted picture at the moment. I am trying to fill in the gaps using my own experience with Julia, hoping it might help readers (and hopefully not hinder):

High-level differentiations:

  • write(::IO, x): Serialization of x to a binary stream - as in write(fileio, Float64(1.0)).
  • print(::IO, x): Serialization of x to a text-based stream. I believe this to be the intended behaviour of print in most languages. Though it is ok to apply minor output control here (ex: output precision to reduce output size), it is not ok to apply complex attributes like internationalization with the print function. The idea being that this output will later be read (de-serialized) by some reader program.
  • printfmt(::IO, x): Sorry for adding a new one here: There needs to be some form of print that is meant to generate a human-readable/highly-formatted output - typically targeting the REPL. Preferably, this function should be named so as to not confuse it with print. The printfmt function would make heavy use of ::IOContext arguments to allow for fine control of the output. This output is not meant to be read by some machine like the output of print. Thus, one can apply more complex formatting like internationalization here.
  • show(::IO, x): Slightly higher level than printfmt. Meant to provide the user with more detail about the object (ex: type name & other properties not normally printed).
  • display(::Display, x): Higher-level concept than show. Not necessarily tied to an IO stream (at least not directly). Meant to give the user some control over where/how to display certain output, by means of the Base.Multimedia.displays "stack".

Some basic relationships

  • write(::IO, ::MIME"text/plain", x) = print(::IO, x): When write is qualified with MIME text/plain, it basically means we are serializing to a text-based stream (i.e. print).
  • show(::IO, x) = printfmt(::IO, typeof(x), "(", x, ")"): The default behaviour.
  • show(::IO, ::MIME"text/plain", x) = show(::IO, x): The default behaviour.

printfmt() methods
One would apply internationalization of numbers at this level.

For example:
printfmt(STDOUT, 3141592.65) => "3,141,592.65" #English (US)
printfmt(STDOUT, 3141592.65) => "3 141 592,65" #French (CAN)

writemime() (deprecated)

I know in Julia v0.5, we replaced writemime() with show() instead of write(), but this is odd to me. I feel that the ex-writemime function is more of a "serializing" operation (though potentially with some information loss):

writemime(::IO, ::MIME"image/png", x) => write(::IO, ::MIME"image/png", x)

Instead, I believe the show() function should not only serialize the object to the given mime, but also describe the object (like implementations of the show() function usually do):

type MyObject
    # Contents can be rendered as image/png
end

function show(io::IO, ::MIME"text/html", x::MyObject)
    printfmt(io, typeof(x), "\n")
    write(io, MIME("image/png"), x) #Render image
end

show(), printfmt() methods & the ::IOContext object
Thus, I believe it is the show()/printfmt() functions that would make the most use of ::IOContext. For example:

function show(io::IOContext, ::MIME"text/html", x::MyObject)
    if !haskey(io.dict, :omittype)
        printfmt(io, typeof(x), "\n")
    end
    write(io, MIME("image/png"), x) #Render image
    #Maybe show other important details regarding the object here...
    #Maybe display an internal reference count, for example so one can more 
    #easily debug the code.
end

show(IOContext(fileio, compact=true, omittype=true), x) #Show the object

display() & plots

As stated before, the display mechanism applies in a more general context (not limited to IOs), and allows for some user control.

Take for example the idea of displaying a plot object created with Plots.jl:
WARNING: This is not how Plots.jl currently displays its plots - this is a conceptualization.

immutable PyPlotDisplay <: Base.Display
    backend::Symbol #:tk, :gtk, ...
end

#If we want to specify the default output for plots generated by Plots.jl,
#we could do something like the following:
pushdisplay(PyPlotDisplay(:gtk))

#... and not specifying this display would simply cause IJulia's REPL
#    to fall back to inline plots!

display to control output precision

Now this is where I address the issue itself (sorry for the long wait).

Assuming we have a reference to the Julia REPL, repl, a user could add the following to their ~/.juliarc file:

ctx = IOContext(repl)
ctx[:floatprecision] = 4
ctx[:floatoutput] = :SI #Or maybe :SCI/:ENG/:binary/:finance/...
[...]

In my opinion, this solution is consistent with the intent of both Base.Display, and IOContext.

As for the comment from @fbruetting:

NumericIO.jl is not a solution to the problem in general, but could be used to improve the control of Julia's numeric output:

print(formatted(STDOUT, :SI, ndigits=4), 3.14159e-8) => "31.4n"
print(formatted(STDOUT, :SCI, ndigits=4), 3.14159e-8) => "3.14×10⁻⁸"
print(formatted(STDOUT, :ENG, ndigits=4), 3.14159e-8) => "31.4×10⁻⁹"
print(formatted(STDOUT, :ENG, charset=:ASCII, ndigits=4), 3.14159e-8) => "31.4E-9"

Of course, some things would have to be modified to conform with my description above:

formatted -> IOContext
print -> printfmt
...

The most basic thing that is needed here is just to change the REPL display function so that it passes :compact => d.compact, where d.compact is a REPL setting that can be changed via some API. Or better yet, just passes d.defaults… (a list of an arbitrary number of default IOContext options).

To the extent that IOContext adds support for more fine-grained control of numeric output, we can add REPL settings for that too, but the basic point is that the user should be able to control what options the REPL passes to its default IOContext. (And IJulia could use the same options.)

Any final conclusion about this?

Dusting off an old issue here; I think @stevengj, as usual, has the right idea here: some mechanism (global Refs maybe) to have some REPL defaults that the default REPL display function would pass in it's IOContext. One of those could include a :precision option that by default would be 6, but could be configurable (setNumericPrecision(x)). This would be a great "first PR" if someone wants to tackle it.

For accessing the REPL's IOContext part, see #29249.

Did this mature to the point that there is a concrete example of how to adjust the number of significant digits? I share the complaint that others have expressed previously, print() loses utility if the output cannot be tailored to a global need.

I'm sure the issue is much more complex than I'm aware (since I haven't read all the posts here), but I just wanted to share my quick, simplistic solution that was sufficient for my needs:

using Printf
Base.show(io::IO, x::Float64) = write(io, @sprintf("%.2f", x))

It's fairly straightforward to turn this into a macro:

macro format(ex)
   quote 
       Base.show(io::IO, x::Float64) = write(io, @sprintf($ex, x))
   end
end

which can then be called as follows:

julia> @format "%.2f"

julia> rand(3, 3)
3×3 Array{Float64,2}:
 0.64  0.68  0.98
 0.85  0.41  0.49
 0.42  0.45  0.96

That is a temporary manual solution. I need something which isn't a macro for this.

Why can't there be a regular function which dynamically accepts a format string?

What's wrong with a macro?

@chakravala, feel free to review/chime in on https://github.com/JuliaLang/julia/pull/32859, which allows doing Printf.format(fmt::String, args...)

@OliverEvans96 the issue with a macro arises from long-term support. A macro gets the job done, but it quickly turns to a headache for future code maintainers. Couple this with the fact that macros exist in isolation and it's easy to see that they will be orphaned and bit rot on a long-enough horizon.

I think the desire is to have something officially supported so that it's a tier one feature.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ararslan picture ararslan  ·  3Comments

wilburtownsend picture wilburtownsend  ·  3Comments

Keno picture Keno  ·  3Comments

yurivish picture yurivish  ·  3Comments

TotalVerb picture TotalVerb  ·  3Comments