PainterQubits / Unitful.jl

Physical quantities with arbitrary units
Other
612 stars 112 forks source link

Display units with Float64 powers rather than as Rational numbers #498

Open boriskaus opened 2 years ago

boriskaus commented 2 years ago

Thanks for this great package! In general it seems that Unitful transfers the powers of units to Rational numbers before displaying:

julia> using Unitful
julia> u"Pa"^(1/2)*u"s"^(-1)
Pa^1/2 s^-1

or, on a Mac:

Pa¹ᐟ² s⁻¹

This works well if the powers are simple. Yet, in my research field we often have powers that are not easily transferrable to rational numbers, such as

julia> u"Pa"^(3.21)*u"s"^(-1)
Pa³⁶¹⁴¹³⁸⁷⁰⁰⁹⁶⁴⁸²³ᐟ¹¹²⁵⁸⁹⁹⁹⁰⁶⁸⁴²⁶²⁴ s⁻¹

In these cases it would probably be better to display the units as Float64 rather than as Rational numbers, such as

Pa^3.1 s^-1

Would such an option be difficult to implement?

sostock commented 2 years ago

The powers are not just converted to Rational for display, they are actually stored as Rationals in the type of the unit. One could write a function to print unit powers as decimals instead of fractions, but this wouldn’t change the underlying behavior. If display is all that you are concerned with, this may be an option.

If you actually require numbers that cannot be represented as Rational{Int}, different printing wouldn’t help much. The power of a unit is hardcoded to Rational{Int} here. You could try to change this to Float64 (you might have to change some other code too if this breaks things, I’m not sure) and see whether it works for you.

Keep in mind that every unit^power has its own type, so if you do a lot of arithmetic involving different powers, things might get slow (julia will have to compile new code for every new power).

boriskaus commented 2 years ago

thanks! Display is indeed all I am concerned with. How can I best replace/overload the current printing option?

sostock commented 2 years ago

The printing can be changed by replacing Unitful.superscript. You can either replace the implementation here or add a specialized method to your own code:

Unitful.superscript(i::Rational{Int64}; io=nothing) = "^"*string(float(i))
boriskaus commented 2 years ago

thanks for the suggestion! I have now added this in our package, which works as expected.

function Unitful.superscript(i::Rational{Int64}; io=nothing) 
    string(superscript(float(i)))
    if io === nothing
        iocontext_value = nothing
    else
        iocontext_value = get(io, :fancy_exponent, nothing)
    end
    if iocontext_value isa Bool
        fancy_exponent = iocontext_value
    else
        v = get(ENV, "UNITFUL_FANCY_EXPONENTS", Sys.isapple() ? "true" : "false")
        t = tryparse(Bool, lowercase(v))
        fancy_exponent = (t === nothing) ? false : t
    end
    if fancy_exponent
        return superscript(float(i)) 
    else
        return  "^" * string(float(i)) 
    end

end

Unitful.superscript(i::Float64) = map(repr(i)) do c
    c == '-' ? '\u207b' :
    c == '1' ? '\u00b9' :
    c == '2' ? '\u00b2' :
    c == '3' ? '\u00b3' :
    c == '4' ? '\u2074' :
    c == '5' ? '\u2075' :
    c == '6' ? '\u2076' :
    c == '7' ? '\u2077' :
    c == '8' ? '\u2078' :
    c == '9' ? '\u2079' :
    c == '0' ? '\u2070' :
    c == '.' ? '\u0387' :
    error("unexpected character")
end
sostock commented 2 years ago

Note that this superscript(::Float64) does not work for all values (try 1.0e6). There is also an unnecessary string(superscript(float(i))) in the superscript(::Rational{Int64}; io) method.

We could also think about incorporating something like this into Unitful to avoid the type piracy and invalidations, therefore I am reopening it.