Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions crhumanize/float.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
package crhumanize

import (
"math"
"strconv"
"strings"
)

// Float formats the given float with the specified number of decimal digits.
// Trailing 0 decimals are stripped.
func Float(value float64, decimalDigits int) SafeString {
s := strconv.FormatFloat(value, 'f', decimalDigits, 64)
s = stripTrailingZeroDecimals(s)
Expand All @@ -37,3 +40,29 @@ func stripTrailingZeroDecimals(s string) string {
}
return s
}

// Percent formats (numerator/denominator) as a percentage. At most one decimal
// digit is used (only when the integer part is a single digit). If denominator
// is 0, returns the empty string.
//
// Values very close to 0 are formatted as ~0% to indicate that the value is
// non-zero.
//
// Examples: "0.2%", "12%".
func Percent[T Numeric](numerator, denominator T) SafeString {
if denominator == 0 {
return ""
}
if numerator == 0 {
return "0%"
}
value := (float64(numerator) / float64(denominator)) * 100
if math.Abs(value) < 0.05 {
return "~0%"
}
decimalDigits := 0
if math.Abs(value) < 9.95 {
decimalDigits = 1
}
return Float(value, decimalDigits) + "%"
}
45 changes: 45 additions & 0 deletions crhumanize/float_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,17 @@ func TestFloat(t *testing.T) {
{0.01, 1, "0"},
{0.01, 2, "0.01"},
{0.01, 4, "0.01"},
{-1.23456789, 2, "-1.23"},
{1.23456789, 2, "1.23"},
{1.23456789, 3, "1.235"},
{1.23456789, 3, "1.235"},
{123456.7777, 1, "123456.8"},
{123456.7777, 2, "123456.78"},
{123456.1010, 4, "123456.101"},
{123456.1010, 2, "123456.1"},
{-123456.1010, 1, "-123456.1"},
{123456.1010, 1, "123456.1"},
{-123456.1010, 0, "-123456"},
{123456.1010, 0, "123456"},
}

Expand All @@ -52,6 +55,34 @@ func TestFloat(t *testing.T) {
}
}

func TestPercent(t *testing.T) {
tests := []struct {
a, b float64
expected string
}{
{a: 0, b: 0, expected: ""},
{a: 0, b: 100, expected: "0%"},
{a: 0.0001, b: 100.0, expected: "~0%"},
{a: 0.044, b: 100, expected: "~0%"},
{a: 0.05, b: 100, expected: "0.1%"},
{a: 0.1234, b: 100.0, expected: "0.1%"},
{a: -0.1234, b: 100.0, expected: "-0.1%"},
{a: 0.05, b: 100.0, expected: "0.1%"},
{a: 9.95, b: 100.0, expected: "10%"},
{a: 9.94, b: 100.0, expected: "9.9%"},
{a: -9.95, b: 100.0, expected: "-10%"},
{a: -9.94, b: 100.0, expected: "-9.9%"},
{a: 10.52345, b: 100.0, expected: "11%"},
}

for _, test := range tests {
result := string(Percent(test.a, test.b))
if result != test.expected {
t.Errorf("Percent(%f,%f) = %s; expected %s", test.a, test.b, result, test.expected)
}
}
}

func ExampleFloat() {
fmt.Println(Float(100.1234, 3))
fmt.Println(Float(100.12, 3))
Expand All @@ -63,3 +94,17 @@ func ExampleFloat() {
// 100.1
// 100
}

func ExamplePercent() {
fmt.Println(Percent(uint64(0), uint64(10000)))
fmt.Println(Percent(uint64(1), uint64(10000)))
fmt.Println(Percent(uint64(12), uint64(10000)))
fmt.Println(Percent(uint64(123), uint64(10000)))
fmt.Println(Percent(uint64(1234), uint64(10000)))
// Output:
// 0%
// ~0%
// 0.1%
// 1.2%
// 12%
}
5 changes: 5 additions & 0 deletions crhumanize/humanize.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ type Integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}

// Numeric is a constraint that permits any integer or floating-point type.
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64
}

// SafeString represents a human readable representation of a value. It
// implements a `SafeValue()` marker method (implementing the
// github.com/cockroachdb/redact.SafeValue interface) to signal that it
Expand Down
Loading