Get output of trig function as a multiple of pi?

Hi there, I’ve been using the Julia REPL a lot as a calculator for my day to day life (as a student of engineering, this is really helpful, and definitely much more comfortable to use than a Casio calc for simple things, or using some other REPL like Python’s).

But I’ve found something that I’d like to know a workaround for.
For example, if I type something like arccos(-0.5) on my Casio (in RAD), I’d get (2/3)pi as a result.
However, in Julia, I get the approximate rational result for that as I enter acos(-0.5) on the REPL (I get 2.0943951023931957 which is a very great approximation of the result, but not exact).

I wonder, is there a way to get the outcome as a multiple of pi for these cases, or maybe a function that does this afterwards that I can pipe to this result?
Of course I could just divide the result by pi and do that by hand but this is not something I’m willing to do :stuck_out_tongue:

Thank you everyone!

There is no acospi, but you can work with acosd and convert degrees.

However, it is unclear what you really want here, since 2/3 is not representable as a Float64. If you want rationals, maybe

julia> rationalize(acosd(-0.5)/180)
2//3

I’d just like to get some output like this

acos( cos(pi/2) )
pi/2

asin(sin((1/6)pi))
pi/6

So, I know pi/2 is no existing type in Julia (or so I believe), neither is pi//2 because pi is obviously irrational, so perhaps just having the fractional part and knowing that it is * pi would do.

So like (imagining an acospi function)

acospi(cos(pi/3))
1//3 (times pi)

Your solution is pretty good, but perhaps a bit long to type (though it works well as a ‘quick fix’ for my problem), thanks for that!

These are impossible in general. Out of the very few special cases you give the intermediate result already cannot be exactly represented. If for whatever reason you really need fraction representation of angle, you should use symbolic calculation instead and you will not be able to find anything like that in the base math functions.

9 Likes

Well, just for fun you could do this:

julia> struct PiF
         x
       end

julia> function Base.show(io :: IO, x :: PiF) 
         y = rationalize(x.x/π)
         println("($(y.num)/$(y.den))π")
       end

julia> PiF(acos(cos(pi/2)))
(1/2)π


julia> PiF(asin(sin(π/6)))
(1/6)π


But I am not sure how useful is that. It is pretty though :slight_smile:

Or even simpler, just define the function:

julia> function pifrac(x)
         y = rationalize(x/π)
         println("($(y.num)/$(y.den))π")
       end
pifrac (generic function with 1 method)

julia> pifrac(asin(sin(π/6)))
(1/6)π

6 Likes

Lovely solution!

Thank you all for the replies, I’ll probably just use the float approximation instead, hahaha.

This is a very nice solution! One might want to define a tolerance in rationalize. As is, the code outputs:

julia> PiF(acos(cos(pi/17)))
(235482333457281/4003199668773778)π

But if one defines some tolerance, say:

y = rationalize(x.x/π, tol=10*eps(x.x))

the output becomes:

julia> PiF(acos(cos(pi/17)))
(1/17)π
7 Likes

Note that these are just introducing more errors to the calculation in general. Unless your actual goal is to do approximate calculation that somehow want to use fractions as much as possible or you somehow know that all the numbers you’ll ever deal with are fractions with smal denominators you should not use any of these.

3 Likes

This reminds me an old university joke.
A businessman hires a mathematician, a computer scientist and a physicist in order to be able to win all the trifecta horse race bets.
The mathematician after long weeks of lemmas, theorems and conjectures, concludes that the problem is formally irresolvable. Then, the computer scientist takes up the challenge on a supercomputer and after having written quantities of algorithms he happily announces that it will take just a few hundred years to calculate the result of each trifecta. The physicist, with a smile on his face, informs his eminent colleagues that he has the solution. He approaches a blackboard and while drawing a curve he begins by saying: “Let us approximate the horse by a perfect sphere…”

2 Likes

I find this solution pretty useful. Can anyone tell me how to suppress display the type name PiF when there is an array of PiF objects? example:

julia > PiFrac.([2π/3,π/3,π/4])
3-element Vector{Scattering.PiFrac}:
 2π/3
 π/3
 π/4

This is fine. Now suppose there is a UnitCell type which has a field of an array of angles

struct UnitCell
    edges
    angles
end

Base.show(io::IO, uc::UnitCell) = print(io, typeof(uc), " with edges ", uc.edges, " and angles ", PiFrac.(uc.angles))
julia > UnitCell([1.0,1.0,1.0], [π/2, π/2, π/2])
UnitCell{3, Float64} with edges [1.0, 1.0, 1.0] and angles PiFrac[π/2, π/2, π/2]

I would like to get rid of “PiFrac” before the bracket “[” just like [1.0, 1.0, 1.0] without “Float64”?

I think the way is to overload print:

julia> struct PiF
         x
       end

julia> function Base.show(io::IO, x::PiF) 
         y = rationalize(x.x/π)
         print("($(y.num)/$(y.den))π")
       end

julia> function Base.print(stdout::IO,x::AbstractVector{PiF})
         print("[")
         for i in firstindex(x):lastindex(x)-1
           print("$(x[i]), ")
         end
         print(" $(x[end])]")
       end

julia> println(v)
[(2/3)π, (1/3)π, (1/4)π ]

julia> print(v)
[(2/3)π, (1/3)π, (1/4)π ]


Thanks! Inspiring by your idea, I have read through the Base source code for printing Array. I found the following more short solution:

Base.show(io::IO, xs::AbstractVector{PiF}) = Base.show_delim_array(io, xs, "[", ",", "]", false)

Edit: to be complete, following is my full solution. Note that (1/2)π is shown as π/2 instead. And (1/1)π is shown as π, (2/1)π is shown as 2π, etc.

"""
A custom type for pretty display of angles in radian.
"""
struct PiFrac
    θ
end

function Base.show(io::IO, x::PiFrac)
    y = rationalize(x.θ/π, tol=100*eps(x.θ))
    if y.num == 1
        y.den == 1 ? print(io, "π") : print(io, "π/$(y.den)")
    else
        y.den == 1 ? print(io, "$(y.num)π") : print(io, "$(y.num)π/$(y.den)")
    end
end

Base.show(io::IO, ::MIME"text/plain", x::PiFrac) = show(io, x)

Base.show(io::IO, xs::AbstractVector{PiFrac}) = Base.show_delim_array(io, xs, "[", ",", "]", false)
julia> PiFrac.([π/2 3π/2 2π; 2π/3 π/4 π])
2×3 Matrix{Scattering.PiFrac}:
 π/2   3π/2  2π
 2π/3  π/4   π
1 Like

Have a look at https://github.com/anj1/AlgebraicNumbers.jl/tree/anj1_log_alg, it does exactly what you’re looking for :slight_smile:

julia> acos_alg(AlgebraicNumber(-1//2))
2//3

And better still, it actually uses exact calculation, not approximate calculation using rationalize, so it will always give the correct result.
(Internally it works via representation using minimal polynomials)

2 Likes