Based on the discussion in #37931, I have found that pi == one(pi)*pi
is broken. However, it should hold according to the documentation of one()
function:
one(x)
one(T::type)
Return a multiplicative identity for `x`: a value such that
`one(x)*x == x*one(x) == x`. Alternatively `one(T)` can
take a type `T`, in which case `one` returns a multiplicative
identity for any `x` of type `T`.
If possible, `one(x)` returns a value of the same type as `x`,
and `one(T)` returns a value of type `T`. However, this may
not be the case for types representing dimensionful quantities
(e.g. time in days), since the multiplicative
identity must be dimensionless. In that case, `one(x)`
should return an identity value of the same precision
(and shape, for matrices) as `x`.
...
Don't know if you prefer fixing the implementation or just updating the documentation to make it consistent. Notice that since one(pi)
already returns true
, it would be possible to make true * pi === pi
.
FYI: Returning 1.0 (or 1 or true) isn't strictly wrong.
That is the multiplicative identity. The problem comes when you multiply it with pi, or even 2 with pi, you want to get pi or 2pi, but that's only possibly in a CAS (maybe e.g. in http://nemocas.org/ ). It was decided that actualy calculations would convert to floats that make pi rational approximation.
I think in this case the docs is at fault. It should say that identity only holds for T
being a concrete type.
edit: I meant to distinguish where a T
is "concretely" represented by bits but find it hard to point finger at:
julia> isconcretetype(typeof(pi))
true
julia> isbits(pi)
true
julia> sizeof(Int)
8
julia> sizeof(Irrational{:π})
0
One way to frame the problem is to say: multiplicative identity of Irrational{:pi}
doesn't exist. (even before we talk about if the identity is of the same type or not)
typeof(pi)
is a concrete type.
🤷♂️ Really starting to regret the Irrational
type entirely.
While of course it would be nice, I don't really see why this so badly needs to hold --- many mathematical identities are not true e.g. with floating-point numbers.
At some point, I think we had the property that true*x
was just x
and false*x
should always be zero(x)
. If we had that then this identity would hold. It would be a bit weird though since true*x
and false*x
would not be of the same type when x
is an irrational. Perhaps that would be ok since it would be very amenable to constant propagation and type analysis.
Perhaps a case where a 0-bit integer is useful!
Isn't pi
(and friends) already 0-bit integers? (base pi
and whatnot)
First, thank you for the responses. I understand this is a minor issue.
I totally agree with @StefanKarpinski that the whole Irrational
type is somehow unfortunate. I can demonstrate it by more practical examples:
1) Problem with negation (not even ≈
helps)
julia> x = big(1.0)
1.0
julia> cos(-big(π)-x) == cos(π+x)
true
julia> cos(-π-x) ≈ cos(π+x)
false
2) 2π
may look like an irrational constant (but is already converted to Float64
), and the order of operations matters
julia> 2π+x == π+π+x
true
julia> 2π+x ≈ π+(π+x)
false
The current documentation contains only the following text, but nothing about Float64
is a fallback type.
AbstractIrrational <: Real
Number type representing an exact irrational value, which is automatically
rounded to the correct precision in arithmetic operations with other numeric
quantities.
What I love about julia is that it is easy to use and intuitive. But the behavior in these examples is not intuitive.
There is no easy fix. pi
is already threatened as Float64
in many cases. Thus, the least breaking change would probably be to make pi
behave like Float64
constant in v2.0 with a single exception of big(pi)
or BigFloat(pi)
, respectively. This would also solve this issue since pi == one(pi)*pi
would compare two floats. However, it would be necessary to deprecate current arithmetic operations with {Float16, Float32, BigFloat}
like
big(1.0) + pi # deprecate this
big(1.0) + big(pi) # in favour of this
I belive it would also make user's code less error-prone.
julia> cos(-big(π)-x) == cos(π+x)
true
julia> cos(-π-x) ≈ cos(π+x)
false
The latter says a bit more about how cos is implemented on floats (vs big numbers) rather than about pi.
It is a bit surprising that it's not even approximately true, with cos symmetric and e.g.:
julia> cos(-π-0.1) == cos(π+0.1)
true
Note also that despite:
julia> cos(-big(π)-x) == cos(π+x)
true
Neither side gives the correct value as both are irrational (and the same), there the approximation of is just the same.
You did the right thing with -big(pi), note how careful you would have to be:
julia> -big(pi) ≈ big(-pi)
false
Most helpful comment
🤷♂️ Really starting to regret the
Irrational
type entirely.