A practical problem is that Python is an OO language, but math supplies a functional interface. That’s not a perfect fit, and it shows.
Coercing to float is practical, even for custom numeric types Python knows nothing about, because custom types can supply a special __float__ method. Then they work without math having to know anything about them. The same applies to core types (like int, Fraction, and Decimal). They also have __float__ methods, and math doesn’t generally require special code to accept them either.
There are rare exceptions, such as math.log(). That does make a special case of ints, because logs of giant ints are useful, and “just blowing up” is no good then. But that is special-case code, and custom numeric types have no way to participate in that.
As to the return type, to the extent that we have “use cases” here, they all appear to work with floats, and go on to multiply the result of sign() with a float. Returning an int would require coercing the result to a float anyway.
That said, some other implementations of sign() do return ints. Arguing about it seems pointless, because there’s no QED to be had. Pick one and move on. Since numpy picked float, that’s carries a lot of weight to me. I don’t anticipate ever using the function anyway ![]()