ApproxLogFunction
A lookup table based method to calculate $y = {\rm log}_b(x)$ with a controllable error, where $b > 0$, $b ≠ 1$, $x > 0$, and $x$ shall be the IEEE754 floating-point normal numbers.
💽 Installation
julia> import Pkg;
julia> Pkg.add("ApproxLogFunction");
👨🏫 Usage
🔌 Exposed Functor
The exposed constructor :
Approxlog(base::Real; abserror::Real, dtype::Type{<:AbstractFloat})
where base
is the radix of logarithm operation, abserror
is the maximum absolute error of the output and dtype
is the data type of input and output value, for example :
alog₂ = Approxlog(2, abserror=0.1, dtype=Float32);
input = 5.20f2;
output = alog₂(input);
📈 Error Visualization
using Plots, ApproxLogFunction
figs = []
base = 2
t = -2f-0 : 1f-4 : 5f0;
x = 2 .^ t;
y₁ = log.(base, x)
for Δout in [0.6, 0.3]
alog = Approxlog(base, abserror=Δout, dtype=Float32)
y₂ = alog.(x)
fg = plot(x, y₁, linewidth=1.1, xscale=:log10);
plot!(x, y₂, linewidth=1.1, titlefontsize=10)
title!("abserror=$Δout")
push!(figs, fg)
end
plot(figs..., layout=(1,2), leg=nothing, size=(999,666))
🥇 Benchmark
Some unimportant information was omitted when benchmarking.
💃🏻 Scalar Input
julia> using ApproxLogFunction, BenchmarkTools;
julia> begin
type = Float32;
base = type(2.0);
Δout = 0.1;
alog = Approxlog(base, abserror=Δout, dtype=type);
end;
julia> x = 1.23456f0;
julia> @benchmark y₁ = log($base, $x)
Time (median): 21.643 ns
julia> @benchmark y₁ = log2($x)
Time (median): 14.800 ns
julia> @benchmark y₂ = alog($x)
Time (median): 74.129 ns
👨👨👧👧 Array Input
julia> using ApproxLogFunction, BenchmarkTools;
julia> begin
type = Float32;
base = type(2.0);
Δout = 0.1;
alog = Approxlog(base, abserror=Δout, dtype=type);
end;
julia> x = rand(type, 1024, 1024);
julia> @benchmark y₁ = log.($base, $x)
Time (median): 21.422 ms
julia> @benchmark y₁ = log2.($x)
Time (median): 11.626 ms
julia> @benchmark y₂ = alog.($x)
Time (median): 5.207 ms
Calculating scaler input is slower, but why calculating array input is faster ? 😂😂😂
⚠️ Attention
About The Input Range
Error is well controlled when using IEEE754 floating-point positive normal numbers, which is represented as :
$$ x = 2^{E-B} \ (1 + F × 2^{-|F|}) $$
where $0 < E < (2^{|E|} - 1)$ is the value of exponent part, $|E|$ is the bit width of $E$, $F$ is the value of fraction part, $|F|$ is the bit width of $F$, and $B=(2 ^ {|E| - 1} - 1)$ is the bias for $E$. So the range for an Approxlog
object's input is $[X_{\rm min}, X_{\rm max}]$, where
$$ \begin{aligned} X_{\rm min} &= 2^{1-B} \ (1 + 0 × 2^{-|F|}) &&= 2^{1-B} \ X_{\rm max} &= 2^{(2^{|E|}-2)-B} \ \big(1 + (2^{|F|}-1) × 2^{-|F|}\big) &&= 2^{B} \ \big(1 + (2^{|F|}-1) × 2^{-|F|}\big) \end{aligned} $$
In short, for Float16
input, its range is :
$$ 2^{1-15} \le X_{\rm Float16} \le 2^{15} \ \left( 1 + (2^{10} -1) × 2^{-10} \right) $$
for Float32
input :
$$ 2^{1-127} \le X_{\rm Float32} \le 2^{127} \ \left(1 + (2^{23} -1) × 2^{-23}\right) $$
and for Float64
input :
$$ 2^{1-1023} \le X_{\rm Float64} \le 2^{1023} \ \left(1 + (2^{52} -1) × 2^{-52}\right) $$
As to positive subnormal numbers, the result is not reliable.
About The Base Range
We have mentioned $b ≠ 1$, but base value closing to $1$ is not recommended, e.g. $1.0001$, $0.9999$.
📁 C-Lang Files
Interface function :
toclang(dir::String, filename::String, funcname::String, f::Approxlog)
dir
is the directory to save the C language files (dir
would be made if it doesn't exist before), filename
is the name of the generated .c
and .h
files, funcname
is the name of the generated approximate function and f
is the approximate log functor, for example :
alog₂ = Approxlog(2, abserror=0.12, dtype=Float32)
toclang("./cfolder/", "approxlog", "alog", alog₂)
this would generate approxlog.c
and approxlog.h
files in ./cfolder/
, the function is named as alog
.