mirror of
https://github.com/miguelmota/cointop
synced 2024-11-10 13:10:26 +00:00
Scale large numbers by adding Million Billion Trillion suffix (#200)
Add option for scaling Thousand Million Billion Trillion numbers by adding suffix.
This commit is contained in:
parent
e1aded93e8
commit
3b37cc34c3
@ -136,6 +136,9 @@ func (ct *Cointop) GetCoinsTable() *table.Table {
|
|||||||
})
|
})
|
||||||
case "24h_volume":
|
case "24h_volume":
|
||||||
text := humanize.Monetaryf(coin.Volume24H, 0)
|
text := humanize.Monetaryf(coin.Volume24H, 0)
|
||||||
|
if ct.IsActiveTableCompactNotation() {
|
||||||
|
text = humanize.ScaleNumericf(coin.Volume24H, 3)
|
||||||
|
}
|
||||||
ct.SetTableColumnWidthFromString(header, text)
|
ct.SetTableColumnWidthFromString(header, text)
|
||||||
ct.SetTableColumnAlignLeft(header, false)
|
ct.SetTableColumnAlignLeft(header, false)
|
||||||
rowCells = append(rowCells,
|
rowCells = append(rowCells,
|
||||||
@ -243,6 +246,9 @@ func (ct *Cointop) GetCoinsTable() *table.Table {
|
|||||||
})
|
})
|
||||||
case "market_cap":
|
case "market_cap":
|
||||||
text := humanize.Monetaryf(coin.MarketCap, 0)
|
text := humanize.Monetaryf(coin.MarketCap, 0)
|
||||||
|
if ct.IsActiveTableCompactNotation() {
|
||||||
|
text = humanize.ScaleNumericf(coin.MarketCap, 3)
|
||||||
|
}
|
||||||
ct.SetTableColumnWidthFromString(header, text)
|
ct.SetTableColumnWidthFromString(header, text)
|
||||||
ct.SetTableColumnAlignLeft(header, false)
|
ct.SetTableColumnAlignLeft(header, false)
|
||||||
rowCells = append(rowCells,
|
rowCells = append(rowCells,
|
||||||
@ -255,6 +261,9 @@ func (ct *Cointop) GetCoinsTable() *table.Table {
|
|||||||
})
|
})
|
||||||
case "total_supply":
|
case "total_supply":
|
||||||
text := humanize.Numericf(coin.TotalSupply, 0)
|
text := humanize.Numericf(coin.TotalSupply, 0)
|
||||||
|
if ct.IsActiveTableCompactNotation() {
|
||||||
|
text = humanize.ScaleNumericf(coin.TotalSupply, 3)
|
||||||
|
}
|
||||||
ct.SetTableColumnWidthFromString(header, text)
|
ct.SetTableColumnWidthFromString(header, text)
|
||||||
ct.SetTableColumnAlignLeft(header, false)
|
ct.SetTableColumnAlignLeft(header, false)
|
||||||
rowCells = append(rowCells,
|
rowCells = append(rowCells,
|
||||||
@ -267,6 +276,9 @@ func (ct *Cointop) GetCoinsTable() *table.Table {
|
|||||||
})
|
})
|
||||||
case "available_supply":
|
case "available_supply":
|
||||||
text := humanize.Numericf(coin.AvailableSupply, 0)
|
text := humanize.Numericf(coin.AvailableSupply, 0)
|
||||||
|
if ct.IsActiveTableCompactNotation() {
|
||||||
|
text = humanize.ScaleNumericf(coin.AvailableSupply, 3)
|
||||||
|
}
|
||||||
ct.SetTableColumnWidthFromString(header, text)
|
ct.SetTableColumnWidthFromString(header, text)
|
||||||
ct.SetTableColumnAlignLeft(header, false)
|
ct.SetTableColumnAlignLeft(header, false)
|
||||||
rowCells = append(rowCells,
|
rowCells = append(rowCells,
|
||||||
|
@ -89,6 +89,11 @@ type State struct {
|
|||||||
priceAlerts *PriceAlerts
|
priceAlerts *PriceAlerts
|
||||||
priceAlertEditID string
|
priceAlertEditID string
|
||||||
priceAlertNewID string
|
priceAlertNewID string
|
||||||
|
|
||||||
|
compactNotation bool
|
||||||
|
tableCompactNotation bool
|
||||||
|
favoritesCompactNotation bool
|
||||||
|
portfolioCompactNotation bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cointop cointop
|
// Cointop cointop
|
||||||
@ -181,6 +186,9 @@ var DefaultCurrency = "USD"
|
|||||||
// DefaultChartRange ...
|
// DefaultChartRange ...
|
||||||
var DefaultChartRange = "1Y"
|
var DefaultChartRange = "1Y"
|
||||||
|
|
||||||
|
// DefaultCompactNotation ...
|
||||||
|
var DefaultCompactNotation = false
|
||||||
|
|
||||||
// DefaultMaxChartWidth ...
|
// DefaultMaxChartWidth ...
|
||||||
var DefaultMaxChartWidth = 175
|
var DefaultMaxChartWidth = 175
|
||||||
|
|
||||||
@ -291,6 +299,10 @@ func NewCointop(config *Config) (*Cointop, error) {
|
|||||||
Entries: make([]*PriceAlert, 0),
|
Entries: make([]*PriceAlert, 0),
|
||||||
SoundEnabled: true,
|
SoundEnabled: true,
|
||||||
},
|
},
|
||||||
|
compactNotation: DefaultCompactNotation,
|
||||||
|
tableCompactNotation: DefaultCompactNotation,
|
||||||
|
favoritesCompactNotation: DefaultCompactNotation,
|
||||||
|
portfolioCompactNotation: DefaultCompactNotation,
|
||||||
},
|
},
|
||||||
Views: &Views{
|
Views: &Views{
|
||||||
Chart: NewChartView(),
|
Chart: NewChartView(),
|
||||||
|
@ -48,6 +48,7 @@ type ConfigFileConfig struct {
|
|||||||
Colorscheme interface{} `toml:"colorscheme"`
|
Colorscheme interface{} `toml:"colorscheme"`
|
||||||
RefreshRate interface{} `toml:"refresh_rate"`
|
RefreshRate interface{} `toml:"refresh_rate"`
|
||||||
CacheDir interface{} `toml:"cache_dir"`
|
CacheDir interface{} `toml:"cache_dir"`
|
||||||
|
CompactNotation interface{} `toml:"compact_notation"`
|
||||||
Table map[string]interface{} `toml:"table"`
|
Table map[string]interface{} `toml:"table"`
|
||||||
Chart map[string]interface{} `toml:"chart"`
|
Chart map[string]interface{} `toml:"chart"`
|
||||||
}
|
}
|
||||||
@ -70,6 +71,7 @@ func (ct *Cointop) SetupConfig() error {
|
|||||||
ct.loadColorschemeFromConfig,
|
ct.loadColorschemeFromConfig,
|
||||||
ct.loadRefreshRateFromConfig,
|
ct.loadRefreshRateFromConfig,
|
||||||
ct.loadCacheDirFromConfig,
|
ct.loadCacheDirFromConfig,
|
||||||
|
ct.loadCompactNotationFromConfig,
|
||||||
ct.loadPriceAlertsFromConfig,
|
ct.loadPriceAlertsFromConfig,
|
||||||
ct.loadPortfolioFromConfig,
|
ct.loadPortfolioFromConfig,
|
||||||
}
|
}
|
||||||
@ -219,6 +221,7 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) {
|
|||||||
"names": favoritesIfc,
|
"names": favoritesIfc,
|
||||||
"columns": ct.State.favoritesTableColumns,
|
"columns": ct.State.favoritesTableColumns,
|
||||||
"character": ct.State.favoriteChar,
|
"character": ct.State.favoriteChar,
|
||||||
|
"compact_notation": ct.State.favoritesCompactNotation,
|
||||||
}
|
}
|
||||||
|
|
||||||
var holdingsIfc [][]string
|
var holdingsIfc [][]string
|
||||||
@ -238,6 +241,7 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) {
|
|||||||
portfolioIfc := map[string]interface{}{
|
portfolioIfc := map[string]interface{}{
|
||||||
"holdings": holdingsIfc,
|
"holdings": holdingsIfc,
|
||||||
"columns": ct.State.portfolioTableColumns,
|
"columns": ct.State.portfolioTableColumns,
|
||||||
|
"compact_notation": ct.State.portfolioCompactNotation,
|
||||||
}
|
}
|
||||||
|
|
||||||
cmcIfc := map[string]interface{}{
|
cmcIfc := map[string]interface{}{
|
||||||
@ -264,6 +268,7 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) {
|
|||||||
tableMapIfc := map[string]interface{}{
|
tableMapIfc := map[string]interface{}{
|
||||||
"columns": ct.State.coinsTableColumns,
|
"columns": ct.State.coinsTableColumns,
|
||||||
"keep_row_focus_on_sort": ct.State.keepRowFocusOnSort,
|
"keep_row_focus_on_sort": ct.State.keepRowFocusOnSort,
|
||||||
|
"compact_notation": ct.State.tableCompactNotation,
|
||||||
}
|
}
|
||||||
|
|
||||||
chartMapIfc := map[string]interface{}{
|
chartMapIfc := map[string]interface{}{
|
||||||
@ -286,6 +291,7 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) {
|
|||||||
CacheDir: ct.State.cacheDir,
|
CacheDir: ct.State.cacheDir,
|
||||||
Table: tableMapIfc,
|
Table: tableMapIfc,
|
||||||
Chart: chartMapIfc,
|
Chart: chartMapIfc,
|
||||||
|
CompactNotation: ct.State.compactNotation,
|
||||||
}
|
}
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
@ -310,6 +316,11 @@ func (ct *Cointop) loadTableConfig() error {
|
|||||||
if ok {
|
if ok {
|
||||||
ct.State.keepRowFocusOnSort = keepRowFocusOnSortIfc.(bool)
|
ct.State.keepRowFocusOnSort = keepRowFocusOnSortIfc.(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if compactNotation, ok := ct.config.Table["compact_notation"]; ok {
|
||||||
|
ct.State.tableCompactNotation = compactNotation.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,6 +469,16 @@ func (ct *Cointop) loadCacheDirFromConfig() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadCompactNotationFromConfig loads compact-notation setting from config file to struct
|
||||||
|
func (ct *Cointop) loadCompactNotationFromConfig() error {
|
||||||
|
log.Debug("loadCompactNotationFromConfig()")
|
||||||
|
if compactNotation, ok := ct.config.CompactNotation.(bool); ok {
|
||||||
|
ct.State.compactNotation = compactNotation
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// LoadAPIChoiceFromConfig loads API choices from config file to struct
|
// LoadAPIChoiceFromConfig loads API choices from config file to struct
|
||||||
func (ct *Cointop) loadAPIChoiceFromConfig() error {
|
func (ct *Cointop) loadAPIChoiceFromConfig() error {
|
||||||
log.Debug("loadAPIKeysFromConfig()")
|
log.Debug("loadAPIKeysFromConfig()")
|
||||||
@ -480,6 +501,8 @@ func (ct *Cointop) loadFavoritesFromConfig() error {
|
|||||||
}
|
}
|
||||||
ct.State.favoriteChar = favoriteChar
|
ct.State.favoriteChar = favoriteChar
|
||||||
}
|
}
|
||||||
|
} else if k == "compact_notation" {
|
||||||
|
ct.State.favoritesCompactNotation = valueIfc.(bool)
|
||||||
}
|
}
|
||||||
ifcs, ok := valueIfc.([]interface{})
|
ifcs, ok := valueIfc.([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -568,6 +591,8 @@ func (ct *Cointop) loadPortfolioFromConfig() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if key == "compact_notation" {
|
||||||
|
ct.State.portfolioCompactNotation = valueIfc.(bool)
|
||||||
} else {
|
} else {
|
||||||
// Backward compatibility < v1.6.0
|
// Backward compatibility < v1.6.0
|
||||||
holdings, err := ct.InterfaceToFloat64(valueIfc)
|
holdings, err := ct.InterfaceToFloat64(valueIfc)
|
||||||
|
@ -40,6 +40,9 @@ func (ct *Cointop) UpdateMarketbar() error {
|
|||||||
total = math.Round(total*1e2) / 1e2
|
total = math.Round(total*1e2) / 1e2
|
||||||
totalstr = humanize.Monetaryf(total, 2)
|
totalstr = humanize.Monetaryf(total, 2)
|
||||||
}
|
}
|
||||||
|
if ct.State.compactNotation {
|
||||||
|
totalstr = humanize.ScaleNumericf(total, 3)
|
||||||
|
}
|
||||||
|
|
||||||
timeframe := ct.State.selectedChartRange
|
timeframe := ct.State.selectedChartRange
|
||||||
chartname := ct.SelectedCoinName()
|
chartname := ct.SelectedCoinName()
|
||||||
@ -153,12 +156,19 @@ func (ct *Cointop) UpdateMarketbar() error {
|
|||||||
separator2 = "\n" + offset
|
separator2 = "\n" + offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
marketCapStr := humanize.Monetaryf(market.TotalMarketCapUSD, 0)
|
||||||
|
volumeStr := humanize.Monetaryf(market.Total24HVolumeUSD, 0)
|
||||||
|
if ct.State.compactNotation {
|
||||||
|
marketCapStr = humanize.ScaleNumericf(market.TotalMarketCapUSD, 3)
|
||||||
|
volumeStr = humanize.ScaleNumericf(market.Total24HVolumeUSD, 3)
|
||||||
|
}
|
||||||
|
|
||||||
content = fmt.Sprintf(
|
content = fmt.Sprintf(
|
||||||
"%sGlobal ▶ Market Cap: %s %s 24H Volume: %s %s BTC Dominance: %.2f%%",
|
"%sGlobal ▶ Market Cap: %s %s 24H Volume: %s %s BTC Dominance: %.2f%%",
|
||||||
chartInfo,
|
chartInfo,
|
||||||
fmt.Sprintf("%s%s", ct.CurrencySymbol(), humanize.Monetaryf(market.TotalMarketCapUSD, 0)),
|
fmt.Sprintf("%s%s", ct.CurrencySymbol(), marketCapStr),
|
||||||
separator1,
|
separator1,
|
||||||
fmt.Sprintf("%s%s", ct.CurrencySymbol(), humanize.Monetaryf(market.Total24HVolumeUSD, 0)),
|
fmt.Sprintf("%s%s", ct.CurrencySymbol(), volumeStr),
|
||||||
separator2,
|
separator2,
|
||||||
market.BitcoinPercentageOfMarketCap,
|
market.BitcoinPercentageOfMarketCap,
|
||||||
)
|
)
|
||||||
|
@ -21,6 +21,7 @@ var ArrowDown = "▼"
|
|||||||
type HeaderColumn struct {
|
type HeaderColumn struct {
|
||||||
Slug string
|
Slug string
|
||||||
Label string
|
Label string
|
||||||
|
ShortLabel string // only columns with a ShortLabel can be scaled?
|
||||||
PlainLabel string
|
PlainLabel string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,11 +70,13 @@ var HeaderColumns = map[string]*HeaderColumn{
|
|||||||
"market_cap": {
|
"market_cap": {
|
||||||
Slug: "market_cap",
|
Slug: "market_cap",
|
||||||
Label: "[m]arket cap",
|
Label: "[m]arket cap",
|
||||||
|
ShortLabel: "[m]cap",
|
||||||
PlainLabel: "market cap",
|
PlainLabel: "market cap",
|
||||||
},
|
},
|
||||||
"24h_volume": {
|
"24h_volume": {
|
||||||
Slug: "24h_volume",
|
Slug: "24h_volume",
|
||||||
Label: "24H [v]olume",
|
Label: "24H [v]olume",
|
||||||
|
ShortLabel: "24[v]",
|
||||||
PlainLabel: "24H volume",
|
PlainLabel: "24H volume",
|
||||||
},
|
},
|
||||||
"1h_change": {
|
"1h_change": {
|
||||||
@ -104,11 +107,13 @@ var HeaderColumns = map[string]*HeaderColumn{
|
|||||||
"total_supply": {
|
"total_supply": {
|
||||||
Slug: "total_supply",
|
Slug: "total_supply",
|
||||||
Label: "[t]otal supply",
|
Label: "[t]otal supply",
|
||||||
|
ShortLabel: "[t]ot",
|
||||||
PlainLabel: "total supply",
|
PlainLabel: "total supply",
|
||||||
},
|
},
|
||||||
"available_supply": {
|
"available_supply": {
|
||||||
Slug: "available_supply",
|
Slug: "available_supply",
|
||||||
Label: "[a]vailable supply",
|
Label: "[a]vailable supply",
|
||||||
|
ShortLabel: "[a]vl",
|
||||||
PlainLabel: "available supply",
|
PlainLabel: "available supply",
|
||||||
},
|
},
|
||||||
"percent_holdings": {
|
"percent_holdings": {
|
||||||
@ -123,6 +128,15 @@ var HeaderColumns = map[string]*HeaderColumn{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLabel fetch the label to use for the heading (depends on configuration)
|
||||||
|
func (ct *Cointop) GetLabel(h *HeaderColumn) string {
|
||||||
|
// TODO: technically this should support nosort
|
||||||
|
if ct.IsActiveTableCompactNotation() && h.ShortLabel != "" {
|
||||||
|
return h.ShortLabel
|
||||||
|
}
|
||||||
|
return h.Label
|
||||||
|
}
|
||||||
|
|
||||||
// TableHeaderView is structure for table header view
|
// TableHeaderView is structure for table header view
|
||||||
type TableHeaderView = ui.View
|
type TableHeaderView = ui.View
|
||||||
|
|
||||||
@ -145,6 +159,22 @@ func (ct *Cointop) GetActiveTableHeaders() []string {
|
|||||||
return cols
|
return cols
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetActiveTableHeaders returns the list of active table headers
|
||||||
|
func (ct *Cointop) IsActiveTableCompactNotation() bool {
|
||||||
|
var compact bool
|
||||||
|
switch ct.State.selectedView {
|
||||||
|
case PortfolioView:
|
||||||
|
compact = ct.State.portfolioCompactNotation
|
||||||
|
case CoinsView:
|
||||||
|
compact = ct.State.tableCompactNotation
|
||||||
|
case FavoritesView:
|
||||||
|
compact = ct.State.favoritesCompactNotation
|
||||||
|
default:
|
||||||
|
compact = ct.State.tableCompactNotation
|
||||||
|
}
|
||||||
|
return compact
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateTableHeader renders the table header
|
// UpdateTableHeader renders the table header
|
||||||
func (ct *Cointop) UpdateTableHeader() error {
|
func (ct *Cointop) UpdateTableHeader() error {
|
||||||
log.Debug("UpdateTableHeader()")
|
log.Debug("UpdateTableHeader()")
|
||||||
@ -175,7 +205,7 @@ func (ct *Cointop) UpdateTableHeader() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
label := hc.Label
|
label := ct.GetLabel(hc)
|
||||||
if noSort {
|
if noSort {
|
||||||
label = hc.PlainLabel
|
label = hc.PlainLabel
|
||||||
}
|
}
|
||||||
@ -240,7 +270,7 @@ func (ct *Cointop) SetTableColumnWidth(header string, width int) {
|
|||||||
prev = prevIfc.(int)
|
prev = prevIfc.(int)
|
||||||
} else {
|
} else {
|
||||||
hc := HeaderColumns[header]
|
hc := HeaderColumns[header]
|
||||||
prev = utf8.RuneCountInString(hc.Label) + 1
|
prev = utf8.RuneCountInString(ct.GetLabel(hc)) + 1
|
||||||
switch header {
|
switch header {
|
||||||
case "price", "balance":
|
case "price", "balance":
|
||||||
prev++
|
prev++
|
||||||
|
@ -357,6 +357,12 @@ draft: false
|
|||||||
|
|
||||||
Supported columns relating to price change are `1h_change`, `24h_change`, `7d_change`, `30d_change`, `1y_change`
|
Supported columns relating to price change are `1h_change`, `24h_change`, `7d_change`, `30d_change`, `1y_change`
|
||||||
|
|
||||||
|
## How can I use K (thousand), M (million), B (billion), T (trillion) suffixes for shorter numbers?
|
||||||
|
|
||||||
|
There is a setting at the top-level of the configuration file called `compact_notation=true` which changes the marketbar values `market cap`, `volume` and `portfolio total value`.
|
||||||
|
|
||||||
|
The same setting can be applied at in the `[table]` section to impact the `24h_volume`, `market_cap`, `total_supply`, `available_supply` columns in the main coin view; and in the `[favorites]` section to change the same columns. The setting also changes the column names to be shorter.
|
||||||
|
|
||||||
## How can use a different config file other than the default?
|
## How can use a different config file other than the default?
|
||||||
|
|
||||||
Run cointop with the `--config` flag, eg `cointop --config="/path/to/config.toml"`, to use the specified file as the config.
|
Run cointop with the `--config` flag, eg `cointop --config="/path/to/config.toml"`, to use the specified file as the config.
|
||||||
|
@ -2,6 +2,7 @@ package humanize
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -12,7 +13,7 @@ import (
|
|||||||
|
|
||||||
// Numericf produces a string from of the given number with give fixed precision
|
// Numericf produces a string from of the given number with give fixed precision
|
||||||
// in base 10 with thousands separators after every three orders of magnitude
|
// in base 10 with thousands separators after every three orders of magnitude
|
||||||
// using a thousands and decimal spearator according to LC_NUMERIC; defaulting "en".
|
// using thousands and decimal separator according to LC_NUMERIC; defaulting "en".
|
||||||
//
|
//
|
||||||
// e.g. Numericf(834142.32, 2) -> "834,142.32"
|
// e.g. Numericf(834142.32, 2) -> "834,142.32"
|
||||||
func Numericf(value float64, precision int) string {
|
func Numericf(value float64, precision int) string {
|
||||||
@ -21,16 +22,16 @@ func Numericf(value float64, precision int) string {
|
|||||||
|
|
||||||
// Monetaryf produces a string from of the given number give minimum precision
|
// Monetaryf produces a string from of the given number give minimum precision
|
||||||
// in base 10 with thousands separators after every three orders of magnitude
|
// in base 10 with thousands separators after every three orders of magnitude
|
||||||
// using thousands and decimal spearator according to LC_MONETARY; defaulting "en".
|
// using thousands and decimal separator according to LC_MONETARY; defaulting "en".
|
||||||
//
|
//
|
||||||
// e.g. Monetaryf(834142.3256, 2) -> "834,142.3256"
|
// e.g. Monetaryf(834142.3256, 2) -> "834,142.3256"
|
||||||
func Monetaryf(value float64, precision int) string {
|
func Monetaryf(value float64, precision int) string {
|
||||||
return f(value, precision, "LC_MONETARY", false)
|
return f(value, precision, "LC_MONETARY", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// f formats given value v, with d decimal places using thousands and decimal
|
// f formats given value, with precision decimal places using thousands and decimal
|
||||||
// separator according to language found in given locale environment variable e.
|
// separator according to language found in given locale environment variable e.
|
||||||
// If r is true the decimal places are fixed to the given d otherwise d is the
|
// If fixed is true the decimal places are fixed to the given precision otherwise d is the
|
||||||
// minimum of decimal places until the first 0.
|
// minimum of decimal places until the first 0.
|
||||||
func f(value float64, precision int, envvar string, fixed bool) string {
|
func f(value float64, precision int, envvar string, fixed bool) string {
|
||||||
parts := strings.Split(strconv.FormatFloat(value, 'f', -1, 64), ".")
|
parts := strings.Split(strconv.FormatFloat(value, 'f', -1, 64), ".")
|
||||||
@ -51,3 +52,47 @@ func f(value float64, precision int, envvar string, fixed bool) string {
|
|||||||
format := fmt.Sprintf("%%.%df", precision)
|
format := fmt.Sprintf("%%.%df", precision)
|
||||||
return message.NewPrinter(lang).Sprintf(format, value)
|
return message.NewPrinter(lang).Sprintf(format, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scale returns a scaled-down version of value and a suffix to add (M,B,etc.)
|
||||||
|
func Scale(value float64) (float64, string) {
|
||||||
|
type scalingUnit struct {
|
||||||
|
value float64
|
||||||
|
suffix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// quadrillion, quintrillion, sextillion, septillion, octillion, nonillion, and decillion
|
||||||
|
var scales = [...]scalingUnit{
|
||||||
|
{value: 1e12, suffix: "T"},
|
||||||
|
{value: 1e9, suffix: "B"},
|
||||||
|
{value: 1e6, suffix: "M"},
|
||||||
|
{value: 1e3, suffix: "K"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scale := range scales {
|
||||||
|
if math.Abs(value) > scale.value {
|
||||||
|
return value / scale.value, scale.suffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScaleNumericf scales a large number down using a suffix, then formats it with the
|
||||||
|
// prescribed number of significant digits.
|
||||||
|
func ScaleNumericf(value float64, digits int) string {
|
||||||
|
value, suffix := Scale(value)
|
||||||
|
|
||||||
|
// Round the scaled value to a certain number of significant figures
|
||||||
|
var s string
|
||||||
|
if math.Abs(value) < 1 {
|
||||||
|
s = Numericf(value, digits)
|
||||||
|
} else {
|
||||||
|
numDigits := len(fmt.Sprintf("%.0f", math.Abs(value)))
|
||||||
|
if numDigits >= digits {
|
||||||
|
s = Numericf(value, 0)
|
||||||
|
} else {
|
||||||
|
s = Numericf(value, digits-numDigits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s + suffix
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package humanize
|
package humanize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -10,3 +11,43 @@ func TestMonetary(t *testing.T) {
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestScale(t *testing.T) {
|
||||||
|
scaleTests := map[float64]string{
|
||||||
|
5.54 * 1e12: "5.5T",
|
||||||
|
4.44 * 1e9: "4.4B",
|
||||||
|
3.34 * 1e6: "3.3M",
|
||||||
|
2.24 * 1e3: "2.2K",
|
||||||
|
1.1: "1.1",
|
||||||
|
0.06: "0.1",
|
||||||
|
0.04: "0.0",
|
||||||
|
-5.54 * 1e12: "-5.5T",
|
||||||
|
}
|
||||||
|
|
||||||
|
for value, expected := range scaleTests {
|
||||||
|
volScale, volSuffix := Scale(value)
|
||||||
|
result := fmt.Sprintf("%.1f%s", volScale, volSuffix)
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("Expected %f to scale to '%s' but got '%s'\n", value, expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScaleNumeric(t *testing.T) {
|
||||||
|
scaleTests := map[float64]string{
|
||||||
|
5.54 * 1e12: "5.5T",
|
||||||
|
4.44 * 1e9: "4.4B",
|
||||||
|
3.34 * 1e6: "3.3M",
|
||||||
|
2.24 * 1e3: "2.2K",
|
||||||
|
1.1: "1.1",
|
||||||
|
0.0611: "0.06",
|
||||||
|
-5.5432 * 1e12: "-5.5T",
|
||||||
|
}
|
||||||
|
|
||||||
|
for value, expected := range scaleTests {
|
||||||
|
result := ScaleNumericf(value, 2)
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("Expected %f to scale to '%s' but got '%s'\n", value, expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user