Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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: 3 additions & 1 deletion src/DynamicQuantities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount
export uparse, @u_str

include("fixed_rational.jl")
include("lazy_float.jl")

include("types.jl")
include("utils.jl")
include("math.jl")
include("units.jl")

import Requires: @init, @require
import .Units: uparse, @u_str
import .Units: uparse, @u_str, DEFAULT_UNIT_TYPE

if !isdefined(Base, :get_extension)
@init @require Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" include("../ext/DynamicQuantitiesUnitfulExt.jl")
Expand Down
32 changes: 32 additions & 0 deletions src/lazy_float.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# This is used to store floats without forcing promotion on other
# numeric types.
struct LazyFloat64 <: AbstractFloat
value::Float64
end

LazyFloat64(x::LazyFloat64) = x
LazyFloat64(x::Number) = LazyFloat64(convert(Float64, x))
float(x::LazyFloat64) = x.value

Base.convert(::Type{LazyFloat64}, x::LazyFloat64) = x
Base.convert(::Type{LazyFloat64}, x::FixedRational) = LazyFloat64(convert(Float64, x))
Base.convert(::Type{LazyFloat64}, x::Number) = LazyFloat64(x)
Base.convert(::Type{T}, x::LazyFloat64) where {T<:Number} = convert(T, float(x))
Base.promote_rule(::Type{LazyFloat64}, ::Type{T}) where {T} = T

(::Type{T})(x::LazyFloat64) where {T<:Number} = T(float(x))

Base.show(io::IO, x::LazyFloat64) = print(io, float(x))

Base.:+(a::LazyFloat64, b::LazyFloat64) = LazyFloat64(float(a) + float(b))
Base.:-(a::LazyFloat64) = LazyFloat64(-float(a))
Base.:-(a::LazyFloat64, b::LazyFloat64) = LazyFloat64(float(a) - float(b))
Base.:*(a::LazyFloat64, b::LazyFloat64) = LazyFloat64(float(a) * float(b))
Base.inv(a::LazyFloat64) = LazyFloat64(inv(float(a)))
Base.abs(a::LazyFloat64) = LazyFloat64(abs(float(a)))
Base.:/(a::LazyFloat64, b::LazyFloat64) = a * inv(b)
Base.:^(a::LazyFloat64, b::Int) = LazyFloat64(float(a) ^ b)
Base.:^(a::LazyFloat64, b::LazyFloat64) = LazyFloat64(float(a) ^ float(b))
Base.sqrt(a::LazyFloat64) = LazyFloat64(sqrt(float(a)))
Base.cbrt(a::LazyFloat64) = LazyFloat64(cbrt(float(a)))
Base.eps(::Type{LazyFloat64}) = eps(Float64)
41 changes: 21 additions & 20 deletions src/units.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ export uparse, @u_str
import ..DEFAULT_DIM_TYPE
import ..DEFAULT_VALUE_TYPE
import ..Quantity
import ..LazyFloat64

@assert DEFAULT_VALUE_TYPE == Float64 "`units.jl` must be updated to support a different default value type."
const DEFAULT_UNIT_TYPE = LazyFloat64

macro add_prefixes(base_unit, prefixes)
@assert prefixes.head == :tuple
Expand All @@ -23,26 +24,26 @@ function _add_prefixes(base_unit::Symbol, prefixes)
for (prefix, value) in zip(keys(all_prefixes), values(all_prefixes))
prefix in prefixes || continue
new_unit = Symbol(prefix, base_unit)
push!(expr.args, :(const $new_unit = $value * $base_unit))
push!(expr.args, :(const $new_unit = DEFAULT_UNIT_TYPE($value) * $base_unit))
end
return expr
end

# SI base units
"Length in meters. Available variants: `fm`, `pm`, `nm`, `μm` (/`um`), `cm`, `dm`, `mm`, `km`, `Mm`, `Gm`."
const m = Quantity(1.0, length=1)
const m = Quantity(DEFAULT_UNIT_TYPE(1.0), length=1)
"Mass in grams. Available variants: `μg` (/`ug`), `mg`, `kg`."
const g = Quantity(1e-3, mass=1)
const g = Quantity(DEFAULT_UNIT_TYPE(1e-3), mass=1)
"Time in seconds. Available variants: `fs`, `ps`, `ns`, `μs` (/`us`), `ms`, `min`, `h` (/`hr`), `day`, `yr`, `kyr`, `Myr`, `Gyr`."
const s = Quantity(1.0, time=1)
const s = Quantity(DEFAULT_UNIT_TYPE(1.0), time=1)
"Current in Amperes. Available variants: `nA`, `μA` (/`uA`), `mA`, `kA`."
const A = Quantity(1.0, current=1)
const A = Quantity(DEFAULT_UNIT_TYPE(1.0), current=1)
"Temperature in Kelvin. Available variant: `mK`."
const K = Quantity(1.0, temperature=1)
const K = Quantity(DEFAULT_UNIT_TYPE(1.0), temperature=1)
"Luminosity in candela. Available variant: `mcd`."
const cd = Quantity(1.0, luminosity=1)
const cd = Quantity(DEFAULT_UNIT_TYPE(1.0), luminosity=1)
"Amount in moles. Available variant: `mmol`."
const mol = Quantity(1.0, amount=1)
const mol = Quantity(DEFAULT_UNIT_TYPE(1.0), amount=1)

@add_prefixes m (f, p, n, μ, u, c, d, m, k, M, G)
@add_prefixes g (μ, u, m, k)
Expand All @@ -56,9 +57,9 @@ const mol = Quantity(1.0, amount=1)
"Frequency in Hertz. Available variants: `kHz`, `MHz`, `GHz`."
const Hz = inv(s)
"Force in Newtons."
const N = kg * m / s^2
const N = kg * m / (s * s)
"Pressure in Pascals. Available variant: `kPa`."
const Pa = N / m^2
const Pa = N / (m * m)
"Energy in Joules. Available variant: `kJ`."
const J = N * m
"Power in Watts. Available variants: `kW`, `MW`, `GW`."
Expand Down Expand Up @@ -89,11 +90,11 @@ const T = N / (A * m)

# Common assorted units
## Time
const min = 60 * s
const h = 60 * min
const min = DEFAULT_UNIT_TYPE(60) * s
const h = DEFAULT_UNIT_TYPE(60) * min
const hr = h
const day = 24 * h
const yr = 365.25 * day
const day = DEFAULT_UNIT_TYPE(24) * h
const yr = DEFAULT_UNIT_TYPE(365.25) * day

@add_prefixes min ()
@add_prefixes h ()
Expand All @@ -103,13 +104,13 @@ const yr = 365.25 * day

## Volume
"Volume in liters. Available variants: `mL`, `dL`."
const L = dm^3
const L = dm * dm * dm

@add_prefixes L (m, d)

## Pressure
"Pressure in bars."
const bar = 100 * kPa
const bar = DEFAULT_UNIT_TYPE(100) * kPa

@add_prefixes bar ()

Expand All @@ -126,12 +127,12 @@ Parse a string containing an expression of units and return the
corresponding `Quantity` object with `Float64` value. For example,
`uparse("m/s")` would be parsed to `Quantity(1.0, length=1, time=-1)`.
"""
function uparse(s::AbstractString)
return as_quantity(eval(Meta.parse(s)))::Quantity{DEFAULT_VALUE_TYPE,DEFAULT_DIM_TYPE}
function uparse(s::AbstractString)::Quantity{LazyFloat64,DEFAULT_DIM_TYPE}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that any unit is converted to a LazyFloat64 at the end. So even u"1.0" would become a LazyFloat64.

return as_quantity(eval(Meta.parse(s)))
end

as_quantity(q::Quantity) = q
as_quantity(x::Number) = Quantity(convert(DEFAULT_VALUE_TYPE, x), DEFAULT_DIM_TYPE)
as_quantity(x::Number) = Quantity(convert(LazyFloat64, x), DEFAULT_DIM_TYPE)
as_quantity(x) = error("Unexpected type evaluated: $(typeof(x))")

"""
Expand Down
25 changes: 17 additions & 8 deletions test/unittests.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using DynamicQuantities
using DynamicQuantities: FixedRational
using DynamicQuantities: DEFAULT_DIM_BASE_TYPE, DEFAULT_DIM_TYPE, DEFAULT_VALUE_TYPE
using DynamicQuantities: DEFAULT_DIM_BASE_TYPE, DEFAULT_DIM_TYPE, DEFAULT_VALUE_TYPE, DEFAULT_UNIT_TYPE
using Ratios: SimpleRatio
using SaferIntegers: SafeInt16
using Test
Expand Down Expand Up @@ -335,13 +335,22 @@ end
@test ustrip(z) ≈ 60 * 60 * 24 * 365.25

# Test type stability of extreme range of units
@test typeof(u"1") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"1f0") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"s"^2) == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"Ω") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"Gyr") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"fm") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"fm"^2) == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"1") == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE}
@test typeof(u"1f0") == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE}
@test typeof(u"s"^2) == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE}
@test typeof(u"Ω") == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE}
@test typeof(u"Gyr") == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE}
@test typeof(u"fm") == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE}
@test typeof(u"fm"^2) == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE}

# Test type demotion
@test typeof(1u"m") == Quantity{Int64,DEFAULT_DIM_TYPE}
@test typeof(1f0u"m") == Quantity{Float32,DEFAULT_DIM_TYPE}
@test typeof(1.0u"m") == Quantity{Float64,DEFAULT_DIM_TYPE}

@test typeof(1u"m^2/s") == Quantity{Int64,DEFAULT_DIM_TYPE}
@test typeof(1f0u"m^2/s") == Quantity{Float32,DEFAULT_DIM_TYPE}
@test typeof(1.0u"m^2/s") == Quantity{Float64,DEFAULT_DIM_TYPE}

@test_throws LoadError eval(:(u":x"))
end
Expand Down