Julia documentation states that false acts like a strong zero in multiplication, such that NaN * false produces 0.0. However, Julia also defines-(x::Bool) = -Int(x), which seems to indicate -false is no longer a strong zero.
Personally, I find it a bit confusing, as:
julia> -NaN * false
-0.0
julia> NaN * -false
NaN
and
julia> 1 - NaN * false
1.0
julia> 1 - false * NaN
1.0
julia> - false * NaN + 1
NaN
julia> - NaN * false + 1
1.0
Iām not sure I agree thatās really a problem. false is a strong zero. -false is a different value of a different type. Why would that be a strong zero? Isnāt that exactly like how false + 1 is not a strong zero?
But is -false not more like -1 * false? Which then should be a strong zero. Or at least that I would think of it more like that than 0-false, though maybe that is not warranted.
I agree that -false does not need to be a strong zero. I am more curious about the best practice when using or writing code. If I write something similar to rotate!, how should I choose between y = -conj(s)*x + c*y and y = c*y - conj(s)*x, as they are not equivalent given s==false and x==NaN (or Inf)? It could be helpful if there were a convention I could follow.
Another question that just occurred to me is the behavior of +false. Julia documentation also describes the unitary plus as the identity operation. But
-false needs to be an Integer because -true is an integer. Otherwise the function -(x::Bool)wouldnāt be type stable. On the other hand, the unary plus could be defined as +(x::Bool) = x, I donāt think that would cause any problems.
I wonder what the reason is for the method +(x::Bool) = int(x). It was added in https://github.com/JuliaLang/julia/commit/d2f319f1c6a5193fe068c5a1d057f70899d58b5c without much explanation. One would expect x \cdot y = x \cdot (+y) holds for any numbers x, y, but it clearly doesnāt hold with this method in place (NaN * false == 0.0 and NaN * (+false) == NaN * 0 == NaN).
I think that the current behavior is consisent, if one considers the rules of operator precedence, and that the āstrong-zeronessā of false is not propagated to zeros of other types (even if derived from operating with false). Going through the examples given by the OP and other participants of this thread:
julia> -NaN * false # NaN * strong zero = normal zero promoted to `Float64`
-0.0
julia> -false # additive inverse of strong zero = normal zero of default integer type
0
julia> -1 * false # -1 * strong zero = normal zero promoted to Int, same as before
0
julia> NaN * -false # NaN * (-false = 0) = NaN
NaN
julia> 1 - NaN * false # 1 - (NaN * false = 0.0) = 1.0
1.0
julia> 1 - false * NaN # 1 - (false * NaN = 0.0) = 1.0
1.0
julia> - false * NaN + 1 # ((-false = 0.0) * NaN = NaN ) + 1 = NaN
NaN
julia> - NaN * false + 1 # (-NaN * false = 0.0) + 1 = 1.0
1.0
Yes, as best as I can remember that is the reason: all arithmetic operations promote Bool to Int. So +x isnāt the identity function exactly, but Iām not sure what to call it. Arithmetic identity? Identity except for Bool ? I wonder if there are other types in the ecosystem like Bool in this way?
Looking through our methods, I would say this is wrong:
-(A::AbstractArray) = broadcast_preserving_zero_d(-, A)
+(x::AbstractArray{<:Number}) = x
I like the current way of achieving consistency in Julia @heliosdrm, @jeff.bezanson, just wondering if I should now always write a - b * c rather than -b * c + a (or the other way around) to be consistent in my code. And if it is too much to expect other packages to pick a convention (at least within that package).
You should write fma(-b, c, a)
Yeah, this is a tradeoff in allowing false to be a āstrong zeroā. NaN*im works, but some identities no longer hold. I would probably prefer forms with fewer operations. Other than that just donāt worry about NaNs too much
Yeah it might make sense to add methods for those. Some might argue that you only use fma if you know you have floats, but it can be debated in an issue or PR.