Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Unitful.jl changelog

## Unreleased

* ![Feature:](https://img.shields.io/badge/-feature-green) `Unitful.numtype` is now part of the public API. It returns the underlying numeric type of a quantity (e.g. `Float64` for `1.0u"m"`), of any `Number` (where it just returns its type), or of a `Base.Enum` (the backing integer type). On Julia ≥ 1.11 the name is declared `public`.

## v1.28.0 (2026-01-29)

* ![Feature:](https://img.shields.io/badge/-feature-green) Dimensionless quantities now support `iseven` and `isodd` ([#829](https://github.com/JuliaPhysics/Unitful.jl/pull/829)).
Expand Down
6 changes: 6 additions & 0 deletions docs/src/manipulations.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ Unitful.dimension
Unitful.ustrip
```

## Numeric type extraction

```@docs
Unitful.numtype
```

## Unit multiplication

```@docs
Expand Down
5 changes: 5 additions & 0 deletions src/Unitful.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export @logscale, @logunit, @dB, @B, @cNp, @Np
export Level, Gain
export uparse

# Public, but not exported to avoid name clashes (`public` requires Julia ≥ 1.11).
if VERSION >= v"1.11.0-DEV.469"
eval(Meta.parse("public numtype"))
end

const unitmodules = Vector{Module}()

function _basefactors(m::Module)
Expand Down
33 changes: 33 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,41 @@
@inline isunitless(::Units) = false
@inline isunitless(::Units{()}) = true

"""
numtype(x::Number)
numtype(::Type{T}) where {T<:Number}
numtype(x::Base.Enum)
numtype(::Type{T}) where {T<:Base.Enum}

Return the underlying numeric type of a number or number type.

For an [`AbstractQuantity`](@ref Unitful.AbstractQuantity), this is the type
parameter describing the value stored alongside the units. For an `Enum`, it is
the integer type that backs the enumeration. For a plain `Number`, there is
nothing to strip, so `numtype` returns its type. Other number-like types can
extend `numtype` by adding methods.

```jldoctest
julia> using Unitful

julia> Unitful.numtype(1.0u"m")
Float64

julia> Unitful.numtype(typeof(1.0u"m"))
Float64

julia> Unitful.numtype(1 + 2im)
Complex{Int64}
```
"""
function numtype end

@inline numtype(x::Number) = typeof(x)
@inline numtype(::Type{T}) where {T<:Number} = T
@inline numtype(::AbstractQuantity{T}) where {T} = T
@inline numtype(::Type{Q}) where {T, Q<:AbstractQuantity{T}} = T
@inline numtype(x::Base.Enum) = numtype(typeof(x))
@inline numtype(::Type{E}) where {T, E<:Base.Enum{T}} = T

@inline dimtype(u::Unit{U,D}) where {U,D} = D

Expand Down
24 changes: 23 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -562,12 +562,34 @@ Unitful.uconvert(U::Unitful.Units, q::QQQ) = uconvert(U, Quantity(q.val, cm))
# promoting the second time should not change the types
@test_throws ErrorException promote(px, py)
end
@testset "> Some internal behaviors" begin
@testset "> numtype" begin
# quantities
@test Unitful.numtype(Quantity{Float64}) <: Float64
@test Unitful.numtype(Quantity{Float64, 𝐋}) <: Float64
@test Unitful.numtype(typeof(1.0kg)) <: Float64
@test Unitful.numtype(1.0kg) <: Float64
# plain numbers (public API fallback)
@test Unitful.numtype(1) === Int
@test Unitful.numtype(1.0) === Float64
@test Unitful.numtype(1 + 2im) === Complex{Int}
@test Unitful.numtype(1 // 2) === Rational{Int}
@test Unitful.numtype(Float64) === Float64
@test Unitful.numtype(Complex{Float32}) === Complex{Float32}
# Bool is a Number, so it hits the generic fallback
@test Unitful.numtype(true) === Bool
@test Unitful.numtype(Bool) === Bool
# Enums carry their backing integer type as a parameter
@eval @enum NumtypeEnum::Int32 numtype_a numtype_b
@test Unitful.numtype(numtype_a) === Int32
@test Unitful.numtype(NumtypeEnum) === Int32
# Do not accidentally support `Union{}`
@test_throws MethodError Unitful.numtype(Union{})
# not a Number → MethodError
@test_throws MethodError Unitful.numtype("not a number")
@test_throws MethodError Unitful.numtype(String)
if VERSION >= v"1.11.0-DEV.469"
@test Base.ispublic(Unitful, :numtype)
end
end
end

Expand Down
Loading