|
| 1 | +""" |
| 2 | + ExactReal{T<:Real} <: Real |
| 3 | +
|
| 4 | +Real numbers with the assurance that they precisely correspond to the number |
| 5 | +described by their binary form. The purpose is to guarantee that a non interval |
| 6 | +number is exact, so that `ExactReal` can be used with `Interval` without |
| 7 | +producing the "NG" flag. |
| 8 | +
|
| 9 | +!!! danger |
| 10 | + By using `ExactReal`, users acknowledge the responsibility of ensuring that |
| 11 | + the number they input corresponds to their intended value. |
| 12 | + For example, `ExactReal(0.1)` implies that the user knows that ``0.1`` can |
| 13 | + not be represented exactly as a binary number, and that they are using a |
| 14 | + slightly different number than ``0.1``. |
| 15 | + To help identify the binary number, `ExactReal` is displayed without any |
| 16 | + rounding. |
| 17 | +
|
| 18 | + ```julia |
| 19 | + julia> ExactReal(0.1) |
| 20 | + ExactReal{Float64}(0.1000000000000000055511151231257827021181583404541015625) |
| 21 | + ``` |
| 22 | +
|
| 23 | + In case of doubt, [`has_exact_display`](@ref) can be use to check if the |
| 24 | + string representation of a `Real` is equal to its binary value. |
| 25 | +
|
| 26 | +# Examples |
| 27 | +
|
| 28 | +```jldoctest |
| 29 | +julia> setdisplay(:full); |
| 30 | +
|
| 31 | +julia> 0.5 * interval(1) |
| 32 | +Interval{Float64}(0.5, 0.5, com, NG) |
| 33 | +
|
| 34 | +julia> ExactReal(0.5) * interval(1) |
| 35 | +Interval{Float64}(0.5, 0.5, com) |
| 36 | +
|
| 37 | +julia> [1, interval(2)] |
| 38 | +2-element Vector{Interval{Float64}}: |
| 39 | + Interval{Float64}(1.0, 1.0, com, NG) |
| 40 | + Interval{Float64}(2.0, 2.0, com) |
| 41 | +
|
| 42 | +julia> [ExactReal(1), interval(2)] |
| 43 | +2-element Vector{Interval{Float64}}: |
| 44 | + Interval{Float64}(1.0, 1.0, com) |
| 45 | + Interval{Float64}(2.0, 2.0, com) |
| 46 | +``` |
| 47 | +
|
| 48 | +See also: [`@exact`](@ref). |
| 49 | +""" |
| 50 | +struct ExactReal{T<:Real} <: Real |
| 51 | + value :: T |
| 52 | + |
| 53 | + ExactReal(value::T) where {T<:Real} = new{T}(value) |
| 54 | +end |
| 55 | + |
| 56 | +_value(x::ExactReal) = x.value # hook for interval constructor |
| 57 | + |
| 58 | +# conversion and promotion |
| 59 | + |
| 60 | +Base.convert(::Type{ExactReal{T}}, x::ExactReal{T}) where {T<:Real} = x |
| 61 | + |
| 62 | +Base.promote_rule(::Type{ExactReal{T}}, ::Type{ExactReal{S}}) where {T<:Real,S<:Real} = |
| 63 | + throw(ArgumentError("promotion between `ExactReal` is not allowed")) |
| 64 | + |
| 65 | +# to Interval |
| 66 | + |
| 67 | +Base.convert(::Type{Interval{T}}, x::ExactReal) where {T<:NumTypes} = |
| 68 | + interval(T, x.value) |
| 69 | + |
| 70 | +Base.promote_rule(::Type{Interval{T}}, ::Type{ExactReal{S}}) where {T<:NumTypes,S<:Real} = |
| 71 | + Interval{promote_numtype(T, S)} |
| 72 | +Base.promote_rule(::Type{ExactReal{T}}, ::Type{Interval{S}}) where {T<:Real,S<:NumTypes} = |
| 73 | + Interval{promote_numtype(T, S)} |
| 74 | + |
| 75 | +# to Real |
| 76 | + |
| 77 | +Base.convert(::Type{T}, x::ExactReal) where {T<:Real} = |
| 78 | + convert(T, x.value) |
| 79 | + |
| 80 | +Base.promote_rule(::Type{T}, ::Type{ExactReal{S}}) where {T<:Real,S<:Real} = |
| 81 | + promote_type(T, S) |
| 82 | +Base.promote_rule(::Type{ExactReal{T}}, ::Type{S}) where {T<:Real,S<:Real} = |
| 83 | + promote_type(T, S) |
| 84 | +# needed to resolve method ambiguities |
| 85 | +Base.promote_rule(::Type{ExactReal{T}}, ::Type{Bool}) where {T<:Real} = |
| 86 | + promote_type(T, Bool) |
| 87 | +Base.promote_rule(::Type{Bool}, ::Type{ExactReal{T}}) where {T<:Real} = |
| 88 | + promote_type(Bool, T) |
| 89 | + |
| 90 | +# display |
| 91 | + |
| 92 | +Base.string(x::ExactReal{T}) where {T<:AbstractFloat} = |
| 93 | + Base.Ryu.writefixed(x.value, 2000, false, false, false, UInt8('.'), true) |
| 94 | + |
| 95 | +Base.string(x::ExactReal) = string(x.value) |
| 96 | + |
| 97 | +Base.show(io::IO, ::MIME"text/plain", x::ExactReal{T}) where {T<:AbstractFloat} = |
| 98 | + print(io, "ExactReal{$T}($(string(x)))") |
| 99 | + |
| 100 | +# always exact |
| 101 | + |
| 102 | +Base.:-(x::ExactReal) = ExactReal(-x.value) |
| 103 | + |
| 104 | +function Base.:+(x::ExactReal, y::Complex{<:ExactReal}) |
| 105 | + iszero(real(y).value) && return complex(x, imag(y)) |
| 106 | + return complex(x + real(y), imag(y)) |
| 107 | +end |
| 108 | + |
| 109 | +""" |
| 110 | + has_exact_display(x::Real) |
| 111 | +
|
| 112 | +Determine if the display of `x` up to 2000 decimals is equal to the bitwise |
| 113 | +value of `x`. This is famously not true for the float displayed as `0.1`. |
| 114 | +""" |
| 115 | +has_exact_display(x::Real) = string(x) == string(ExactReal(x)) |
| 116 | + |
| 117 | +# |
| 118 | + |
| 119 | +struct ExactIm end |
| 120 | + |
| 121 | +Base.:*(x::ExactReal, ::ExactIm) = complex(ExactReal(zero(x.value)), x) |
| 122 | +Base.:*(::ExactIm, x::ExactReal) = complex(ExactReal(zero(x.value)), x) |
| 123 | + |
| 124 | +Base.:*(x::Real, ::ExactIm) = complex(zero(x), x) |
| 125 | +Base.:*(::ExactIm, x::Real) = complex(zero(x), x) |
| 126 | + |
| 127 | +Base.:*(x::Complex, ::ExactIm) = complex(-imag(x), real(x)) |
| 128 | +Base.:*(::ExactIm, x::Complex) = complex(-imag(x), real(x)) |
| 129 | + |
| 130 | +# |
| 131 | + |
| 132 | +""" |
| 133 | + @exact |
| 134 | +
|
| 135 | +Wrap every literal numbers of the expression in an [`ExactReal`](@ref). This |
| 136 | +macro allows defining generic functions, seamlessly accepting both `Number` and |
| 137 | +[`Interval`](@ref) arguments, without producing the "NG" flag. |
| 138 | +
|
| 139 | +!!! danger |
| 140 | + By using [`ExactReal`](@ref), users acknowledge the responsibility of |
| 141 | + ensuring that the number they input corresponds to their intended value. |
| 142 | + For example, `ExactReal(0.1)` implies that the user knows that ``0.1`` can |
| 143 | + not be represented exactly as a binary number, and that they are using a |
| 144 | + slightly different number than ``0.1``. |
| 145 | + To help identify the binary number, `ExactReal` is displayed without any |
| 146 | + rounding. |
| 147 | +
|
| 148 | + ```julia |
| 149 | + julia> ExactReal(0.1) |
| 150 | + ExactReal{Float64}(0.1000000000000000055511151231257827021181583404541015625) |
| 151 | + ``` |
| 152 | +
|
| 153 | + In case of doubt, [`has_exact_display`](@ref) can be use to check if the |
| 154 | + string representation of a `Real` is equal to its binary value. |
| 155 | +
|
| 156 | +# Examples |
| 157 | +
|
| 158 | +```jldoctest |
| 159 | +julia> setdisplay(:full); |
| 160 | +
|
| 161 | +julia> f(x) = 1.2*x + 0.1 |
| 162 | +f (generic function with 1 method) |
| 163 | +
|
| 164 | +julia> f(interval(1, 2)) |
| 165 | +Interval{Float64}(1.2999999999999998, 2.5, com, NG) |
| 166 | +
|
| 167 | +julia> @exact g(x) = 1.2*x + 0.1 |
| 168 | +g (generic function with 1 method) |
| 169 | +
|
| 170 | +julia> g(interval(1, 2)) |
| 171 | +Interval{Float64}(1.2999999999999998, 2.5, com) |
| 172 | +
|
| 173 | +julia> g(1.4) |
| 174 | +1.78 |
| 175 | +``` |
| 176 | +
|
| 177 | +See also: [`ExactReal`](@ref). |
| 178 | +""" |
| 179 | +macro exact(expr) |
| 180 | + exact_expr = postwalk(expr) do x |
| 181 | + x isa Real && return ExactReal(x) |
| 182 | + return x |
| 183 | + end |
| 184 | + |
| 185 | + exact_expr = prewalk(exact_expr) do x |
| 186 | + if @capture(x, b_ * im) || @capture(x, im * b_) |
| 187 | + if b isa ExactReal |
| 188 | + return :(complex(ExactReal(zero($b.value)), $b)) |
| 189 | + end |
| 190 | + end |
| 191 | + |
| 192 | + if @capture(x, a_ + b_ * im) || @capture(x, a_ + im * b_) || @capture(x, b_ * im + a_) || @capture(x, im * b_ + a_) |
| 193 | + if a isa ExactReal && b isa ExactReal |
| 194 | + return :(complex($a, $b)) |
| 195 | + end |
| 196 | + end |
| 197 | + |
| 198 | + return x |
| 199 | + end |
| 200 | + |
| 201 | + return esc(exact_expr) |
| 202 | +end |
0 commit comments