{"id":28210693,"url":"https://github.com/emmt/neutrals.jl","last_synced_at":"2026-02-16T13:08:33.693Z","repository":{"id":293323837,"uuid":"983671790","full_name":"emmt/Neutrals.jl","owner":"emmt","description":"Universal neutral numbers of the addition and multiplication of numbers in Julia.","archived":false,"fork":false,"pushed_at":"2025-12-19T17:05:05.000Z","size":90,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-22T06:53:21.019Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/emmt.png","metadata":{"files":{"readme":"README.md","changelog":"NEWS.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-05-14T18:26:59.000Z","updated_at":"2025-12-19T16:36:27.000Z","dependencies_parsed_at":"2025-10-09T01:10:44.816Z","dependency_job_id":"7a0fc25f-c466-4111-bc0e-75491965e25d","html_url":"https://github.com/emmt/Neutrals.jl","commit_stats":null,"previous_names":["emmt/neutrals.jl"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/emmt/Neutrals.jl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FNeutrals.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FNeutrals.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FNeutrals.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FNeutrals.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/emmt","download_url":"https://codeload.github.com/emmt/Neutrals.jl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FNeutrals.jl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29508741,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-16T09:05:14.864Z","status":"ssl_error","status_checked_at":"2026-02-16T08:55:59.364Z","response_time":115,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-17T17:10:26.464Z","updated_at":"2026-02-16T13:08:33.687Z","avatar_url":"https://github.com/emmt.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Neutrals\n[![Build Status](https://github.com/emmt/Neutrals.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/emmt/Neutrals.jl/actions/workflows/CI.yml?query=branch%3Amain)\n[![Build Status](https://ci.appveyor.com/api/projects/status/github/emmt/Neutrals.jl?svg=true)](https://ci.appveyor.com/project/emmt/Neutrals-jl)\n[![version](https://juliahub.com/docs/General/Neutrals/stable/version.svg)](https://juliahub.com/ui/Packages/General/Neutrals)\n[![Coverage](https://codecov.io/gh/emmt/Neutrals.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/emmt/Neutrals.jl)\n[![deps](https://juliahub.com/docs/General/Neutrals/stable/deps.svg)](https://juliahub.com/ui/Packages/General/Neutrals?t=2)\n[![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)\n\nThis package provides two singleton values, `𝟘` and `𝟙` which are the respective *neutral\nelements* for the addition and multiplication of numbers regardless of their types. In\nother words, whatever the type and value of the number `x`, `𝟘 + x` and `𝟙*x` yields `x`\nunchanged and without computations. Hence, `𝟘 + x === x` and `𝟙*x === x` hold even though\n`x` is not an instance of `isbitstype` like `BigInt` or `BigFloat`. Besides, `𝟘` is a\nso-called *strong zero* which means that `𝟘*x` always yields `𝟘` without computations. In\nparticular, `𝟘*Inf` and `𝟘*NaN` both yield `𝟘`. Since `𝟘` and `𝟙` are singletons, their\nspecific behaviors in arithmetic operations is inferable at compile time and can result in\nvaluable optimizations.\n\nConsistent rules for the subtraction and division follow from the rules for the addition\nand multiplication with `𝟘` or `𝟙`. For example, `-𝟙`, the opposite of `𝟙`, is also a\nsingleton whose effect in a multiplication is to negate the other operand: `(-𝟙)*x` always\nyields `-x`.\n\nTable of contents:\n* [Compatibility](#compatibility)\n* [Binary Operations](#binary-operations)\n  * [Addition](#addition)\n  * [Subtraction](#subtraction)\n  * [Multiplication](#multiplication)\n  * [Division](#division)\n  * [`div`, `rem`, and `mod`](#div-rem-and-mod)\n  * [Bitwise Binary Operations](#bitwise-binary-operations)\n  * [Bit-shift Operations](#bit-shift-operations)\n  * [Comparisons](#comparisons)\n* [Conversion Rules](#conversion-rules)\n* [Broadcasting Rules](#broadcasting-rules)\n* [Ranges](#ranges)\n* [Dimensionful Quantities](#dimensionful-quantities)\n* [Miscellaneous](#miscellaneous)\n* [Related packages](#related-packages)\n\n\n## Compatibility\n\nBefore version 1.3 of Julia, `𝟘` and `𝟙` cannot be used as names of constants, the aliases\n`ZERO` and `ONE` can be used instead.\n\n## Binary operations\n\nThis section describes the rules involving a neutral number and any other number. For\n[commutative operations](https://en.wikipedia.org/wiki/Commutative_property) like the\nmultiplication (`*`), the addition (`+`), binary bitwise operations (`|`, `\u0026`, and `xor`\nor `⊻`), and the comparison for equality (`==`), the same rules apply if the operands are\nexchanged.\n\n### Addition\n\nThe following rules apply for the addition involving a neutral number and any\ndimensionless number `x`:\n\n``` julia\nx + 𝟘 -\u003e x\nx + 𝟙 -\u003e x + one(x)\nx + (-𝟙) -\u003e x - one(x)\n```\n\nThe result of an addition with a neutral number has the same type as `x`, except if `x` is\na Boolean and the neutral number is `±𝟙` which yield an `Int` (as does the addition of\nBooleans in Julia).\n\n### Subtraction\n\nThe rules for the subtraction involving a neutral number and any dimensionless number `x`\nfollow from those of the addition:\n\n``` julia\nx - 𝟘 -\u003e x\n𝟘 - x -\u003e -x\nx - 𝟙 -\u003e x - one(x)\n𝟙 - x -\u003e one(x) - x\nx - (-𝟙) -\u003e x + one(x)\n(-𝟙) - x -\u003e -one(x) - x\n```\n\n### Multiplication\n\nThe following rules apply for the multiplication of a neutral number and a number `x`:\n\n``` julia\n𝟘*x -\u003e 𝟘         # if `x` is dimensionless\n𝟘*x -\u003e 𝟘*unit(x) # if `x` is dimensionful\n𝟙*x -\u003e x\n-𝟙*x -\u003e -x\n```\n\nIf `x` is dimensionful, the result has the same dimensions as `x`. For example:\n\n``` julia\njulia\u003e using Neutrals, Unitful.DefaultSymbols\n\njulia\u003e 𝟘*3\n𝟘\n\njulia\u003e 𝟘*(3kg)\n𝟘 kg\n\n```\n\n\n### Division\n\nThe rules for the division involving a neutral number and any number `x` follow from those\nof the multiplication:\n\n``` julia\n𝟘/x -\u003e 𝟘         # if `x` is dimensionless\n𝟘/x -\u003e 𝟘/unit(x) # if `x` is dimensionful\n𝟙/x -\u003e inv(x)\n-𝟙*x -\u003e -inv(x)\nx/𝟘 -\u003e DivideError\nx/𝟙 -\u003e x\nx/-𝟙 -\u003e -x\n```\n\n\n### `div`, `rem`, and `mod`\n\nSimilar rules are implemented for the quotient and remainder of the truncated division\n(`div` or `÷` and `rem` or `%`) and for the modulo (`mod`). In Julia, for `x` and `y`\nintegers `div(x, y)` and `rem(x, y)` yield a result of the signedness of `x`, while\n`mod(x, y)` yields a result of the signedness of `y`. This rule is preserved when one of\nthe operand is a neutral number, considering that neutral numbers are signed integers.\n\nFor `div`, `rem`, and `mod` when one operand is a Boolean and the other is a neutral\nnumber the behavior implemented in Julia for Booleans is reflected. This implies that the\nneutral number be converted into a `Bool`. Hence, if the neutral operand is `-𝟙`, an\n`InexactError` is thrown.\n\n\n### Bitwise Binary Operations\n\nIn binary bitwise operations `|`, `\u0026`, and `xor` (also denoted `⊻`) between an integer `i`\nand a neutral number `n`, the implemented rules are such that the result is as if `𝟘` and\n`𝟙` are converted to the type of `i` while `-𝟙` is assumed to represent a bit mask of the\nsame type as `i` with all bits set to `1`, that is `~zero(i)`. For a given binary bitwise\noperation denoted by `⋄`, this corresponds to the following rules:\n\n``` julia\ni ⋄  𝟘 -\u003e i ⋄ zero(i)\ni ⋄  𝟙 -\u003e i ⋄ one(i)\ni ⋄ -𝟙 -\u003e i ⋄ ~zero(i)\n```\n\nThese rules may be optimized in the implementation. For example:\n\n``` julia\ni |  𝟘 -\u003e i\ni | -𝟙 -\u003e ~zero(i)\ni \u0026  𝟘 -\u003e zero(i)\ni \u0026 -𝟙 -\u003e i\ni ⊻  𝟘 -\u003e i\n```\n\nIt may be noted that, `i \u0026 𝟘` yields `zero(i)` and not `𝟘` as would do `i*𝟘`. This is\nbecause `𝟘` is defined relatively to the addition and the multiplication (`+` and `*`),\nnot the *bitwise-and* operation (`\u0026`).\n\n\n### Bit-shift Operations\n\nIn Julia, bit-shifting integer `x` by `n` bits yields a result of the same type as `x`\nexcept for Booleans for which the result is an `Int`. With the `Neutrals` package, if `n`\nis a neutral number (`𝟘`, `𝟙`, or `-𝟙`), the following rules are implemented:\n\n``` julia\nx \u003c\u003c   𝟘 -\u003e x\nx \u003c\u003c   𝟙 -\u003e x \u003c\u003c UInt(1)\nx \u003c\u003c  -𝟙 -\u003e x \u003e\u003e UInt(1)\nx \u003e\u003e   𝟘 -\u003e x\nx \u003e\u003e   𝟙 -\u003e x \u003e\u003e UInt(1)\nx \u003e\u003e  -𝟙 -\u003e x \u003c\u003c UInt(1)\nx \u003e\u003e\u003e  𝟘 -\u003e x\nx \u003e\u003e\u003e  𝟙 -\u003e x \u003e\u003e\u003e UInt(1)\nx \u003e\u003e\u003e -𝟙 -\u003e x \u003c\u003c UInt(1)\n```\n\nThese rules provide two optimizations: bit shifting `x` by `𝟘` bits leaves `x` unchanged,\nwhile bit shifting `x` by `±𝟙` bit shifts `x` by one bit in the correct direction where\n`UInt(1)` is to dispatch on the type of `x` not on that of the number of bits. This\nclosely reflects the behavior implemented in `base/int.jl` except that bit-shifting by `𝟘`\nalways yields the left operand  unchanged even though it is a Boolean.\n\n\n### Comparisons\n\nWhen comparing values with `==`, `\u003c`, `\u003c=`, `isequal`, `isless`, and `cmp`, the rule of\nthumb is that the behavior shall reflect the expression. This poses no problem for `𝟘` and\n`𝟙` which are representable by any numeric type. This is not the case of `-𝟙` which\ncannot be simply converted to a Boolean, an unsigned number (integer, rational, or\ncomplex).\n\nIf `u` is an unsigned number, the following identities hold:\n\n``` julia\nu == -𝟙 -\u003e false\nu != -𝟙 -\u003e true\nisequal(u, -𝟙) -\u003e false\n```\n\nOf course, these binary operators being symmetric, their result does not depend on the\norder of the arguments.\n\nFurthermore, if `u` is an unsigned real (i.e., not a complex), then:\n\n``` julia\nu \u003c -𝟙 -\u003e false\nu ≤ -𝟙 -\u003e false\nu \u003e -𝟙 -\u003e true\nu ≥ -𝟙 -\u003e true\nisless(u, -𝟙) -\u003e false\nisless(-𝟙, u) -\u003e true\ncmp(u, -𝟙) -\u003e 1\ncmp(-𝟙, u) -\u003e -1\n```\n\n\n## Conversion Rules\n\nAs for other numbers, a neutral number `n` (`𝟘`, `𝟙`, or `-𝟙`) can be converted into a\nnumeric type `T` by `T(n)` which yields a value of type `T`. This operation is always\nsuccessful for `𝟘` and `𝟙` which are representable by any numeric type. For `-𝟙`, an\n`InexactError` exception is thrown if `T` is not a signed type, this includes Booleans,\nunsigned integers, but also rationals and complexes with Boolean or unsigned parts. As for\nany non-big integer, `AbstractFloat(n)` and `float(n)` both yield `n` converted to\n`Float64`.\n\nThe expression `n % T` can also be used to *convert* a neutral number `n` to an integer\ntype `T` modulo the number of integers representable in `T`. In this case, `-𝟙 % T` works\neven though `T` is unsigned. For example:\n\n``` julia\njulia\u003e -𝟙 % UInt16\n0xffff\n\n```\n\nThe method `convert(T, n)` with `T` a numeric type and `n` a neutral number amounts to\ncalling `T(n)`. As a result, a neutral number `n` is automatically converted to a value of\ntype `T` when stored in an array whose elements are of type `T` or when assigned to a\nfield of type `T` in a mutable structure. For example, assuming `x` is an array of\nnumbers, it can be zero-filled by:\n\n``` julia\nfor i in eachindex(x); x[i] = zero(eltype(x)); end\n```\n\nwhich, provided `eltype(x)` is dimensionless, can be written as:\n\n``` julia\nfor i in eachindex(x); x[i] = 𝟘; end\n```\n\nor, better, as:\n\n``` julia\nfor i in eachindex(x); x[i] *= 𝟘; end\n```\n\nwhich works whether `eltype(x)` is dimensionful or dimensionless.\n\n\n## Broadcasting Rules\n\nSome broadcasted operations involving a neutral number and a number or an array of numbers\n`x` are optimized to return `x` unchanged:\n\n``` julia\nx .+ 𝟘 -\u003e x # idem for 𝟘 .+ x -\u003e x\nx .- 𝟘 -\u003e x\n𝟙 .* x -\u003e x # idem for x .* 𝟙\nx ./ 𝟙 -\u003e x\nx .^ 𝟙 -\u003e x\n```\n\nIn addition, if `x` has integer element type, then:\n\n``` julia\nx .÷ 𝟙    -\u003e x\nx .| 𝟘    -\u003e x # idem for 𝟘 .| x\nx .\u0026 (-𝟙) -\u003e x # idem for (-𝟙) .\u0026 x\nx .⊻ 𝟘    -\u003e x # idem for 𝟘 .⊻ x -\u003e x\nx .\u003c\u003c 𝟘   -\u003e x\nx .\u003e\u003e 𝟘   -\u003e x\nx .\u003e\u003e\u003e 𝟘  -\u003e x\n```\n\nOther broadcasted operations should work as can be inferred from the rules for numbers.\n\nFor multiplying or dividing an array of numbers by neutral numbers, you may\ndirectly use the `*`, `/`, or `\\` operators instead of `.*`, `./`, or `.\\`:\n\n``` julia\n𝟘*x -\u003e similar(x, typeof(𝟘*unit(eltype(x))))\n𝟙*x -\u003e x\n𝟙\\x -\u003e x\nx/𝟙 -\u003e x\n(-𝟙)*x -\u003e -x\n(-𝟙)\\x -\u003e -x\nx/(-𝟙) -\u003e -x\n```\n\nNote that `𝟘*x` is a lightweight array (`sizeof(𝟘*x) = 0`) whose elements are all equal to\nthe singleton `𝟘` if `eltype(x)` is dimensionless or to the singleton `𝟘*unit(eltype(x))`\nif `eltype(x)` is dimensionful (see [Dimensionful Quantities](#dimensionful-quantities)).\n\n\n## Ranges\n\nRanges can be constructed with neutral numbers specified as the start, step, and/or stop\nparameters of the range. `𝟙:stop` is identical to `Base.OneTo(stop)` if `stop` is a\nnon-neutral integer or is `𝟙` and is identical to `Base.OneTo(Int(stop))` otherwise.\n`start:𝟙:stop` identical to `start:stop` whatever, `start` and `stop`.  Examples:\n\n``` julia\n𝟙:6 -\u003e Base.OneTo(6)\n3:𝟙:6 -\u003e 3:6\ncollect(𝟘:𝟘) -\u003e [𝟘]\ncollect(𝟙:𝟙) -\u003e [𝟙]\n```\n\n## Dimensionful Quantities\n\nNeutral numbers can work with dimensionful numbers provided the `Neutrals` package be\nproperly extended for such numbers and provided the operation makes sense (e.g., adding\n`𝟘` to a length in meters does not make sense because `𝟘` is dimensionless).\n\nThis is the case of the [`Unitful`](https://github.com/PainterQubits/Unitful.jl)\nquantities. For example:\n\n``` julia\nusing Unitful, Unitful.DefaultSymbols\nx = 3kg\n𝟘*x === 𝟘*unit(x)         # true\n𝟙*x === x                 # true\n-𝟙*x == -x                # true\nx + 𝟘                     # error, 𝟘 is dimensionless\nx + 𝟘*unit(x) == x        # true\nx - 𝟘                     # error, 𝟘 is dimensionless\nx - 𝟘*unit(x) == x        # true\n𝟘*unit(x) == zero(x)      # true\n𝟘*unit(x) !== zero(x)     # true\n𝟙*unit(x) == oneunit(x)   # true\n𝟙*unit(x) !== oneunit(x)  # true\n-𝟙*unit(x) == -oneunit(x) # true\n𝟙 == one(x)               # true\n𝟙 !== one(x)              # true\n```\n\nNote that `𝟘*unit(x)` is equal but not identical to `zero(x)` because it is `𝟘` with the\nunit of `x`.\n\n\n## Miscellaneous\n\n`Complex(x,y)` and `complex(x,y)` yield the same result as `x + y*im` even though `x` or\n`y` is a neutral number.\n\n\n## Macros\n\nThe macro `@dispatch_on_value sym expr` generates code that dispatches expression `expr`\nbased on the run-time value of the symbol `sym`.\n\nFor example:\n\n```julia\nfunction xpby!(dst::AbstractArray, x::AbstractArray, β::Number, y::AbstractArray)\n    @assert axes(dst) == axes(x) == axes(y)\n    @dispatch_on_value β unsafe_xpby!(dst, x, β, y)\n    return dst\nend\nfunction unsafe_xpby!(dst::AbstractArray, x::AbstractArray, β::Number, y::AbstractArray)\n    @inbounds @simd for i in eachindex(dst, x, y)\n        dst[i] = x[i] + β*y[i]\n    end\n    nothing\nend\n```\n\nAbove, the `@dispatch_on_value ...` statement expands to (with comments removed):\n\n```julia\nif !Neutrals.is_static_number(β) \u0026\u0026 Base.iszero(β)\n    unsafe_xpby!(dst, α, x, Neutrals.Neutral{0}()*TypeUtils.units_of(β), y)\nelseif !Neutrals.is_static_number(β) \u0026\u0026 β == Base.oneunit(β)\n    unsafe_xpby!(dst, α, x, Neutrals.Neutral{1}()*TypeUtils.units_of(β), y)\nelseif !Neutrals.is_static_number(β) \u0026\u0026 TypeUtils.is_signed(β) \u0026\u0026 β == -Base.oneunit(β)\n    unsafe_xpby!(dst, α, x, Neutrals.Neutral{-1}()*TypeUtils.units_of(β), y)\nelse\n    unsafe_xpby!(dst, α, x, β, y)\nend\n```\n\n## Related Packages\n\n- In base Julia, `false` behaves as a strong zero when multiplied by a float. Moreover it\n  preserves the sign of the other operand, e.g. `false*(-NaN)` yields `-0.0`. The sign is\n  not preserved in the multiplication by `𝟘` which yields `𝟘`.\n\n- [`Zeros.jl`](https://github.com/perrutquist/Zeros.jl) was a source of inspiration to\n  improve `Neutrals.jl`. `Zeros.jl` provides `Zero()` and `One()` which are also strong\n  neutral elements for addition and multiplication with numbers. `Zero()` and `One()` are\n  similar to `𝟘` or `ZERO`, and `𝟙` or `ONE`. However, `-One()` yields `-1` which is not a\n  singleton, division by `One()` converts the other operand to floating-point,\n  multiplication of a dimensionful number and `Zero()` is not supported, etc. Some\n  broadcasted binary operations involving a neutral number and and array `A` are faster,\n  like `𝟘 .+ A` compared to `Zero() .+ A`.\n\n- [`StaticNumbers.jl`](https://github.com/perrutquist/StaticNumbers.jl) is a generalization\n  of `Zeros` to other any numeric values, not just `0` and `1`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femmt%2Fneutrals.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Femmt%2Fneutrals.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femmt%2Fneutrals.jl/lists"}