internal/number: scientific encoding Implement spec’s suggestion for rendering scientific notation. Added TODO for supporting HTML variant. Change-Id: I05b263f598cec44ad1670c8d74196c3a8745a211 Reviewed-on: https://go-review.googlesource.com/46251 Run-TryBot: Marcel van Lohuizen <mpvl@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/internal/number/format.go b/internal/number/format.go index a5f2efd..6cb243c 100755 --- a/internal/number/format.go +++ b/internal/number/format.go
@@ -12,9 +12,17 @@ ) // TODO: -// - split out the logic that computes the visible digits from the rest of the -// formatting code (needed for plural). // - grouping of fractions +// - allow user-defined superscript notation (such as <sup>4</sup>) +// - same for non-breaking spaces, like + +// 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 { @@ -325,25 +333,71 @@ } // exp - dst = append(dst, f.Symbol(SymExponential)...) - switch { - case exp < 0: - dst = append(dst, f.Symbol(SymMinusSign)...) - exp = -exp - case f.Flags&AlwaysExpSign != 0: - dst = append(dst, f.Symbol(SymPlusSign)...) - } buf := [12]byte{} - b = strconv.AppendUint(buf[:0], uint64(exp), 10) - for i := len(b); i < int(f.MinExponentDigits); i++ { + // TODO: use exponential if superscripting is not available (no Latin + // numbers or no tags) and use exponential in all other cases. + 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) - } - for _, c := range b { - dst = f.AppendDigit(dst, c-'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 != "" {
diff --git a/internal/number/format_test.go b/internal/number/format_test.go index 0ec2440..d7b65b0 100755 --- a/internal/number/format_test.go +++ b/internal/number/format_test.go
@@ -259,60 +259,60 @@ }, { pattern: "#E0", test: pairs{ - "0": "0E0", - "1": "1E0", - "123.456": "1E2", + "0": "0\u202f×\u202f10⁰", + "1": "1\u202f×\u202f10⁰", + "123.456": "1\u202f×\u202f10²", }, }, { pattern: "#E+0", test: pairs{ - "0": "0E+0", - "1000": "1E+3", - "1E100": "1E+100", - "1E-100": "1E-100", + "0": "0\u202f×\u202f10⁺⁰", + "1000": "1\u202f×\u202f10⁺³", + "1E100": "1\u202f×\u202f10⁺¹⁰⁰", + "1E-100": "1\u202f×\u202f10⁻¹⁰⁰", "NaN": "NaN", "-Inf": "-∞", }, }, { pattern: "##0E00", test: pairs{ - "100": "100E00", - "12345": "10E03", - "123.456": "100E00", + "100": "100\u202f×\u202f10⁰⁰", + "12345": "10\u202f×\u202f10⁰³", + "123.456": "100\u202f×\u202f10⁰⁰", }, }, { pattern: "##0.###E00", test: pairs{ - "100": "100E00", - "12345": "12.34E03", - "123.456": "123.4E00", + "100": "100\u202f×\u202f10⁰⁰", + "12345": "12.34\u202f×\u202f10⁰³", + "123.456": "123.4\u202f×\u202f10⁰⁰", }, }, { pattern: "##0.000E00", test: pairs{ - "100": "100.0E00", - "12345": "12.34E03", - "123.456": "123.4E00", + "100": "100.0\u202f×\u202f10⁰⁰", + "12345": "12.34\u202f×\u202f10⁰³", + "123.456": "123.4\u202f×\u202f10⁰⁰", }, }, { pattern: "@@E0", test: pairs{ - "0": "0.0E0", - "99": "9.9E1", - "0.99": "9.9E-1", + "0": "0.0\u202f×\u202f10⁰", + "99": "9.9\u202f×\u202f10¹", + "0.99": "9.9\u202f×\u202f10⁻¹", }, }, { pattern: "@###E00", test: pairs{ - "0": "0E00", - "1": "1E00", - "11": "1.1E01", - "111": "1.11E02", - "1111": "1.111E03", - "11111": "1.111E04", - "0.1": "1E-01", - "0.11": "1.1E-01", - "0.001": "1E-03", + "0": "0\u202f×\u202f10⁰⁰", + "1": "1\u202f×\u202f10⁰⁰", + "11": "1.1\u202f×\u202f10⁰¹", + "111": "1.11\u202f×\u202f10⁰²", + "1111": "1.111\u202f×\u202f10⁰³", + "11111": "1.111\u202f×\u202f10⁰⁴", + "0.1": "1\u202f×\u202f10⁻⁰¹", + "0.11": "1.1\u202f×\u202f10⁻⁰¹", + "0.001": "1\u202f×\u202f10⁻⁰³", }, }, { pattern: "*x###", @@ -339,18 +339,18 @@ "1234.567": "1234.567", }, }, { - pattern: "**0.0##E00", + pattern: "**0.0#######E00", test: pairs{ - "0": "**0.0E00", - "10": "**1.0E01", - "11": "**1.1E01", - "111": "*1.11E02", - "1111": "1.111E03", - "11111": "1.111E04", - "11110": "1.111E04", - "11100": "*1.11E04", - "11000": "**1.1E04", - "10000": "**1.0E04", + "0": "***0.0\u202f×\u202f10⁰⁰", + "10": "***1.0\u202f×\u202f10⁰¹", + "11": "***1.1\u202f×\u202f10⁰¹", + "111": "**1.11\u202f×\u202f10⁰²", + "1111": "*1.111\u202f×\u202f10⁰³", + "11111": "1.1111\u202f×\u202f10⁰⁴", + "11110": "*1.111\u202f×\u202f10⁰⁴", + "11100": "**1.11\u202f×\u202f10⁰⁴", + "11000": "***1.1\u202f×\u202f10⁰⁴", + "10000": "***1.0\u202f×\u202f10⁰⁴", }, }, { pattern: "*xpre#suf", @@ -443,7 +443,7 @@ dec := mkdec(dec) buf = f.Format(buf[:0], &dec) if got := string(buf); got != want { - t.Errorf("\n got %q\nwant %q", got, want) + t.Errorf("\n got %[1]q (%[1]s)\nwant %[2]q (%[2]s)", got, want) } }) } @@ -469,7 +469,7 @@ d := mkdec(tc.num) b := f.Format(nil, &d) if got := string(b); got != tc.want { - t.Errorf("got %q; want %q", got, tc.want) + t.Errorf("got %[1]q (%[1]s); want %[2]q (%[2]s)", got, tc.want) } }) } @@ -483,8 +483,8 @@ want string }{ {f.InitDecimal, "123456.78", "123,456.78"}, - {f.InitScientific, "123456.78", "1.23E5"}, - {f.InitEngineering, "123456.78", "123E3"}, + {f.InitScientific, "123456.78", "1.23\u202f×\u202f10⁵"}, + {f.InitEngineering, "123456.78", "123\u202f×\u202f10³"}, {f.InitPercent, "0.1234", "12.34%"}, {f.InitPerMille, "0.1234", "123.40‰"}, @@ -497,7 +497,7 @@ d := mkdec(tc.num) b := f.Format(nil, &d) if got := string(b); got != tc.want { - t.Errorf("got %q; want %q", got, tc.want) + t.Errorf("got %[1]q (%[1]s); want %[2]q (%[2]s)", got, tc.want) } }) }