1// SPDX-License-Identifier: MIT
2
3package template
4
5import (
6 "math"
7 "strconv"
8 "strings"
9)
10
11// JSNumberString formats x like JavaScript's Number.prototype.toString: the
12// shortest decimal that round-trips, positional for decimal exponents from -7
13// to 20, exponent notation beyond. The reference uses it for the text of a
14// number token.
15func JSNumberString(x float64) string {
16 switch {
17 case math.IsNaN(x):
18 return "NaN"
19 case x == 0:
20 return "0"
21 case math.IsInf(x, 1):
22 return "Infinity"
23 case math.IsInf(x, -1):
24 return "-Infinity"
25 }
26 sign := ""
27 if x < 0 {
28 sign = "-"
29 x = -x
30 }
31 s := strconv.FormatFloat(x, 'e', -1, 64)
32 mant, expStr, _ := strings.Cut(s, "e")
33 exp, _ := strconv.Atoi(expStr)
34 digits := strings.Replace(mant, ".", "", 1)
35 k := len(digits)
36 n := exp + 1
37 switch {
38 case k <= n && n <= 21:
39 return sign + digits + strings.Repeat("0", n-k)
40 case 0 < n && n <= 21:
41 return sign + digits[:n] + "." + digits[n:]
42 case -6 < n && n <= 0:
43 return sign + "0." + strings.Repeat("0", -n) + digits
44 }
45 e := n - 1
46 esign := "+"
47 if e < 0 {
48 esign = "-"
49 e = -e
50 }
51 if k == 1 {
52 return sign + digits + "e" + esign + strconv.Itoa(e)
53 }
54 return sign + digits[:1] + "." + digits[1:] + "e" + esign + strconv.Itoa(e)
55}
56
57// DotNetNumberString formats x like .NET's Double.ToString("G15",
58// CultureInfo.InvariantCulture), which is what the C# runner's NumberToken
59// shows (and what a number becomes when it is read as a string): at most 15
60// significant digits, positional when the decimal exponent is at least -4 and
61// below 15, "E+XX"/"E-XX" with at least two exponent digits otherwise, "-0" for
62// negative zero.
63func DotNetNumberString(x float64) string {
64 switch {
65 case math.IsNaN(x):
66 return "NaN"
67 case math.IsInf(x, 1):
68 return "Infinity"
69 case math.IsInf(x, -1):
70 return "-Infinity"
71 case x == 0:
72 if math.Signbit(x) {
73 return "-0"
74 }
75 return "0"
76 }
77 sign := ""
78 if x < 0 {
79 sign = "-"
80 x = -x
81 }
82 s := strconv.FormatFloat(x, 'e', 14, 64)
83 mant, expStr, _ := strings.Cut(s, "e")
84 exp, _ := strconv.Atoi(expStr)
85 digits := strings.TrimRight(strings.Replace(mant, ".", "", 1), "0")
86 if digits == "" {
87 digits = "0"
88 }
89 if exp >= 15 || exp < -4 {
90 out := digits[:1]
91 if len(digits) > 1 {
92 out += "." + digits[1:]
93 }
94 esign := "+"
95 if exp < 0 {
96 esign = "-"
97 exp = -exp
98 }
99 e := strconv.Itoa(exp)
100 if len(e) < 2 {
101 e = "0" + e
102 }
103 return sign + out + "E" + esign + e
104 }
105 if exp < 0 {
106 return sign + "0." + strings.Repeat("0", -exp-1) + digits
107 }
108 if len(digits) <= exp+1 {
109 return sign + digits + strings.Repeat("0", exp+1-len(digits))
110 }
111 return sign + digits[:exp+1] + "." + digits[exp+1:]
112}