package number
import (
"strconv"
"unicode/utf8"
"golang.org/x/text/language"
)
type VisibleDigits interface {
Digits (buf []byte , t language .Tag , scale int ) Digits
}
type Formatter struct {
Pattern
Info
}
func (f *Formatter ) init (t language .Tag , index []uint8 ) {
f .Info = InfoFromTag (t )
f .Pattern = formats [index [tagToID (t )]]
}
func (f *Formatter ) InitPattern (t language .Tag , pat *Pattern ) {
f .Info = InfoFromTag (t )
f .Pattern = *pat
}
func (f *Formatter ) InitDecimal (t language .Tag ) {
f .init (t , tagToDecimal )
}
func (f *Formatter ) InitScientific (t language .Tag ) {
f .init (t , tagToScientific )
f .Pattern .MinFractionDigits = 0
f .Pattern .MaxFractionDigits = -1
}
func (f *Formatter ) InitEngineering (t language .Tag ) {
f .init (t , tagToScientific )
f .Pattern .MinFractionDigits = 0
f .Pattern .MaxFractionDigits = -1
f .Pattern .MaxIntegerDigits = 3
f .Pattern .MinIntegerDigits = 1
}
func (f *Formatter ) InitPercent (t language .Tag ) {
f .init (t , tagToPercent )
}
func (f *Formatter ) InitPerMille (t language .Tag ) {
f .init (t , tagToPercent )
f .Pattern .DigitShift = 3
}
func (f *Formatter ) Append (dst []byte , x interface {}) []byte {
var d Decimal
r := f .RoundingContext
d .Convert (r , x )
return f .Render (dst , FormatDigits (&d , r ))
}
func FormatDigits (d *Decimal , r RoundingContext ) Digits {
if r .isScientific () {
return scientificVisibleDigits (r , d )
}
return decimalVisibleDigits (r , d )
}
func (f *Formatter ) Format (dst []byte , d *Decimal ) []byte {
return f .Render (dst , FormatDigits (d , f .RoundingContext ))
}
func (f *Formatter ) Render (dst []byte , d Digits ) []byte {
var result []byte
var postPrefix , preSuffix int
if d .IsScientific {
result , postPrefix , preSuffix = appendScientific (dst , f , &d )
} else {
result , postPrefix , preSuffix = appendDecimal (dst , f , &d )
}
if f .PadRune == 0 {
return result
}
width := int (f .FormatWidth )
if count := utf8 .RuneCount (result ); count < width {
insertPos := 0
switch f .Flags & PadMask {
case PadAfterPrefix :
insertPos = postPrefix
case PadBeforeSuffix :
insertPos = preSuffix
case PadAfterSuffix :
insertPos = len (result )
}
num := width - count
pad := [utf8 .UTFMax ]byte {' ' }
sz := 1
if r := f .PadRune ; r != 0 {
sz = utf8 .EncodeRune (pad [:], r )
}
extra := sz * num
if n := len (result ) + extra ; n < cap (result ) {
result = result [:n ]
copy (result [insertPos +extra :], result [insertPos :])
} else {
buf := make ([]byte , n )
copy (buf , result [:insertPos ])
copy (buf [insertPos +extra :], result [insertPos :])
result = buf
}
for ; num > 0 ; num -- {
insertPos += copy (result [insertPos :], pad [:sz ])
}
}
return result
}
func decimalVisibleDigits(r RoundingContext , d *Decimal ) Digits {
if d .NaN || d .Inf {
return Digits {digits : digits {Neg : d .Neg , NaN : d .NaN , Inf : d .Inf }}
}
n := Digits {digits : d .normalize ().digits }
exp := n .Exp
exp += int32 (r .DigitShift )
if r .MaxIntegerDigits > 0 {
if p := int (exp ) - int (r .MaxIntegerDigits ); p > 0 {
if p > len (n .Digits ) {
p = len (n .Digits )
}
if n .Digits = n .Digits [p :]; len (n .Digits ) == 0 {
exp = 0
} else {
exp -= int32 (p )
}
for len (n .Digits ) > 0 && n .Digits [0 ] == 0 {
n .Digits = n .Digits [1 :]
exp --
}
}
}
p := len (n .Digits )
if maxSig := int (r .MaxSignificantDigits ); maxSig > 0 {
p = maxSig
}
if maxFrac := int (r .MaxFractionDigits ); maxFrac >= 0 {
if cap := int (exp ) + maxFrac ; cap < p {
p = int (exp ) + maxFrac
}
if p < 0 {
p = 0
}
}
n .round (r .Mode , p )
n .End = int32 (len (n .Digits ))
if n .End == 0 {
exp = 0
if r .MinFractionDigits > 0 {
n .End = int32 (r .MinFractionDigits )
}
if p := int32 (r .MinSignificantDigits ) - 1 ; p > n .End {
n .End = p
}
} else {
if end := exp + int32 (r .MinFractionDigits ); end > n .End {
n .End = end
}
if n .End < int32 (r .MinSignificantDigits ) {
n .End = int32 (r .MinSignificantDigits )
}
}
n .Exp = exp
return n
}
func appendDecimal(dst []byte , f *Formatter , n *Digits ) (b []byte , postPre , preSuf int ) {
if dst , ok := f .renderSpecial (dst , n ); ok {
return dst , 0 , len (dst )
}
digits := n .Digits
exp := n .Exp
var intDigits , fracDigits []byte
numInt := 0
numFrac := int (n .End - n .Exp )
if exp > 0 {
numInt = int (exp )
if int (exp ) >= len (digits ) {
intDigits = digits
} else {
intDigits = digits [:exp ]
fracDigits = digits [exp :]
}
} else {
fracDigits = digits
}
neg := n .Neg
affix , suffix := f .getAffixes (neg )
dst = appendAffix (dst , f , affix , neg )
savedLen := len (dst )
minInt := int (f .MinIntegerDigits )
if minInt == 0 && f .MinSignificantDigits > 0 {
minInt = 1
}
for i := minInt ; i > numInt ; i -- {
dst = f .AppendDigit (dst , 0 )
if f .needsSep (i ) {
dst = append (dst , f .Symbol (SymGroup )...)
}
}
i := 0
for ; i < len (intDigits ); i ++ {
dst = f .AppendDigit (dst , intDigits [i ])
if f .needsSep (numInt - i ) {
dst = append (dst , f .Symbol (SymGroup )...)
}
}
for ; i < numInt ; i ++ {
dst = f .AppendDigit (dst , 0 )
if f .needsSep (numInt - i ) {
dst = append (dst , f .Symbol (SymGroup )...)
}
}
if numFrac > 0 || f .Flags &AlwaysDecimalSeparator != 0 {
dst = append (dst , f .Symbol (SymDecimal )...)
}
i = 0
for n := -int (n .Exp ); i < n ; i ++ {
dst = f .AppendDigit (dst , 0 )
}
for _ , d := range fracDigits {
i ++
dst = f .AppendDigit (dst , d )
}
for ; i < numFrac ; i ++ {
dst = f .AppendDigit (dst , 0 )
}
return appendAffix (dst , f , suffix , neg ), savedLen , len (dst )
}
func scientificVisibleDigits(r RoundingContext , d *Decimal ) Digits {
if d .NaN || d .Inf {
return Digits {digits : digits {Neg : d .Neg , NaN : d .NaN , Inf : d .Inf }}
}
n := Digits {digits : d .normalize ().digits , IsScientific : true }
if len (n .Digits ) == 0 {
n .Digits = append (n .Digits , 0 )
n .Exp = 1
}
maxInt , numInt := int (r .MaxIntegerDigits ), int (r .MinIntegerDigits )
if numInt == 0 {
numInt = 1
}
if maxInt > numInt {
numInt = 1
d := int (n .Exp -1 ) % maxInt
if d < 0 {
d += maxInt
}
numInt += d
}
p := len (n .Digits )
if maxSig := int (r .MaxSignificantDigits ); maxSig > 0 {
p = maxSig
}
if maxFrac := int (r .MaxFractionDigits ); maxFrac >= 0 && numInt +maxFrac < p {
p = numInt + maxFrac
}
n .round (r .Mode , p )
n .Comma = uint8 (numInt )
n .End = int32 (len (n .Digits ))
if minSig := int32 (r .MinFractionDigits ) + int32 (numInt ); n .End < minSig {
n .End = minSig
}
return n
}
func appendScientific(dst []byte , f *Formatter , n *Digits ) (b []byte , postPre , preSuf int ) {
if dst , ok := f .renderSpecial (dst , n ); ok {
return dst , 0 , 0
}
digits := n .Digits
numInt := int (n .Comma )
numFrac := int (n .End ) - int (n .Comma )
var intDigits , fracDigits []byte
if numInt <= len (digits ) {
intDigits = digits [:numInt ]
fracDigits = digits [numInt :]
} else {
intDigits = digits
}
neg := n .Neg
affix , suffix := f .getAffixes (neg )
dst = appendAffix (dst , f , affix , neg )
savedLen := len (dst )
i := 0
for ; i < len (intDigits ); i ++ {
dst = f .AppendDigit (dst , intDigits [i ])
if f .needsSep (numInt - i ) {
dst = append (dst , f .Symbol (SymGroup )...)
}
}
for ; i < numInt ; i ++ {
dst = f .AppendDigit (dst , 0 )
if f .needsSep (numInt - i ) {
dst = append (dst , f .Symbol (SymGroup )...)
}
}
if numFrac > 0 || f .Flags &AlwaysDecimalSeparator != 0 {
dst = append (dst , f .Symbol (SymDecimal )...)
}
i = 0
for ; i < len (fracDigits ); i ++ {
dst = f .AppendDigit (dst , fracDigits [i ])
}
for ; i < numFrac ; i ++ {
dst = f .AppendDigit (dst , 0 )
}
buf := [12 ]byte {}
exp := n .Exp - int32 (n .Comma )
exponential := f .Symbol (SymExponential )
if exponential == "E" {
dst = append (dst , f .Symbol (SymSuperscriptingExponent )...)
dst = f .AppendDigit (dst , 1 )
dst = f .AppendDigit (dst , 0 )
switch {
case exp < 0 :
dst = append (dst , superMinus ...)
exp = -exp
case f .Flags &AlwaysExpSign != 0 :
dst = append (dst , superPlus ...)
}
b = strconv .AppendUint (buf [:0 ], uint64 (exp ), 10 )
for i := len (b ); i < int (f .MinExponentDigits ); i ++ {
dst = append (dst , superDigits [0 ]...)
}
for _ , c := range b {
dst = append (dst , superDigits [c -'0' ]...)
}
} else {
dst = append (dst , exponential ...)
switch {
case exp < 0 :
dst = append (dst , f .Symbol (SymMinusSign )...)
exp = -exp
case f .Flags &AlwaysExpSign != 0 :
dst = append (dst , f .Symbol (SymPlusSign )...)
}
b = strconv .AppendUint (buf [:0 ], uint64 (exp ), 10 )
for i := len (b ); i < int (f .MinExponentDigits ); i ++ {
dst = f .AppendDigit (dst , 0 )
}
for _ , c := range b {
dst = f .AppendDigit (dst , c -'0' )
}
}
return appendAffix (dst , f , suffix , neg ), savedLen , len (dst )
}
const (
superMinus = "\u207B"
superPlus = "\u207A"
)
var (
superDigits = []string {
"\u2070" ,
"\u00B9" ,
"\u00B2" ,
"\u00B3" ,
"\u2074" ,
"\u2075" ,
"\u2076" ,
"\u2077" ,
"\u2078" ,
"\u2079" ,
}
)
func (f *Formatter ) getAffixes (neg bool ) (affix , suffix string ) {
str := f .Affix
if str != "" {
if f .NegOffset > 0 {
if neg {
str = str [f .NegOffset :]
} else {
str = str [:f .NegOffset ]
}
}
sufStart := 1 + str [0 ]
affix = str [1 :sufStart ]
suffix = str [sufStart +1 :]
}
if f .NegOffset == 0 && (neg || f .Flags &AlwaysSign != 0 ) {
affix = "-" + affix
}
return affix , suffix
}
func (f *Formatter ) renderSpecial (dst []byte , d *Digits ) (b []byte , ok bool ) {
if d .NaN {
return fmtNaN (dst , f ), true
}
if d .Inf {
return fmtInfinite (dst , f , d ), true
}
return dst , false
}
func fmtNaN(dst []byte , f *Formatter ) []byte {
return append (dst , f .Symbol (SymNan )...)
}
func fmtInfinite(dst []byte , f *Formatter , d *Digits ) []byte {
affix , suffix := f .getAffixes (d .Neg )
dst = appendAffix (dst , f , affix , d .Neg )
dst = append (dst , f .Symbol (SymInfinity )...)
dst = appendAffix (dst , f , suffix , d .Neg )
return dst
}
func appendAffix(dst []byte , f *Formatter , affix string , neg bool ) []byte {
quoting := false
escaping := false
for _ , r := range affix {
switch {
case escaping :
dst = append (dst , string (r )...)
escaping = false
case r == '\\' :
escaping = true
case r == '\'' :
quoting = !quoting
case quoting :
dst = append (dst , string (r )...)
case r == '%' :
if f .DigitShift == 3 {
dst = append (dst , f .Symbol (SymPerMille )...)
} else {
dst = append (dst , f .Symbol (SymPercentSign )...)
}
case r == '-' || r == '+' :
if neg {
dst = append (dst , f .Symbol (SymMinusSign )...)
} else if f .Flags &ElideSign == 0 {
dst = append (dst , f .Symbol (SymPlusSign )...)
} else {
dst = append (dst , ' ' )
}
default :
dst = append (dst , string (r )...)
}
}
return dst
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .