Skip to content

Commit 82c684a

Browse files
authored
palette/moreland: fix floating-point arithmetic error in Palette
* palette/moreland: fix floating-point arithmetic error in Palette Fixes #798. Signed-off-by: Eng Zer Jun <[email protected]> * Rename tests Reference: #799 (review) Signed-off-by: Eng Zer Jun <[email protected]> * Apply code suggestions Reference: #799 (review) Signed-off-by: Eng Zer Jun <[email protected]> --------- Signed-off-by: Eng Zer Jun <[email protected]>
1 parent 8cb2ca2 commit 82c684a

File tree

4 files changed

+97
-2
lines changed

4 files changed

+97
-2
lines changed

palette/moreland/luminance.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,13 @@ func (l luminance) Palette(n int) palette.Palette {
179179
var v float64
180180
c := make([]color.Color, n)
181181
for i := range n {
182-
v = l.min + float64(delta*float64(i))
182+
if i == n-1 {
183+
// Avoid potential overflow on last element
184+
// due to floating point error.
185+
v = l.max
186+
} else {
187+
v = l.min + float64(delta*float64(i))
188+
}
183189
var err error
184190
c[i], err = l.At(v)
185191
if err != nil {

palette/moreland/luminance_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,47 @@ func BenchmarkLuminance_At(b *testing.B) {
161161
})
162162
}
163163
}
164+
165+
// See https://github.com/gonum/plot/issues/798
166+
func TestIssue798Kindlmann(t *testing.T) {
167+
for _, test := range []struct {
168+
n int
169+
min, max float64
170+
}{
171+
0: {n: 2, min: 0, max: 1},
172+
1: {n: 15, min: 0.3402859786606234, max: 15.322841335211892},
173+
} {
174+
t.Run("", func(t *testing.T) {
175+
defer func() {
176+
r := recover()
177+
if r != nil {
178+
t.Errorf("unexpected panic with n=%d min=%f max=%f: %v", test.n, test.min, test.max, r)
179+
}
180+
}()
181+
colors := Kindlmann()
182+
colors.SetMin(test.min)
183+
colors.SetMax(test.max)
184+
col := colors.Palette(test.n).Colors()
185+
min, err := colors.At(test.min)
186+
if err != nil {
187+
t.Fatalf("unexpected error calling colors.At(min): %v", err)
188+
}
189+
if !sameColor(min, col[0]) {
190+
t.Errorf("unexpected min color %#v != %#v", min, col[0])
191+
}
192+
max, err := colors.At(test.max)
193+
if err != nil {
194+
t.Fatalf("unexpected error calling colors.At(max): %v", err)
195+
}
196+
if !sameColor(max, col[len(col)-1]) {
197+
t.Errorf("unexpected max color %#v != %#v", max, col[len(col)-1])
198+
}
199+
})
200+
}
201+
}
202+
203+
func sameColor(a, b color.Color) bool {
204+
ar, ag, ab, aa := a.RGBA()
205+
br, bg, bb, ba := b.RGBA()
206+
return ar == br && ag == bg && ab == bb && aa == ba
207+
}

palette/moreland/smooth.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,16 @@ func (p smoothDiverging) Palette(n int) palette.Palette {
171171
p.SetMax(1)
172172
}
173173
delta := (p.max - p.min) / float64(n-1)
174+
var v float64
174175
c := make([]color.Color, n)
175176
for i := range c {
176-
v := p.min + float64(delta*float64(i))
177+
if i == n-1 {
178+
// Avoid potential overflow on last element
179+
// due to floating point error.
180+
v = p.max
181+
} else {
182+
v = p.min + float64(delta*float64(i))
183+
}
177184
var err error
178185
c[i], err = p.At(v)
179186
if err != nil {

palette/moreland/smooth_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,41 @@ func similar(a, b color.Color, tolerance float64) bool {
246246
}
247247
return true
248248
}
249+
250+
// See https://github.com/gonum/plot/issues/798
251+
func TestIssue798SmoothBlueRed(t *testing.T) {
252+
for _, test := range []struct {
253+
n int
254+
min, max float64
255+
}{
256+
0: {n: 2, min: 0, max: 1},
257+
1: {n: 15, min: 0.3402859786606234, max: 15.322841335211892},
258+
} {
259+
t.Run("", func(t *testing.T) {
260+
defer func() {
261+
r := recover()
262+
if r != nil {
263+
t.Errorf("unexpected panic with n=%d min=%f max=%f: %v", test.n, test.min, test.max, r)
264+
}
265+
}()
266+
colors := SmoothBlueRed()
267+
colors.SetMin(test.min)
268+
colors.SetMax(test.max)
269+
col := colors.Palette(test.n).Colors()
270+
min, err := colors.At(test.min)
271+
if err != nil {
272+
t.Fatalf("unexpected error calling colors.At(min): %v", err)
273+
}
274+
if !sameColor(min, col[0]) {
275+
t.Errorf("unexpected min color %#v != %#v", min, col[0])
276+
}
277+
max, err := colors.At(test.max)
278+
if err != nil {
279+
t.Fatalf("unexpected error calling colors.At(max): %v", err)
280+
}
281+
if !sameColor(max, col[len(col)-1]) {
282+
t.Errorf("unexpected max color %#v != %#v", max, col[len(col)-1])
283+
}
284+
})
285+
}
286+
}

0 commit comments

Comments
 (0)