Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: clarify that signbit(x) corresponds to x < zero(x) for nonzero x #56591

Open
nsajko opened this issue Nov 18, 2024 · 7 comments
Open

doc: clarify that signbit(x) corresponds to x < zero(x) for nonzero x #56591

nsajko opened this issue Nov 18, 2024 · 7 comments
Labels
design Design of APIs or of the language itself docs This change adds or pertains to documentation maths Mathematical functions

Comments

@nsajko
Copy link
Contributor

nsajko commented Nov 18, 2024

Otherwise signbit is useless for generic code.

Xref #53964, which discusses the case of zero x.

@nsajko nsajko added design Design of APIs or of the language itself docs This change adds or pertains to documentation maths Mathematical functions labels Nov 18, 2024
@barucden
Copy link
Contributor

What's the difference between this issue and the referenced issue #53964, please?

The referenced issue contains remarks about other tricky values besides zero. NaN, in particular, is non-zero and signbit(-NaN) is not equivalent to -NaN < 0.0, so the definition proposed here is not completely correct either.

@nsajko
Copy link
Contributor Author

nsajko commented Nov 18, 2024

What's the difference between this issue and the referenced issue

The referenced issue concerns zero values, while this issue concerns nonzero values.

@mikmoore
Copy link
Contributor

Otherwise signbit is useless for generic code.

I would argue that this is a true statement no matter what we document. Branch-cutting on +0.0 vs -0.0 is probably the only legitimate "generic" use for signbit and even then it's only applicable for the subset of types that support signed zeros. Otherwise it's for bit-twiddling and inspecting NaNs (neither of which are "generic use").

If you want to check <(0), you should not use signbit because it's not the same function and it doesn't have the same semantics.

@nsajko
Copy link
Contributor Author

nsajko commented Nov 19, 2024

"Useless for generic code" is basically the same thing as "useless", though. That is, if signbit (or another function) had no documented interface, using it or adding methods to it from any package would be a bad practice, making it almost useless (except for a few known argument types). The goal here is to make it useful by clarifying the doc string.

Here's a concrete motivating example: I want to check whether an Integer is less than zero. signbit(x) would be correct for all x such that x isa Integer that are currently available in the ecosystem, as far as I know, while also being more efficient for a type like BigInt (avoiding a ccall, better effects, inlineable, less machine code operations).

However, given the vague current state of the doc string (perhaps additionally encouraged by @mikmoore's laissez-faire stance), a package could in the future define a signbit method such that signbit(x) holds for, e.g., x == 7.

Clarifying the doc string should prevent such events and allow packages to rely on signbit (safely).

How about:

As long as x == x and !iszero(x), signbit(x) is equivalent to x < zero(x).

?

@nsajko
Copy link
Contributor Author

nsajko commented Nov 19, 2024

Here's an example laissez-faire interpretation of signbit: brainandforce/SignType.jl#1

There don't seem to be any guarantees that Base.signbit will be consistently false for positive values and true for negative values

@barucden
Copy link
Contributor

barucden commented Nov 19, 2024

There don't seem to be any guarantees that Base.signbit will be consistently false for positive values and true for negative values

I disagree. The current documentation exactly says that: "Return true if the value of the sign of x is negative, otherwise false.".

I agree with @mikmoore that signbit should not be used for a generic implementation of x > 0 (but it can be used for a specialized implementation of x > 0 for some types). I think the distinction between signbit and sign is confusing because both of them deal with a sign, just on a different level (or in a different context).

Being a mathematical operation, sign is consistent with the comparisons against zero; signbit is not due to zero and special inputs. Nevertheless, signbit and sign are equivalent for non-zero and non-special inputs. We already have such a pair of functions: isequal and ==. We could draw inspiration from the docs of isequal:

Similar to ==, except for the treatment of floating point numbers and of missing values. isequal treats all floating-point NaN values as equal to each other, treats -0.0 as unequal to 0.0, and missing as equal to missing. Always returns a Bool value.

The definition of signbit could be:

Return true if the sign of the representation of x is negative, otherwise false.

This function is similar to sign, except for the treatment of floating point numbers. For example, signbit treats both -NaN or -0.0 as negative and both NaN or 0.0 as positive.

@mikmoore
Copy link
Contributor

mikmoore commented Nov 19, 2024

I proposed language in #53964, but perhaps people would prefer that the docstring lay out a table:

Return `false` or `true` depending on the value of a `Real` number `x`:
- `x < 0` -> `true`
- `x > 0` -> `false`
- `x == 0` -> implementation defined
  - if a type represents distinct signed zeros, require `isequal(x, -0)` -> `true` and `isequal(x, +0)` -> `false`
- special values (e.g., `NaN`) -> implementation defined
  - prefer consistency with non-special values when possible

while also being more efficient for a type like BigInt (avoiding a ccall, better effects, inlineable, less machine code operations).

To my point about "general use," this is a non-idiomatic use of signbit. For a better resolution, see #53677. Although it's hard to imagine one is doing any actual BigInt math where x < 0 is a dominant cost in that calculation.


This function is similar to sign, except for the treatment of floating point numbers. For example, signbit treats both -NaN or -0.0 as negative and both NaN or 0.0 as positive.

I agree with the sentiment, but NaN is such a tricky case that this specific choice of language seems inadequate. Note (on my machine)

julia> signbit(NaN64), signbit(0.0/0.0), signbit(-0.0/0.0), signbit(-(0.0/0.0))
(false, true, true, false)

The choice of what specific NaN one receives (including the signbit) from an operation is implementation-specific to the hardware and is subject to whatever calculations might follow. It may not match the representation of the constant NaN64/NaN32/NaN16. Further, the fact that we do not print the sign of NaN (a decision I agree with) adds to the potential confusion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design Design of APIs or of the language itself docs This change adds or pertains to documentation maths Mathematical functions
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants