mirror of
https://github.com/tgorordo/WignerSymbols.jl.git
synced 2026-06-05 15:42:15 -07:00
As HalfInteger <: Real, there should be no reason to convert anything in this situation. It happens because the convert method resorts to Float64 as an intermediate value. To still get conversion to floats, we can just dispatch on AbstractFloat instead. However, it should be better to convert the numerator to T first and then divide, so that we would not use a potentially lower precision intermediate value. This solves the problem where calling sum on an vector of HalfIntegers yields a floating point value, even though there is no reason to convert in the summation: julia> sum([HalfInteger(1//2), HalfInteger(3//2)]) 2.0 This is because there is an implicit convert(::Real) in the Base.add_sum function. With this patch the sum call correctly yields a HalfInteger. It also updates the tests related to HalfInteger convert methods: - Make sure that the convert tests also check types - Add a few tests for converting out of HalfInteger
190 lines
5.8 KiB
Julia
190 lines
5.8 KiB
Julia
# HalfInteger
|
|
|
|
"""
|
|
struct HalfInteger <: Real
|
|
|
|
Represents half-integer values.
|
|
|
|
---
|
|
|
|
HalfInteger(numerator::Integer, denominator::Integer)
|
|
|
|
Constructs a `HalfInteger` object as a rational number from the given integer numerator
|
|
and denominator values.
|
|
|
|
# Examples
|
|
|
|
```jldoctest
|
|
julia> HalfInteger(1, 2)
|
|
1/2
|
|
|
|
julia> HalfInteger(-2, 1)
|
|
-2
|
|
```
|
|
"""
|
|
struct HalfInteger <: Real
|
|
numerator::Int # with an implicit denominator of 2
|
|
|
|
function HalfInteger(num::Integer, den::Integer)
|
|
(den == 2) && return new(num)
|
|
(den == 1) && return new(2*num)
|
|
(den == 0) && throw(ArgumentError("Denominator can not be zero."))
|
|
# If non-trivial, we'll see if we can reduce it down to a half-integer
|
|
numerator, r = divrem(2*num, den)
|
|
if r == 0
|
|
return new(numerator)
|
|
else
|
|
throw(ArgumentError("$num // $den is not a half-integer value."))
|
|
end
|
|
end
|
|
end
|
|
|
|
"""
|
|
HalfInteger(x::Real)
|
|
|
|
Attempts to create a `HalfInteger` out of the real number `x`. Throws an `InexactError` if
|
|
`x` can not be represented as a half-integer value.
|
|
|
|
# Examples
|
|
|
|
```jldoctest
|
|
julia> HalfInteger(3)
|
|
3
|
|
|
|
julia> HalfInteger(1.5)
|
|
3/2
|
|
```
|
|
"""
|
|
HalfInteger(x::Real) = convert(HalfInteger, x)
|
|
|
|
Base.promote_rule(::Type{HalfInteger}, ::Type{<:Integer}) = HalfInteger
|
|
Base.promote_rule(::Type{HalfInteger}, T::Type{<:Rational}) = T
|
|
Base.promote_rule(::Type{HalfInteger}, T::Type{<:Real}) = T
|
|
|
|
Base.convert(::Type{HalfInteger}, n::Integer) = HalfInteger(2*n, 2)
|
|
function Base.convert(::Type{HalfInteger}, r::Rational)
|
|
if r.den == 1
|
|
return HalfInteger(2*r.num, 2)
|
|
elseif r.den == 2
|
|
return HalfInteger(r.num, 2)
|
|
else
|
|
throw(InexactError(:HalfInteger, HalfInteger, r))
|
|
end
|
|
end
|
|
function Base.convert(::Type{HalfInteger}, r::Real)
|
|
num = 2*r
|
|
if isinteger(num)
|
|
return HalfInteger(convert(Int, num), 2)
|
|
else
|
|
throw(InexactError(:HalfInteger, HalfInteger, r))
|
|
end
|
|
end
|
|
Base.convert(T::Type{<:Integer}, s::HalfInteger) = iseven(s.numerator) ? convert(T, s.numerator>>1) : throw(InexactError(Symbol(T), T, s))
|
|
Base.convert(T::Type{<:Rational}, s::HalfInteger) = convert(T, s.numerator//2)
|
|
Base.convert(T::Type{<:AbstractFloat}, s::HalfInteger) = convert(T, s.numerator) / T(2)
|
|
Base.convert(::Type{HalfInteger}, s::HalfInteger) = s
|
|
|
|
# Arithmetic
|
|
|
|
Base.:+(a::HalfInteger, b::HalfInteger) = HalfInteger(a.numerator+b.numerator, 2)
|
|
Base.:-(a::HalfInteger, b::HalfInteger) = HalfInteger(a.numerator-b.numerator, 2)
|
|
Base.:-(a::HalfInteger) = HalfInteger(-a.numerator, 2)
|
|
Base.:*(a::Integer, b::HalfInteger) = HalfInteger(a * b.numerator, 2)
|
|
Base.:*(a::HalfInteger, b::Integer) = b * a
|
|
Base.:<=(a::HalfInteger, b::HalfInteger) = a.numerator <= b.numerator
|
|
Base.:<(a::HalfInteger, b::HalfInteger) = a.numerator < b.numerator
|
|
Base.one(::Type{HalfInteger}) = HalfInteger(2, 2)
|
|
Base.zero(::Type{HalfInteger}) = HalfInteger(0, 2)
|
|
|
|
Base.floor(x::HalfInteger) = isinteger(x) ? x : x - HalfInteger(1, 2)
|
|
Base.floor(::Type{T}, x::HalfInteger) where T <: Integer = convert(T, floor(x))
|
|
|
|
Base.ceil(x::HalfInteger) = isinteger(x) ? x : x + HalfInteger(1, 2)
|
|
Base.ceil(::Type{T}, x::HalfInteger) where T <: Integer = convert(T, ceil(x))
|
|
|
|
# Hashing
|
|
|
|
function Base.hash(a::HalfInteger, h::UInt)
|
|
iseven(a.numerator) && return hash(a.numerator>>1, h)
|
|
num, den = a.numerator, 2
|
|
den = 1
|
|
pow = -1
|
|
if abs(num) < 9007199254740992
|
|
return hash(ldexp(Float64(num),pow), h)
|
|
end
|
|
h = Base.hash_integer(den, h)
|
|
h = Base.hash_integer(pow, h)
|
|
h = Base.hash_integer(num, h)
|
|
return h
|
|
end
|
|
|
|
# Parsing and printing
|
|
|
|
"""
|
|
parse(HalfInteger, s)
|
|
|
|
Parses the string `s` into the corresponding `HalfInteger`-value. String can either be a
|
|
number or a fraction of the form `n/2`.
|
|
"""
|
|
function Base.parse(::Type{HalfInteger}, s::AbstractString)
|
|
if in('/', s)
|
|
num, den = split(s, '/'; limit=2)
|
|
parse(Int, den) == 2 ||
|
|
throw(ArgumentError("Denominator not 2 in HalfInteger string '$s'."))
|
|
HalfInteger(parse(Int, num), 2)
|
|
elseif !isempty(strip(s))
|
|
HalfInteger(parse(Int, s))
|
|
else
|
|
throw(ArgumentError("input string is empty or only contains whitespace"))
|
|
end
|
|
end
|
|
|
|
Base.show(io::IO, x::HalfInteger) =
|
|
print(io, iseven(x.numerator) ? "$(div(x.numerator, 2))" : "$(x.numerator)/2")
|
|
|
|
# Other methods
|
|
|
|
Base.isinteger(a::HalfInteger) = iseven(a.numerator)
|
|
ishalfinteger(a::HalfInteger) = true
|
|
ishalfinteger(a::Integer) = true
|
|
ishalfinteger(a::Rational) = a.den == 1 || a.den == 2
|
|
ishalfinteger(a::Real) = isinteger(2*a)
|
|
|
|
converthalfinteger(a::Number) = convert(HalfInteger, a)
|
|
|
|
Base.numerator(a::HalfInteger) = iseven(a.numerator) ? div(a.numerator, 2) : a.numerator
|
|
Base.denominator(a::HalfInteger) = iseven(a.numerator) ? 1 : 2
|
|
|
|
# Range of HalfIntegers
|
|
|
|
"""
|
|
struct HalfIntegerRange <: AbstractVector{HalfInteger}
|
|
|
|
A range of `HalfInteger` values from `start` to `stop`, spaced by `1`. The `a:b` syntax
|
|
where both `a` and `b` are `HalfInteger`s can also be use to construct this range.
|
|
"""
|
|
struct HalfIntegerRange <: AbstractVector{HalfInteger}
|
|
start :: HalfInteger
|
|
stop :: HalfInteger
|
|
|
|
function HalfIntegerRange(start::HalfInteger, stop::HalfInteger)
|
|
(start <= stop) ||
|
|
throw(ArgumentError("Second argument must be greater or equal to the first."))
|
|
return new(start, stop)
|
|
end
|
|
end
|
|
Base.iterate(it::HalfIntegerRange) = (it.start, it.start + 1)
|
|
Base.iterate(it::HalfIntegerRange, s) = (s <= it.stop) ? (s, s+1) : nothing
|
|
Base.length(it::HalfIntegerRange) = floor(Int, it.stop - it.start) + 1
|
|
Base.size(it::HalfIntegerRange) = (length(it),)
|
|
function Base.getindex(it::HalfIntegerRange, i::Integer)
|
|
1 <= i <= length(it) || throw(BoundsError(it, i))
|
|
it.start + i - 1
|
|
end
|
|
|
|
"""
|
|
(:)(i::HalfInteger, j::HalfInteger)
|
|
|
|
Constructs a `HalfIntegerRange` out of two `HalfInteger` values.
|
|
"""
|
|
Base.:(:)(i::HalfInteger, j::HalfInteger) = HalfIntegerRange(i, j)
|