// Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package number import ( "strconv" "unicode/utf8" "golang.org/x/text/language" ) // TODO: // - grouping of fractions // - allow user-defined superscript notation (such as 4) // - same for non-breaking spaces, like   // A VisibleDigits computes digits, comma placement and trailing zeros as they // will be shown to the user. type VisibleDigits interface { Digits(buf []byte, t language.Tag, scale int) Digits // TODO: Do we also need to add the verb or pass a format.State? } // Formatting proceeds along the following lines: // 0) Compose rounding information from format and context. // 1) Convert a number into a Decimal. // 2) Sanitize Decimal by adding trailing zeros, removing leading digits, and // (non-increment) rounding. The Decimal that results from this is suitable // for determining the plural form. // 3) Render the Decimal in the localized form. // Formatter contains all the information needed to render a number. type Formatter struct { Pattern Info } func (f *Formatter) init(t language.Tag, index []uint8) { f.Info = InfoFromTag(t) f.Pattern = formats[index[tagToID(t)]] } // InitPattern initializes a Formatter for the given Pattern. func (f *Formatter) InitPattern(t language.Tag, pat *Pattern) { f.Info = InfoFromTag(t) f.Pattern = *pat } // InitDecimal initializes a Formatter using the default Pattern for the given // language. func (f *Formatter) InitDecimal(t language.Tag) { f.init(t, tagToDecimal) } // InitScientific initializes a Formatter using the default Pattern for the // given language. func (f *Formatter) InitScientific(t language.Tag) { f.init(t, tagToScientific) f.Pattern.MinFractionDigits = 0 f.Pattern.MaxFractionDigits = -1 } // InitEngineering initializes a Formatter using the default Pattern for the // given language. 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 } // InitPercent initializes a Formatter using the default Pattern for the given // language. func (f *Formatter) InitPercent(t language.Tag) { f.init(t, tagToPercent) } // InitPerMille initializes a Formatter using the default Pattern for the given // language. 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 } // decimalVisibleDigits converts d according to the RoundingContext. Note that // the exponent may change as a result of this operation. 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) // Cap integer digits. Remove *most-significant* digits. 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) } // Strip leading zeros. for len(n.Digits) > 0 && n.Digits[0] == 0 { n.Digits = n.Digits[1:] exp-- } } } // Rounding if not already done by Convert. 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) // set End (trailing zeros) 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 } // appendDecimal appends a formatted number to dst. It returns two possible // insertion points for padding. 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 // Split in integer and fraction part. var intDigits, fracDigits []byte numInt := 0 numFrac := int(n.End - n.Exp) if exp > 0 { numInt = int(exp) if int(exp) >= len(digits) { // ddddd | ddddd00 intDigits = digits } else { // ddd.dd 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 } // add leading zeros 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)...) } // Add trailing zeros 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} // Normalize to have at least one digit. This simplifies engineering // notation. if len(n.Digits) == 0 { n.Digits = append(n.Digits, 0) n.Exp = 1 } // Significant digits are transformed by the parser for scientific notation // and do not need to be handled here. maxInt, numInt := int(r.MaxIntegerDigits), int(r.MinIntegerDigits) if numInt == 0 { numInt = 1 } // If a maximum number of integers is specified, the minimum must be 1 // and the exponent is grouped by this number (e.g. for engineering) if maxInt > numInt { // Correct the exponent to reflect a single integer digit. numInt = 1 // engineering // 0.01234 ([12345]e-1) -> 1.2345e-2 12.345e-3 // 12345 ([12345]e+5) -> 1.2345e4 12.345e3 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 } // appendScientific appends a formatted number to dst. It returns two possible // insertion points for padding. 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) } // exp buf := [12]byte{} // TODO: use exponential if superscripting is not available (no Latin // numbers or no tags) and use exponential in all other cases. exp := n.Exp - int32(n.Comma) exponential := f.Symbol(SymExponential) if exponential == "E" { dst = append(dst, "\u202f"...) // NARROW NO-BREAK SPACE dst = append(dst, f.Symbol(SymSuperscriptingExponent)...) dst = append(dst, "\u202f"...) // NARROW NO-BREAK SPACE 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" // SUPERSCRIPT HYPHEN-MINUS superPlus = "\u207A" // SUPERSCRIPT PLUS SIGN ) var ( // Note: the digits are not sequential!!! superDigits = []string{ "\u2070", // SUPERSCRIPT DIGIT ZERO "\u00B9", // SUPERSCRIPT DIGIT ONE "\u00B2", // SUPERSCRIPT DIGIT TWO "\u00B3", // SUPERSCRIPT DIGIT THREE "\u2074", // SUPERSCRIPT DIGIT FOUR "\u2075", // SUPERSCRIPT DIGIT FIVE "\u2076", // SUPERSCRIPT DIGIT SIX "\u2077", // SUPERSCRIPT DIGIT SEVEN "\u2078", // SUPERSCRIPT DIGIT EIGHT "\u2079", // SUPERSCRIPT DIGIT NINE } ) 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:] } // TODO: introduce a NeedNeg sign to indicate if the left pattern already // has a sign marked? 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: // escaping occurs both inside and outside of quotes 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 }