Skip to content

Commit

Permalink
number: added number package
Browse files Browse the repository at this point in the history
provides more styles and more control over
formatting than fmt.

Change-Id: Ib9d2c27db14804af911556e3caa8b35fddd32fce
Reviewed-on: https://go-review.googlesource.com/59812
Run-TryBot: Marcel van Lohuizen <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
Reviewed-by: Nigel Tao <[email protected]>
  • Loading branch information
mpvl committed Sep 12, 2017
1 parent bd91bbf commit d406163
Show file tree
Hide file tree
Showing 7 changed files with 652 additions and 0 deletions.
29 changes: 29 additions & 0 deletions number/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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 formats numbers according to the customs of different locales.
//
// The number formats of this package allow for greater formatting flexibility
// than passing values to message.Printf calls as is. It currently supports the
// builtin Go types and anything that implements the Convert interface
// (currently internal).
//
// p := message.NewPrinter(language.English)
//
// p.Printf("%v bottles of beer on the wall.", number.Decimal(1234))
// // Prints: 1,234 bottles of beer on the wall.
//
// p.Printf("%v of gophers lose too much fur", number.Percent(0.12))
// // Prints: 12% of gophers lose too much fur.
//
// p := message.NewPrinter(language.Dutch)
//
// p.Printf("There are %v bikes per household.", number.Decimal(1.2))
// // Prints: Er zijn 1,2 fietsen per huishouden.
//
// Provided that the printed translation is available.
//
// The width and scale specified in the formatting directives override the
// configuration of the formatter.
package number
28 changes: 28 additions & 0 deletions number/examples_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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_test

import (
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/text/number"
)

func ExampleMaxIntegerDigits() {
const year = 1999
p := message.NewPrinter(language.English)
p.Println("Year:", number.Decimal(year, number.MaxIntegerDigits(2)))

// Output:
// Year: 99
}

func ExampleIncrementString() {
p := message.NewPrinter(language.English)

p.Println(number.Decimal(1.33, number.IncrementString("0.50")))

// Output: 1.50
}
106 changes: 106 additions & 0 deletions number/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// 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 (
"fmt"
"strings"

"golang.org/x/text/feature/plural"
"golang.org/x/text/internal/format"
"golang.org/x/text/internal/number"
"golang.org/x/text/language"
)

// A FormatFunc formates a number.
type FormatFunc func(x interface{}, opts ...Option) Formatter

// NewFormat creates a FormatFunc based on another FormatFunc and new options.
// Use NewFormat to cash the creation of formatters.
func NewFormat(format FormatFunc, opts ...Option) FormatFunc {
o := *format(nil).options
n := len(o.options)
o.options = append(o.options[:n:n], opts...)
return func(x interface{}, opts ...Option) Formatter {
return newFormatter(&o, opts, x)
}
}

type options struct {
verbs string
initFunc initFunc
options []Option
pluralFunc func(t language.Tag, scale int) (f plural.Form, n int)
}

type optionFlag uint16

const (
hasScale optionFlag = 1 << iota
hasPrecision
noSeparator
exact
)

type initFunc func(f *number.Formatter, t language.Tag)

func newFormatter(o *options, opts []Option, value interface{}) Formatter {
if len(opts) > 0 {
n := *o
n.options = opts
o = &n
}
return Formatter{o, value}
}

func newOptions(verbs string, f initFunc) *options {
return &options{verbs: verbs, initFunc: f}
}

type Formatter struct {
*options
value interface{}
}

// Format implements format.Formatter. It is for internal use only for now.
func (f Formatter) Format(state format.State, verb rune) {
// TODO: consider implementing fmt.Formatter instead and using the following
// piece of code. This allows numbers to be rendered mostly as expected
// when using fmt. But it may get weird with the spellout options and we
// may need more of format.State over time.
// lang := language.Und
// if s, ok := state.(format.State); ok {
// lang = s.Language()
// }

lang := state.Language()
if !strings.Contains(f.verbs, string(verb)) {
fmt.Fprintf(state, "%%!%s(%T=%v)", string(verb), f.value, f.value)
return
}
var p number.Formatter
f.initFunc(&p, lang)
for _, o := range f.options.options {
o(lang, &p)
}
if w, ok := state.Width(); ok {
p.FormatWidth = uint16(w)
}
if prec, ok := state.Precision(); ok {
switch verb {
case 'd':
p.SetScale(0)
case 'f':
p.SetScale(prec)
case 'e':
p.SetPrecision(prec + 1)
case 'g':
p.SetPrecision(prec)
}
}
var d number.Decimal
d.Convert(p.RoundingContext, f.value)
state.Write(p.Format(nil, &d))
}
45 changes: 45 additions & 0 deletions number/format_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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 (
"testing"

"golang.org/x/text/language"
"golang.org/x/text/message"
)

func TestWrongVerb(t *testing.T) {
testCases := []struct {
f Formatter
fmt string
want string
}{{
f: Decimal(12),
fmt: "%e",
want: "%!e(int=12)",
}, {
f: Scientific(12),
fmt: "%f",
want: "%!f(int=12)",
}, {
f: Engineering(12),
fmt: "%f",
want: "%!f(int=12)",
}, {
f: Percent(12),
fmt: "%e",
want: "%!e(int=12)",
}}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
tag := language.Und
got := message.NewPrinter(tag).Sprintf(tc.fmt, tc.f)
if got != tc.want {
t.Errorf("got %q; want %q", got, tc.want)
}
})
}
}
77 changes: 77 additions & 0 deletions number/number.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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

// TODO:
// p.Printf("The gauge was at %v.", number.Spell(number.Percent(23)))
// // Prints: The gauge was at twenty-three percent.
//
// p.Printf("From here to %v!", number.Spell(math.Inf()))
// // Prints: From here to infinity!
//

import (
"golang.org/x/text/internal/number"
)

const (
decimalVerbs = "vfgd"
scientificVerbs = "veg"
)

// Decimal represents a number as a floating point decimal.
func Decimal(x interface{}, opts ...Option) Formatter {
return newFormatter(decimalOptions, opts, x)
}

var decimalOptions = newOptions(decimalVerbs, (*number.Formatter).InitDecimal)

// Scientific prints a values in scientific format.
func Scientific(x interface{}, opts ...Option) Formatter {
return newFormatter(scientificOptions, opts, x)
}

var scientificOptions = newOptions(scientificVerbs, (*number.Formatter).InitScientific)

// Engineering formats a number using engineering notation, which is like
// scientific notation, but with the exponent normalized to multiples of 3.
func Engineering(x interface{}, opts ...Option) Formatter {
return newFormatter(engineeringOptions, opts, x)
}

var engineeringOptions = newOptions(scientificVerbs, (*number.Formatter).InitEngineering)

// Percent formats a number as a percentage. A value of 1.0 means 100%.
func Percent(x interface{}, opts ...Option) Formatter {
return newFormatter(percentOptions, opts, x)
}

var percentOptions = newOptions(decimalVerbs, (*number.Formatter).InitPercent)

// PerMille formats a number as a per mille indication. A value of 1.0 means
// 1000‰.
func PerMille(x interface{}, opts ...Option) Formatter {
return newFormatter(perMilleOptions, opts, x)
}

var perMilleOptions = newOptions(decimalVerbs, (*number.Formatter).InitPerMille)

// TODO:
// - Shortest: akin to verb 'g' of 'G'
//
// TODO: RBNF forms:
// - Compact: 1M 3.5T
// - CompactBinary: 1Mi 3.5Ti
// - Long: 1 million
// - Ordinal:
// - Roman: MCMIIXX
// - RomanSmall: mcmiixx
// - Text: numbers as it typically appears in running text, allowing
// language-specific choices for when to use numbers and when to use words.
// - Spell?: spelled-out number. Maybe just allow as an option?

// NOTE: both spelled-out numbers and ordinals, to render correctly, need
// detailed linguistic information from the translated string into which they
// are substituted. We will need to implement that first.
Loading

0 comments on commit d406163

Please sign in to comment.