diff --git a/builtins.go b/builtins.go index 31c3916..4523b11 100644 --- a/builtins.go +++ b/builtins.go @@ -16,6 +16,8 @@ import ( const ( maxLen = 1 << 20 maxDecimals = 1024 + // shortestDecimals prints the fewest digits that read back as the same float64. + shortestDecimals = -1 ) // builtins is the registry of {name(args)} functions. Derivations read the digits @@ -100,7 +102,7 @@ func chars(alphabet string) func([]string) callFn { const hexDigits = "0123456789abcdef" -// formatFloat prints v to dp decimals, -1 for the shortest form, and a zero unsigned. +// formatFloat prints v to dp decimals, or shortestDecimals, and a zero unsigned. func formatFloat(v float64, dp int) string { s := strconv.FormatFloat(v, 'f', dp, 64) if strings.HasPrefix(s, "-") && strings.Trim(s, "-0.") == "" { diff --git a/calc.go b/calc.go index 827fde1..3b5e497 100644 --- a/calc.go +++ b/calc.go @@ -187,7 +187,7 @@ func neverNumeric(n node) (text string, never bool) { // calcPrep parses the expression and decimals once, at compile time, and places each // operand name at the position expand will read it into. checkCalc proved both args -// valid, so no step here can fail; dp -1 prints the minimal form. +// valid, so no step here can fail. func calcPrep(args []string) callFn { expr, err := parseCalc(args[0]) if err != nil { // a nil AST would be a nil dereference per render, with no message @@ -204,12 +204,12 @@ func calcPrep(args []string) callFn { } } -// calcDecimals is a calc's decimals count, or -1 for the shortest form. +// calcDecimals is a calc's decimals count, or shortestDecimals where it names none. func calcDecimals(args []string) int { if len(args) == 2 { return atoi(args[1]) } - return -1 + return shortestDecimals } // indexVars replaces each operand name with its position in the values expand reads. diff --git a/format.go b/format.go index 42043b5..b448968 100644 --- a/format.go +++ b/format.go @@ -125,10 +125,14 @@ type builtin struct { // operands names the fields the call reads, which expand renders for it; nil // for a builtin that reads none. operands func(args []string) []string - // proveNumber proves what a call prints, token its body: the bounds of its number and the - // datatype of its text, prints or one inside it; nil for a builtin whose text is no number. + // proveNumber bounds the number a call's text reads as, token its body, and says which + // datatypes that text is not; set it where every render reads as a finite number, which + // makes the call a calc operand, and leave it nil otherwise. proveNumber func(token string, prints DataType, args []string) proven - prints DataType + // prints is the datatype every render's text is, which a typed column holds the call to: + // DataTypeString where it reads as a number but no column should type it, as digits' + // leading zeros; unset where proveNumber is nil. + prints DataType } // funcCall splits a "{token}" body shaped name(args) into its parts; ok is false diff --git a/todo.md b/todo.md index e53919f..9f08fd1 100644 --- a/todo.md +++ b/todo.md @@ -26,8 +26,6 @@ least modify. This round, in order: -- Name `printedNumber`'s `dp` cases, and say on `builtin` what `prints` and - `proveNumber` a new builtin sets: the extender copied `digits` by analogy. - Rename the names that mislead: `table.pinnedRow` is the row node, not a pinned row; `table.category` is a bare name beside the dotted `category` of `resolveLink`; `checkRenders` renders nothing; `fieldAlternation` is a plain `{name}` too; and diff --git a/value.go b/value.go index 0c73593..5ec2bf6 100644 --- a/value.go +++ b/value.go @@ -261,16 +261,23 @@ func bounded(lo, hi float64, integral bool) proven { func magnitude(v proven) float64 { return math.Max(math.Abs(v.lo), math.Abs(v.hi)) } -// printedNumber is what a token printing v to dp decimals holds: an integer when whole -// and within int64, else a number. +// printedNumber is what a token printing v to dp decimals holds. func printedNumber(token string, v proven, dp int) proven { - if dp >= 0 { - half, _ := strconv.ParseFloat("5e-"+strconv.Itoa(dp+1), 64) - v = proven{lo: v.lo - half, hi: v.hi + half, nonZero: math.Max(0, v.nonZero-half), integral: v.integral || dp == 0} - } - if dp != 0 && !(dp < 0 && v.integral) { + if dp == shortestDecimals { + if v.integral { // a whole value prints with no point + return printedInteger(token, v) + } return printing(token, DataTypeNumber, v) } + half, _ := strconv.ParseFloat("5e-"+strconv.Itoa(dp+1), 64) + v = proven{lo: v.lo - half, hi: v.hi + half, nonZero: math.Max(0, v.nonZero-half), integral: v.integral || dp == 0} + if dp == 0 { + return printedInteger(token, v) + } + return printing(token, DataTypeNumber, v) +} + +func printedInteger(token string, v proven) proven { v = printing(token, DataTypeInteger, v) if !(magnitude(v) < math.MaxInt64) { v.not[DataTypeInteger] = fmt.Sprintf("{%s} is not proven within int64", token)