-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move the thousands-separator logic into rare, saving 600 KB, and increasing performance of the functions significantly
- Loading branch information
Showing
10 changed files
with
225 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package humanize | ||
|
||
import ( | ||
"bytes" | ||
"math" | ||
"strconv" | ||
) | ||
|
||
/* | ||
Previously, rare used the `message` i18n go library to add commas to numbers, but | ||
as it turns out that was a bit overkill (Benchmarking shows easily 10x slower, and added 600 KB to the | ||
binary). In an effort to pull out and streamline simpler parts of the overall process, | ||
the two below functions are implementations of the simplistic english-only localization | ||
of numbers | ||
*/ | ||
|
||
const ( | ||
baseSeparator = ',' | ||
decimalSeparator = '.' | ||
) | ||
|
||
type IntType interface { | ||
int64 | int32 | uint64 | uint32 | int | uint | ||
} | ||
|
||
func humanizeInt[T IntType](v T) string { | ||
var buf [32]byte // stack alloc | ||
|
||
if v >= 0 && v < 100 { // faster for small numbers | ||
return strconv.FormatInt(int64(v), 10) | ||
} | ||
|
||
negative := v < 0 | ||
if negative { | ||
v = -v | ||
} | ||
|
||
ci := 0 | ||
idx := len(buf) - 1 | ||
for v > 0 { | ||
if ci == 3 { | ||
buf[idx] = baseSeparator | ||
ci = 0 | ||
idx-- | ||
} | ||
|
||
buf[idx] = byte('0' + (v % 10)) | ||
idx-- | ||
ci++ | ||
v /= 10 | ||
} | ||
|
||
if negative { | ||
buf[idx] = '-' | ||
idx-- | ||
} | ||
|
||
return string(buf[idx+1:]) | ||
} | ||
|
||
func isDigit(s byte) bool { | ||
return s >= '0' && s <= '9' | ||
} | ||
|
||
func humanizeFloat(v float64, decimals int) string { | ||
// Special cases | ||
if math.IsNaN(v) { | ||
return "NaN" | ||
} | ||
if math.IsInf(v, 0) { | ||
return "Inf" | ||
} | ||
|
||
// Float to string is complicated, but can leverage FormatFload and insert commas | ||
var buf [64]byte // Operations on the stack | ||
s := strconv.AppendFloat(buf[:0], v, 'f', decimals, 64) | ||
|
||
if v > -1000.0 && v < 1000.0 { | ||
// performance escape hatch when no commas | ||
return string(s) | ||
} | ||
|
||
negative := s[0] == '-' | ||
if !isDigit(s[0]) { // assume it's a sign/prefix | ||
s = s[1:] | ||
} | ||
|
||
decIdx := bytes.IndexByte(s, '.') | ||
if decIdx < 0 { // no decimal | ||
decIdx = len(s) | ||
} | ||
|
||
// Return stack buf | ||
var retbuf [64]byte | ||
ret := retbuf[:0] | ||
|
||
if negative { | ||
ret = append(ret, '-') | ||
} | ||
|
||
// write base | ||
c3 := 3 - (decIdx % 3) | ||
|
||
for i := 0; i < decIdx; i++ { | ||
if c3 == 3 { | ||
if i > 0 { | ||
ret = append(ret, baseSeparator) | ||
} | ||
c3 = 0 | ||
} | ||
ret = append(ret, s[i]) | ||
c3++ | ||
} | ||
|
||
// write decimal | ||
if decIdx < len(s) { | ||
ret = append(ret, decimalSeparator) | ||
ret = append(ret, s[decIdx+1:]...) | ||
} | ||
|
||
return string(ret) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package humanize | ||
|
||
import ( | ||
"math" | ||
"strconv" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestFormatInt(t *testing.T) { | ||
assert.Equal(t, "0", humanizeInt(0)) | ||
assert.Equal(t, "1", humanizeInt(1)) | ||
assert.Equal(t, "-1", humanizeInt(-1)) | ||
assert.Equal(t, "10", humanizeInt(10)) | ||
assert.Equal(t, "100", humanizeInt(100)) | ||
assert.Equal(t, "1,000", humanizeInt(1000)) | ||
assert.Equal(t, "10,000", humanizeInt(10000)) | ||
|
||
assert.Equal(t, "-100", humanizeInt(-100)) | ||
assert.Equal(t, "-1,000", humanizeInt(-1000)) | ||
assert.Equal(t, "-123,123", humanizeInt(-123123)) | ||
} | ||
|
||
func TestFormatFloat(t *testing.T) { | ||
assert.Equal(t, "0", humanizeFloat(0.0, 0)) | ||
assert.Equal(t, "0.00", humanizeFloat(0.0, 2)) | ||
assert.Equal(t, "1", humanizeFloat(1.0, 0)) | ||
assert.Equal(t, "12", humanizeFloat(12.0, 0)) | ||
assert.Equal(t, "123", humanizeFloat(123.0, 0)) | ||
assert.Equal(t, "1,234", humanizeFloat(1234.0, 0)) | ||
assert.Equal(t, "12,345.0", humanizeFloat(12345.0, 1)) | ||
assert.Equal(t, "112,345.0", humanizeFloat(112345.0, 1)) | ||
assert.Equal(t, "1", humanizeFloat(1.123, 0)) | ||
assert.Equal(t, "-1", humanizeFloat(-1.123, 0)) | ||
assert.Equal(t, "1,123,123", humanizeFloat(1123123.123, 0)) | ||
assert.Equal(t, "-1,123,123", humanizeFloat(-1123123.123, 0)) | ||
assert.Equal(t, "1,123,123.12", humanizeFloat(1123123.123, 2)) | ||
assert.Equal(t, "1,123,123.123456", humanizeFloat(1123123.123456, 6)) | ||
assert.Equal(t, "-1,123,123.123456", humanizeFloat(-1123123.123456, 6)) | ||
assert.Equal(t, "-111,121,231,233,123.125000", humanizeFloat(-111121231233123.123456, 6)) | ||
assert.Equal(t, "111,121,231,233,123.125000", humanizeFloat(111121231233123.123456, 6)) | ||
assert.Equal(t, "28,446,744,073,709,551,616.0", humanizeFloat(28446744073709551615.0, 1)) | ||
|
||
assert.Equal(t, "NaN", humanizeFloat(math.NaN(), 2)) | ||
assert.Equal(t, "Inf", humanizeFloat(math.Inf(1), 2)) | ||
assert.Equal(t, "Inf", humanizeFloat(math.Inf(-1), 2)) | ||
} | ||
|
||
func BenchmarkFormatInt(b *testing.B) { | ||
for i := 0; i < b.N; i++ { | ||
humanizeInt(10000) | ||
} | ||
} | ||
|
||
func BenchmarkItoa(b *testing.B) { | ||
for i := 0; i < b.N; i++ { | ||
strconv.Itoa(10000) | ||
} | ||
} | ||
|
||
// BenchmarkFormatFloat-4 2549425 473.6 ns/op 24 B/op 1 allocs/op | ||
func BenchmarkFormatFloat(b *testing.B) { | ||
for i := 0; i < b.N; i++ { | ||
humanizeFloat(10000.123123123123, 10) | ||
} | ||
} |