Really? Not irrational?


I told my 13-year old girl that Julia has both rational and irrational types. How we started this conversation is a different story :slight_smile:

As I showed off how it works in the REPL, she pointed out that it’s wrong to have sqrt(2) not being an irrational type… common sense tells us that a number is either rational or irrational but cannot be neither. :sweat_smile:

I understand that a value can only has one type and it makes perfect sense to put it under AbstractFloat. However, does this behavior bother anyone? Should rational/irrational really be a characteristic of a number a type rather than a type, hence we just have various supporting functions around them?

julia> sqrt(2) isa Irrational

julia> sqrt(2) isa Rational

julia> sqrt(2) isa Real

julia> sqrt(2) isa AbstractFloat


sqrt is not equivalent to the mathematical definition of \sqrt{}, it is an approximation of the latter in the relevant subset of numbers (in the case of sqrt(::Int), the set of Float64). Most computer languages (except for CAS) work this way.

Irrational is a nice addition to the standard floating point menagerie, for representing irrational values “exactly”, for some operations. See mathconstants.jl for details.


AFAIK Irrational represents a computable number, in the original sense: It is a piece of code that can compute successive approximations (including a-posteriori bounds). Now, arithmetic on computable numbers is nice in math but kinda bad in practice (equality is undecidable!). Hence, sqrt(2) is an eagerly evaluated approximation in julia.

I would not argue against a rename of Irrational to LazyComputableReal (which eagerly approximates on arithmetic), though (or did I misunderstand the intention behind Irrational?).


Arithmetic in programming languages is typically implemented using Assuming that this carries over 1-to-1 to ideal mathematical structures will not end well.

In this specific case, the type Irrationals purpose is simply to lift an irrational number (which can’t be represented in the standard numerical types) to the type domain where it can be dispatched on.


I think it is possible that you might have. AFAICT Irrational is not lazily computed at all, eg π is just 3.14159265358979323846. I think that their advantage is that certain operations are exact, eg \log(e) = 1, see here.


Oh. I did not look at the detailed implementation but was convinced by

f(x, prec1,prec2)= BigFloat(x, prec1)-BigFloat(x,prec2)
@show f(pi+0.0, 100, 105);
#f(pi + 0.0, 100, 105) = 0.0
@show f(pi, 100, 105);
#f(pi, 100, 105) = -1.97215226305252951352932141320696557418301608777255751192569732666015625e-31


It’s not, that’s just the float64 approximation and doesn’t really need to be there…


It’s much more clever than that – it’s an object that really does represent the true irrational and “knows its value adaptively” relative to its local context. E.g.

julia> Float64(pi, RoundDown)

julia> Float64(pi, RoundUp)

julia> BigFloat(pi, RoundUp)

julia> BigFloat(pi, RoundDown)

julia> 1.0*pi

julia> big(1.0)*pi

(The last one uses a BigFloat approximation to pi.)

In principal it’s possible to add new Irrational objects in one’s own code. Steven Johnson had a proposal at some point.


I think to talk about whether a Float64 number to be rational or not is meaningless, so the current behaviour is ok. Float64 is just an approximation of real number with some precision restriction. Different real numbers can have same Float64 number corresponding to it. Mathematically, you can use both rational/irrational number to approximate both rational/irrational number with whatever precision you want, so it is useless to find out whether the approximation is rational or irrational.


Thanks for the correction, I see that it is using MPFR.


I’d say the confusing fact is that e.g. 1 isa Rational is false. Indeed, contrary to what one might expect, Rational is not the abstract supertype of all kinds of rationals, but a concrete type representing rationals in one specific way. To correspond more closely to math terminology, that type would have to be called Fraction or something like that.


But only in a very restricted sense (unlimited context-appropriate precision with isolated special methods):

julia> typeof(ℯ)

julia> typeof(log(ℯ))

julia> typeof(log(ℯ^2))

julia> typeof(π*π)

Someone might build a nice number theory engine using Julia, but Base + the standard library is practical for us number-crunchers instead.


Julia is not a formal mathematical language, I realized that it falls far short from that when I realized I can’t define my own operator characters. Julia is a programming language, it does not carry a 1-1 correspondence to mathematical vernacular, although it gets very close to it. What I realized though is that Julia will never be able to used as a formal mathematical vernacular due to limitations in programming language design, pure mathematics is much more general, abstract, and has far more complicated context sensitive grammar than any useful programming language could attain. But it is neat that Julia is able to come closer than others, but I think languages like agda or some theorem provers get even closer than julia is.


You cannot add new ones to the parser, but I think that most every Unicode character that could be used as an operator can be defined as one.
I counted at least 552 operators available in v0.7, with different precedence levels.

For example:

julia> ⊗(a,b) = println("operate on $a and $b")
⊗ (generic function with 1 method)

julia> 123 ⊗ 456
operate on 123 and 456


Yes, I’m aware, I’ve added a character to that list already, but was not able to get all the characters I wanted to use approved, since there is some context-sensitive ambiguity with how some symbols should be interpreted.

Also, the order of operations is entirely pre-set in the list of characters, so if I wanted to use an operator with a different order of operations precedence, I can’t do it, since the Julia developers want to ensure compatibility for all users. However, some languages like agda are more flexible in that regard.

For a Mathematician, this is a limitation, since we like to define operations and symbols on a whim, many symbols can have completely different interpretations and order of operations in different contexts, for a pure mathematician at least, so Julia is not able to handle that situation.

But this is getting a bit off topic.


See also last years pi day blog post for an explanation of Irrational


Very cool. This is a good opportunity to teach about differences between theory and its instantiations on real computers. sqrt(2) isa Irrational being false tells you something about what sqrt(2) actually does — it’s returning a rational approximation to the true value, since we decided that would be more useful. So that answer is “correct” in the highly literal way that computers work.

Another fun example is Float64(pi) < pi, which is true.

It would be a great exercise to implement an irrational type representing square roots, if you think she might be interested in that.




Not sure if people know about this, but with my package you can do stuff like this:

julia> using Reduce
Reduce (Free PSL version, revision 4521), 11-Mar-2018 ...

julia> log(:(e^2))

julia> typeof(ans)

But I just realized that this doesn’t work on symbols yet (it works for Expr objects), so log(:e) doesn’t work yet, but that should be a simple fix to implement.


Alright, so I fixed it, now you can do the symbol type using Reduce:

julia> log(:e)

But to have the code generation for macros for all these functions like @log e^2 my question needs ans: