Skip to content

Commit 9c70f59

Browse files
committed
add compensated_horner method
1 parent 7107f2b commit 9c70f59

File tree

3 files changed

+103
-2
lines changed

3 files changed

+103
-2
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "Polynomials"
22
uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45"
33
license = "MIT"
44
author = "JuliaMath"
5-
version = "1.1.6"
5+
version = "1.1.7"
66

77
[deps]
88
Intervals = "d8418881-c3e1-53bb-8760-2df7ec849ed5"

src/polynomials/standard-basis.jl

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,91 @@ function vander(P::Type{<:StandardBasisPolynomial}, x::AbstractVector{T}, n::Int
297297
end
298298
return A
299299
end
300+
301+
302+
## --------------------------------------------------
303+
"""
304+
compensated_horner(p::P, x)
305+
compensated_horner(ps, x)
306+
307+
Evaluate `p(x)` using a compensation scheme of S. Graillat, Ph. Langlois, N. Louve [Compensated Horner Scheme](https://cadxfem.org/cao/Compensation-horner.pdf). Either a `Polynomial` `p` or it coefficients may be passed in.
308+
309+
The Horner scheme has relative error given by
310+
311+
`|(p(x) - p̂(x))/p(x)| ≤ α ⋅ u ⋅ cond(p, x)`, where `u` is the precision (`2⁻⁵³`).
312+
313+
The compensated Horner scheme has relative error bounded by
314+
315+
`|(p(x) - p̂(x))/p(x)| ≤ u + β(n) ⋅ u² ⋅ cond(p, x)`.
316+
317+
As noted, this reflects the accuracy of a backward stable computation performed in doubled working precision `u²`. (E.g., polynomial evaluation of a `Polynomial{Float64}` object through `compensated_horner` is as accurate as evaluation of a `Polynomial{Double64}` object (using the `DoubleFloat` package), but significantly faster.
318+
319+
Pointed out in https://discourse.julialang.org/t/more-accurate-evalpoly/45932/5.
320+
"""
321+
function compensated_horner(p::P, x) where {T, P <: Polynomials.StandardBasisPolynomial{T}}
322+
compensated_horner(coeffs(p), x)
323+
end
324+
325+
# rexpressed from paper to compute horner_sum in same pass
326+
# sᵢ -- horner sum
327+
# c -- compensating term
328+
@inline function compensated_horner(ps, x)
329+
n, T = length(ps), eltype(ps)
330+
aᵢ = ps[end]
331+
sᵢ = aᵢ * _one(x)
332+
c = zero(T) * _one(x)
333+
for i in n-1:-1:1
334+
aᵢ = ps[i]
335+
pᵢ, πᵢ = two_product_fma(sᵢ, x)
336+
sᵢ, σᵢ = two_sum(pᵢ, aᵢ)
337+
c = fma(c, x, πᵢ + σᵢ)
338+
end
339+
sᵢ + c
340+
end
341+
342+
function compensated_horner(ps::Tuple, x::S) where {S}
343+
ps == () && return zero(S)
344+
if @generated
345+
n = length(ps.parameters)
346+
sσᵢ =:(ps[end] * _one(x), zero(S))
347+
c = :(zero(S) * _one(x))
348+
for i in n-1:-1:1
349+
pπᵢ = :(two_product_fma($sσᵢ[1], x))
350+
sσᵢ = :(two_sum($pπᵢ[1], ps[$i]))
351+
Σ = :($pπᵢ[2] + $sσᵢ[2])
352+
c = :(fma($c, x, $Σ))
353+
end
354+
s = :($sσᵢ[1] + $c)
355+
s
356+
else
357+
compensated_horner(ps, x)
358+
end
359+
end
360+
361+
# error-free-transformations (EFT) of a∘b = x + y where x, y floating point, ∘ mathematical
362+
# operator (not ⊕ or ⊗)
363+
@inline function two_sum(a, b)
364+
x = a + b
365+
z = x - a
366+
y = (a - (x-z)) + (b-z)
367+
x, y
368+
end
369+
370+
# return x, y, floats, with x + y = a * b
371+
@inline function two_product_fma(a, b)
372+
x = a * b
373+
y = fma(a, b, -x)
374+
x, y
375+
end
376+
377+
378+
# Condition number of a standard basis polynomial
379+
# rule of thumb: p̂ a compute value
380+
# |p(x) - p̃(x)|/|p(x)| ≤ α(n)⋅u ⋅ cond(p,x), where u = finite precision of compuation (2^-p)
381+
function LinearAlgebra.cond(p::P, x) where {P <: Polynomials.StandardBasisPolynomial}
382+
= Polynomials.:(P)(abs.(coeffs(p)), p.var)
383+
(abs(x))/ abs(p(x))
384+
end
385+
386+
387+

test/StandardBasis.jl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,10 @@ end
342342
# issue #209
343343
ps = [P([0,1]), P([0,0,1])]
344344
@test Polynomials.evalpoly.(1/2, ps) [p(1/2) for p in ps]
345+
345346
end
346347

348+
347349
# constant polynomials and type
348350
Ts = (Int, Float32, Float64, Complex{Int}, Complex{Float64})
349351
for P in (Polynomial, ImmutablePolynomial, SparsePolynomial)
@@ -374,7 +376,18 @@ end
374376
@test eltype(p3) == eltype(p2)
375377
end
376378

377-
379+
# compensated_horner
380+
# polynomial evaluation for polynomials with large condition numbers
381+
for P in (Polynomial, ImmutablePolynomial, SparsePolynomial)
382+
x = variable(P{Float64})
383+
f(x) = (x - 1)^20
384+
p = f(x)
385+
e₁ = abs( (f(4/3) - p(4/3))/ p(4/3) )
386+
e₂ = abs( (f(4/3) - Polynomials.compensated_horner(p, 4/3))/ p(4/3) )
387+
@test cond(p, 4/3) > 1/eps()
388+
@test e₁ > sqrt(eps())
389+
@test e₂ <= 4eps()
390+
end
378391
end
379392

380393
@testset "Conversion" begin

0 commit comments

Comments
 (0)