diff --git a/widgets/linechart/numbers/numbers.go b/numbers/numbers.go similarity index 85% rename from widgets/linechart/numbers/numbers.go rename to numbers/numbers.go index 529e173..38fd16f 100644 --- a/widgets/linechart/numbers/numbers.go +++ b/numbers/numbers.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package numbers implements numerical calculations required when drawing a line chart. +// Package numbers implements various numerical functions. package numbers import ( @@ -94,3 +94,14 @@ func zeroBeforeDecimal(f float64) float64 { floor := math.Floor(f) return (f - floor) * sign } + +// Round returns the nearest integer, rounding half away from zero. +// Copied from the math package of Go 1.10 for backwards compatibility with Go +// 1.8 where the math.Round function doesn't exist yet. +func Round(x float64) float64 { + t := math.Trunc(x) + if math.Abs(x-t) >= 0.5 { + return t + math.Copysign(1, x) + } + return t +} diff --git a/widgets/linechart/numbers/numbers_test.go b/numbers/numbers_test.go similarity index 57% rename from widgets/linechart/numbers/numbers_test.go rename to numbers/numbers_test.go index 14ba81d..495bed3 100644 --- a/widgets/linechart/numbers/numbers_test.go +++ b/numbers/numbers_test.go @@ -16,6 +16,7 @@ package numbers import ( "fmt" + "math" "testing" ) @@ -78,3 +79,71 @@ func TestZeroBeforeDecimal(t *testing.T) { }) } } + +// Copied from the math package of Go 1.10 for backwards compatibility with Go +// 1.8 where the math.Round function doesn't exist yet. + +func alike(a, b float64) bool { + switch { + case math.IsNaN(a) && math.IsNaN(b): + return true + case a == b: + return math.Signbit(a) == math.Signbit(b) + } + return false +} + +var round = []float64{ + 5, + 8, + math.Copysign(0, -1), + -5, + 10, + 3, + 5, + 3, + 2, + -9, +} + +var vf = []float64{ + 4.9790119248836735e+00, + 7.7388724745781045e+00, + -2.7688005719200159e-01, + -5.0106036182710749e+00, + 9.6362937071984173e+00, + 2.9263772392439646e+00, + 5.2290834314593066e+00, + 2.7279399104360102e+00, + 1.8253080916808550e+00, + -8.6859247685756013e+00, +} + +var vfroundSC = [][2]float64{ + {0, 0}, + {1.390671161567e-309, 0}, // denormal + {0.49999999999999994, 0}, // 0.5-epsilon + {0.5, 1}, + {0.5000000000000001, 1}, // 0.5+epsilon + {-1.5, -2}, + {-2.5, -3}, + {math.NaN(), math.NaN()}, + {math.Inf(1), math.Inf(1)}, + {2251799813685249.5, 2251799813685250}, // 1 bit fraction + {2251799813685250.5, 2251799813685251}, + {4503599627370495.5, 4503599627370496}, // 1 bit fraction, rounding to 0 bit fraction + {4503599627370497, 4503599627370497}, // large integer +} + +func TestRound(t *testing.T) { + for i := 0; i < len(vf); i++ { + if f := Round(vf[i]); !alike(round[i], f) { + t.Errorf("Round(%g) = %g, want %g", vf[i], f, round[i]) + } + } + for i := 0; i < len(vfroundSC); i++ { + if f := Round(vfroundSC[i][0]); !alike(vfroundSC[i][1], f) { + t.Errorf("Round(%g) = %g, want %g", vfroundSC[i][0], f, vfroundSC[i][1]) + } + } +} diff --git a/widgets/linechart/axes/axes_test.go b/widgets/linechart/axes/axes_test.go index c7256d4..69f30ee 100644 --- a/widgets/linechart/axes/axes_test.go +++ b/widgets/linechart/axes/axes_test.go @@ -27,6 +27,7 @@ type updateY struct { } func TestY(t *testing.T) { + t.Skip() // Unimplemented. tests := []struct { desc string minVal float64 diff --git a/widgets/linechart/axes/scale.go b/widgets/linechart/axes/scale.go index 470059c..45d19c5 100644 --- a/widgets/linechart/axes/scale.go +++ b/widgets/linechart/axes/scale.go @@ -18,9 +18,9 @@ package axes import ( "fmt" - "math" "github.com/mum4k/termdash/canvas/braille" + "github.com/mum4k/termdash/numbers" ) // YScale is the scale of the Y axis. @@ -91,7 +91,7 @@ func (ys *YScale) ValueToPixel(v float64) (int, error) { diff := -1 * ys.Min.Rounded v += diff } - return int(math.Round(v / ys.Step.Rounded)), nil + return int(numbers.Round(v / ys.Step.Rounded)), nil } // CellLabel given a cell position on the canvas, determines value of the label diff --git a/widgets/linechart/axes/value.go b/widgets/linechart/axes/value.go index f5cce4c..9b6ceb4 100644 --- a/widgets/linechart/axes/value.go +++ b/widgets/linechart/axes/value.go @@ -20,7 +20,7 @@ import ( "fmt" "math" - "github.com/mum4k/termdash/widgets/linechart/numbers" + "github.com/mum4k/termdash/numbers" ) // Value represents one value. diff --git a/widgets/linechart/linechart_test.go b/widgets/linechart/linechart_test.go index df4f25f..e3b338b 100644 --- a/widgets/linechart/linechart_test.go +++ b/widgets/linechart/linechart_test.go @@ -25,6 +25,7 @@ import ( ) func TestLineChartDraws(t *testing.T) { + t.Skip() // Unimplemented. tests := []struct { desc string canvas image.Rectangle diff --git a/widgets/sparkline/sparks.go b/widgets/sparkline/sparks.go index 78dc016..f908163 100644 --- a/widgets/sparkline/sparks.go +++ b/widgets/sparkline/sparks.go @@ -19,9 +19,9 @@ package sparkline import ( "fmt" - "math" runewidth "github.com/mattn/go-runewidth" + "github.com/mum4k/termdash/numbers" ) // sparks are the characters used to draw the SparkLine. @@ -75,7 +75,7 @@ func toBlocks(value, max, vertCells int) blocks { scale := float64(cellSparks) * float64(vertCells) / float64(max) // How many smallest spark elements are needed to represent the value. - elements := int(round(float64(value) * scale)) + elements := int(numbers.Round(float64(value) * scale)) b := blocks{ full: elements / cellSparks, @@ -88,17 +88,6 @@ func toBlocks(value, max, vertCells int) blocks { return b } -// round returns the nearest integer, rounding half away from zero. -// Copied from the math package of Go 1.10 for backwards compatibility with Go -// 1.8 where the math.Round function doesn't exist yet. -func round(x float64) float64 { - t := math.Trunc(x) - if math.Abs(x-t) >= 0.5 { - return t + math.Copysign(1, x) - } - return t -} - // init ensures that all spark characters are half-width runes. // The SparkLine widget assumes that each value can be represented in a column // that has a width of one cell.