Skip to content

maximum(::Broadcasted) can produce buggy outputs with stateful functions #36081

@tkf

Description

@tkf

Combining a stateful function, lazy broadcasted object, and reduction can produce a buggy result.

Here is a demo:

function badzero_before_second_pass(i)
    global FIRST_PASS
    if i == 1
        if FIRST_PASS
            FIRST_PASS = false
            return -0.0  # "badzero"
        else
            return 0.0
        end
    end
    return -1
end

using Base.Broadcast: broadcasted, instantiate

FIRST_PASS = true
@show maximum(instantiate(broadcasted(badzero_before_second_pass, 1:17)))

FIRST_PASS = true
@show maximum(badzero_before_second_pass.(1:17))

prints

maximum(instantiate(broadcasted(badzero_before_second_pass, 1:17))) = 0.0
maximum(badzero_before_second_pass.(1:17)) = -0.0

This is because maximum go over an array twice:

julia/base/reduce.jl

Lines 594 to 609 in acd0e83

v = op(op(v1,v2),op(v3,v4))
for i in start:last
@inbounds ai = A[i]
v = op(v, f(ai))
end
# enforce correct order of 0.0 and -0.0
# e.g. maximum([0.0, -0.0]) === 0.0
# should hold
if isbadzero(op, v)
for i in first:last
x = @inbounds A[i]
isgoodzero(op,x) && return x
end
end
return v

This is not new in the sense as it could have happened with something like https://github.com/JuliaArrays/MappedArrays.jl.

(I'm not sure what should be done with it but I just thought it's worth recording in the issue. Personally, I think stateful function should be "banned" (i.e., declared to be an undefined behavior) in broadcasting but I've seen, e.g., @mbauman mentioning rand.() is a nice idiom so this is likely not everyone's favorite definition. In general, I think we need to formalize what to be expected from broadcasting. For example, sparse style broadcasting already expects some kind of pureness in the functions.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    broadcastApplying a function over a collectionfoldsum, maximum, reduce, foldl, etc.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions