# \[ANN\] SignType.jl: What if Bool, but Sign

**URL:** <https://discourse.julialang.org/t/ann-signtype-jl-what-if-bool-but-sign/131182>\
**Category:** Package Announcements\
**Created:** [July 29, 2025, 7:09pm UTC](https://discourse.julialang.org/t/ann-signtype-jl-what-if-bool-but-sign/131182 "2025-07-29T19:09:58Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![brainandforce](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brainandforce/32/211054_2.png) [@brainandforce](https://discourse.julialang.org/u/brainandforce)\
**Post date:** [July 29, 2025, 7:09pm UTC](https://discourse.julialang.org/t/ann-signtype-jl-what-if-bool-but-sign/131182/1 "2025-07-29T19:09:58Z")

</div>

Link to package: [SignType.jl](https://github.com/brainandforce/SignType.jl)

In a lot of the code I’ve been writing, I’ve needed to do sign manipulation, and in principle this can be done with Boolean values. However, I find this to be a bit of a mess - just because I know how to treat `true` and `false` as signbits doesn’t mean it makes it clear or easy to read what I’ve done. So I decided to write a package that just implements a signbit type.

The feedback I got from other programmers outside of the Julia community was that a package that just implements a signbit type would be pointless, but at JuliaCon, several people encouraged me to register this package and said they would find it useful for readability and resolving issues that may arise with promotion.

This package exports a type called `Sign` which just represents a signbit. It supports the same logical semantics as `Bool`, but the arithmetic semantics correspond to the values +1 and -1. For convenience, we have the notation `Sign(+)` and `Sign(-)` (a bit of a hack, but it’s nice). Instead of exponentiating `-1`, you can just raise `Sign(-)` to a power.

There are some quirks of having a numeric type that does not represent zero:

- `zero(::Sign) === false`. (This is because `false` is a strong zero and is thus the best analog to `Sign(+)` and `Sign(-)`).
- The `Sign` constructor on any zero value generally returns the signbit, but `convert(Sign, x)` will throw an `InexactError`.
- Constructing or converting a `Sign` from an `isnan` value also throws an `InexactError`.

One other quirk: Rational division (`//`) of `Sign` with `Sign` returns a `Sign`, because `Rational{Sign}` and `Sign` represent exactly the same values.

There’s still some work to be done with getting random sign generation working (and perhaps having a dedicated `SignArray` type analogous to `BitArray`) but otherwise, this package should be ready to use if you want to clarify sign manipulation in your codebase.

Thanks to [@Kevin Qing](https://github.com/quildtide) for helping me out with this at JuliaCon 2025!

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [July 29, 2025, 7:24pm UTC](https://discourse.julialang.org/t/ann-signtype-jl-what-if-bool-but-sign/131182/2 "2025-07-29T19:24:19Z")

</div>

Link to package on JuliaHub (missing in the OP): SignType.jl

Regarding the package name, presumably it should have come in plural, if the package naming rules were to be followed.

After a quick look at the package:

> <https://github.com/brainandforce/SignType.jl/blob/17dbd153bd6a6dc4feb7626b555be8d88ff6632c/src/SignType.jl#L91-L91>

- Why use `primitive type`? Would it not be simpler to just make it a `struct` with a `Bool` field? Actually, that could maybe even be more amenable to some compiler optimizations, I think, because LLVM knows a `Bool` is just a single bit wide, instead of eight bits.

- Why subtype `Signed`? AFAIK `Signed` is meant for two’s complement integers, meant to support bitwise operations. I suppose `Integer` would be a more appropriate direct supertype.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [July 29, 2025, 7:31pm UTC](https://discourse.julialang.org/t/ann-signtype-jl-what-if-bool-but-sign/131182/3 "2025-07-29T19:31:18Z")

</div>

BTW, a package of my own possibly covers some of the same use cases:

```julia-repl
julia> using TypeDomainNaturalNumbers

julia> plus = TypeDomainInteger(1)
1

julia> minus = -plus
-1

```

A difference is that these are singleton types:

```julia-repl
julia> minus isa typeof(plus)
false

julia> Base.issingletontype(typeof(plus))
true

```

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [July 29, 2025, 7:36pm UTC](https://discourse.julialang.org/t/ann-signtype-jl-what-if-bool-but-sign/131182/4 "2025-07-29T19:36:49Z")

</div>

These `promote_rule` methods are not valid:

> <https://github.com/brainandforce/SignType.jl/blob/17dbd153bd6a6dc4feb7626b555be8d88ff6632c/src/SignType.jl#L147-L147>

> <https://github.com/brainandforce/SignType.jl/blob/17dbd153bd6a6dc4feb7626b555be8d88ff6632c/src/SignType.jl#L149-L149>

See:

- [`promote_rule`: forbid method definitions which may cause ambiguity for unknown types · Issue #55866 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/55866)

- [manual: methods: new section on avoiding ambiguity: "playing nicely" by nsajko · Pull Request #58005 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/pull/58005)

---

<div class="post-metadata">

**Author:** ![danielwe](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielwe/32/35657_2.png) [@danielwe](https://discourse.julialang.org/u/danielwe)\
**Post date:** [July 29, 2025, 7:55pm UTC](https://discourse.julialang.org/t/ann-signtype-jl-what-if-bool-but-sign/131182/5 "2025-07-29T19:55:06Z")

</div>

PSA to folks interested in this: do also take a look at the base functions [`copysign`](https://docs.julialang.org/en/v1/base/math/#Base.copysign) and [`flipsign`](https://docs.julialang.org/en/v1/base/math/#Base.flipsign). Took me way too many years before I discovered them.

Of course, they’re no replacement for a package like SignTypes.jl, so use that too! Just wanted to give these functions some PR to an audience that might benefit from them.

---

<div class="post-metadata">

**Author:** ![goerz](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerz/32/3269_2.png) [@goerz](https://discourse.julialang.org/u/goerz)\
**Post date:** [July 29, 2025, 10:11pm UTC](https://discourse.julialang.org/t/ann-signtype-jl-what-if-bool-but-sign/131182/6 "2025-07-29T22:11:24Z")

</div>

> [@nsajko](#):
>
> Regarding the package name, presumably it should have come in plural, if the package naming rules were to be followed.

The naming rules would have called for a plural only if the name had been `Signs`, for a “package providing the type `Sign`”. I didn’t find `SignType` objectionable, though: both `using Signs` and `using SignType` read well as “plain English”, which I think is at least in part behind those guidelines.

In any case, the package was registered a few days ago, so it is too late for any changes at this point. Anyone who is interested in upholding naming guidelines or other QA issues throughout the ecosystem is encouraged to keep an eye on the `#new-packages-feed` on Slack or Zulip and to leave comments on the registration during the waiting period. Conversely, anyone wanting feedback on a package, including the name, is encouraged to post in #Package Announcements before registration, or during the waiting period.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [July 29, 2025, 10:44pm UTC](https://discourse.julialang.org/t/ann-signtype-jl-what-if-bool-but-sign/131182/7 "2025-07-29T22:44:54Z")

</div>

Tangentially related is my package SymbolicSigns.jl, which lets you do computations with symbolic signs. The sign exponents are symbolic integers, thought of as degrees of some objects:

```julia-auto
julia> using SymbolicSigns

julia> da, db, dc = Deg(:a), Deg(:b), Deg(:c)
(Deg(:a), Deg(:b), Deg(:c))

julia> s = Sign(da*db + dc)
(-1)^(|c|+|a||b|)

julia> (s+1)^2
Linear{Sign{Symbol}, Int64} with 2 terms:
2*(-1)^(|c|+|a||b|)+2

```

---

<div class="post-metadata">

**Author:** ![brainandforce](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brainandforce/32/211054_2.png) [@brainandforce](https://discourse.julialang.org/u/brainandforce)\
**Post date:** [July 30, 2025, 8:53pm UTC](https://discourse.julialang.org/t/ann-signtype-jl-what-if-bool-but-sign/131182/8 "2025-07-30T20:53:10Z")

</div>

> [@danielwe](#):
>
> PSA to folks interested in this: do also take a look at the base functions [`copysign`](https://docs.julialang.org/en/v1/base/math/#Base.copysign) and [`flipsign`](https://docs.julialang.org/en/v1/base/math/#Base.flipsign). Took me way too many years before I discovered them.

One of the reasons why I decided to write this package is that there was a discussion about how the number of methods that explicitly include the term `path` in them (e.g. `splitpath`, `joinpath`) was evidence that Julia was in need of a path type. Similarly, with a lot of methods that are designed to handle signs (`sign`, `signbit`, `flipsign`, `copysign`) indicated to me that it would be good to have a `Sign` type.

> [@nsajko](#):
>
> Why use `primitive type`? Would it not be simpler to just make it a `struct` with a `Bool` field? Actually, that could maybe even be more amenable to some compiler optimizations, I think, because LLVM knows a `Bool` is just a single bit wide, instead of eight bits.

I can change this around: I figured since `Sign` mirrors the implementation of `Bool` it would make sense for it to be a primitive type, but I didn’t realize LLVM may be able to apply its optimizations for `Bool` if the struct explicitly contained one.

> [@nsajko](#):
>
> Why subtype `Signed`? AFAIK `Signed` is meant for two’s complement integers, meant to support bitwise operations. I suppose `Integer` would be a more appropriate direct supertype.

This is a bit of a radical decision, but the perspective I had on `Sign` is that it is effectively an `Int1`: just the sign bit of a signed integer and nothing else. Perhaps this is the wrong idea (there is the huge caveat that `Sign` cannot represent zero!) and this would be the better option, mirroring `Bool`.

`Sign` supports bitwise operations (with the exception of short-circuiting boolean operators, which require `Bool`).

> [@nsajko](#):
>
> These `promote_rule` methods are not valid:
> 
> [SignType.jl/src/SignType.jl at 17dbd153bd6a6dc4feb7626b555be8d88ff6632c · brainandforce/SignType.jl · GitHub](https://github.com/brainandforce/SignType.jl/blob/17dbd153bd6a6dc4feb7626b555be8d88ff6632c/src/SignType.jl#L147-L147) [SignType.jl/src/SignType.jl at 17dbd153bd6a6dc4feb7626b555be8d88ff6632c · brainandforce/SignType.jl · GitHub](https://github.com/brainandforce/SignType.jl/blob/17dbd153bd6a6dc4feb7626b555be8d88ff6632c/src/SignType.jl#L149-L149)
> 
> See:
> 
> - [`promote_rule`: forbid method definitions which may cause ambiguity for unknown types · Issue #55866 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/55866)
> - [manual: methods: new section on avoiding ambiguity: “playing nicely” by nsajko · Pull Request #58005 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/pull/58005)

I did not realize this. I guess when working with abstract types provided by `Base`, everything really needs to be explicitly defined for individual types rather than over supertypes. This was an issue when defining construction and conversion, but since my tests didn’t detect method ambiguities I assumed it was fine.

`Sign` should be unobtrusive in its promotion rules (promoting any `T<:Real` that can represent negative numbers with `Sign` should be promoted to `T`, and with `U<:Unsigned` it promotes to `signed(U)`).
