Usage
OptiFloat.jl defines a macro @optifloat
which kicks of the floating point expression optimization and defines a new, potentially more accurrate function:
julia
using OptiFloat
# original
f(b, c) = (-b - sqrt(b^2 - 4c)) / (2c)
# improved
g = @optifloat (-b - sqrt(b^2 - 4c)) / (2c) batchsize=1000 T=Float16
#1 (generic function with 1 method)
The original function f
is inaccurate for large, negative b
:
julia
julia> f(Float16(-200), Float16(-0.1)) # low-precision, inaccurate
Float16(-0.0)
julia> f(-200.0, -0.1) # high-precision, accurate
0.004999987500013958
The improved function g
gives almost the same result as f
:
julia
julia> g(Float16(-200), Float16(-0.1)) # low-precision, accurate
Float16(0.004997)
If we are interested in the underlying expression of g
, we can use the function optifloat
(as opposed to the macro @optifloat
) which returns an OptiFloatResult
that is pretty printed as a informative report:
julia
julia> expr = :((-b - sqrt(b^2 - 4c)) / (2c));
julia> result = optifloat(expr; batchsize=1000, T=Float16)
──────────────────────────────────────────────────────────────────────────────
OptiFloat Result
──────────────────────────────────────────────────────────────────────────────
Original Expression:
╭──────────────┬─────────┬──────────────────────────────────╮
│ Interval │ Error │ Expression │
├──────────────┼─────────┼──────────────────────────────────┤
│ b: (-∞, ∞) │ 1.098 │ (-b - sqrt(b ^ 2 - 4c)) / (2c) │
╰──────────────┴─────────┴──────────────────────────────────╯
Optimized PiecewiseRegime:
╭───────────────────┬───────────┬──────────────────────────────────────────╮
│ Intervals │ Error │ Expression │
├───────────────────┼───────────┼──────────────────────────────────────────┤
│ b: (-∞, -1.592) │ 0.01743 │ ((4c) / (sqrt(b ^ 2 - 4c) - b)) / (2c) │
├───────────────────┼───────────┼──────────────────────────────────────────┤
│ b: (-1.592, ∞) │ 0.00924 │ (-b - sqrt(b ^ 2 - 4c)) / (2c) │
├───────────────────┼───────────┼──────────────────────────────────────────┤
│ Combined │ 0.013 │ % │
╰───────────────────┴───────────┴──────────────────────────────────────────╯
Improved function:
──────────────────────────────────────────────────────────────────────────────
function f(b, c)
begin
if -Inf16 < b <= Float16(-1.592)
return ((4c) / (sqrt(b ^ 2 - 4c) - b)) / (2c)
end
if Float16(-1.592) < b <= Inf16
return (-b - sqrt(b ^ 2 - 4c)) / (2c)
end
end
end
──────────────────────────────────────────────────────────────────────────────
The result
holds e.g. the expression of the final, improved function:
julia
julia> result.improved
:((b, c)->begin
if -Inf16 < b <= Float16(-1.592)
return ((4c) / (sqrt(b ^ 2 - 4c) - b)) / (2c)
end
if Float16(-1.592) < b <= Inf16
return (-b - sqrt(b ^ 2 - 4c)) / (2c)
end
end)
Verbose output / debug logs
For more verbose outputs of OptiFloats optimization process you can enable debug logging for the package:
julia
# enable debug logging for OptiFloat only
ENV["JULIA_DEBUG"] = OptiFloat
# calls to optifloat/@optifloat will print debug information
optifloat(expr; batchsize=1000, T=Float16)