github.com/go-xe2/third@v1.0.3/golang.org/x/text/internal/number/decimal_test.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package number
     6  
     7  import (
     8  	"fmt"
     9  	"math"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  )
    14  
    15  func mkfloat(num string) float64 {
    16  	u, _ := strconv.ParseUint(num, 10, 32)
    17  	return float64(u)
    18  }
    19  
    20  // mkdec creates a decimal from a string. All ASCII digits are converted to
    21  // digits in the decimal. The dot is used to indicate the scale by which the
    22  // digits are shifted. Numbers may have an additional exponent or be the special
    23  // value NaN, Inf, or -Inf.
    24  func mkdec(num string) (d Decimal) {
    25  	var r RoundingContext
    26  	d.Convert(r, dec(num))
    27  	return
    28  }
    29  
    30  type dec string
    31  
    32  func (s dec) Convert(d *Decimal, _ RoundingContext) {
    33  	num := string(s)
    34  	if num[0] == '-' {
    35  		d.Neg = true
    36  		num = num[1:]
    37  	}
    38  	switch num {
    39  	case "NaN":
    40  		d.NaN = true
    41  		return
    42  	case "Inf":
    43  		d.Inf = true
    44  		return
    45  	}
    46  	if p := strings.IndexAny(num, "eE"); p != -1 {
    47  		i64, err := strconv.ParseInt(num[p+1:], 10, 32)
    48  		if err != nil {
    49  			panic(err)
    50  		}
    51  		d.Exp = int32(i64)
    52  		num = num[:p]
    53  	}
    54  	if p := strings.IndexByte(num, '.'); p != -1 {
    55  		d.Exp += int32(p)
    56  		num = num[:p] + num[p+1:]
    57  	} else {
    58  		d.Exp += int32(len(num))
    59  	}
    60  	d.Digits = []byte(num)
    61  	for i := range d.Digits {
    62  		d.Digits[i] -= '0'
    63  	}
    64  	*d = d.normalize()
    65  }
    66  
    67  func byteNum(s string) []byte {
    68  	b := make([]byte, len(s))
    69  	for i := 0; i < len(s); i++ {
    70  		if c := s[i]; '0' <= c && c <= '9' {
    71  			b[i] = s[i] - '0'
    72  		} else {
    73  			b[i] = s[i] - 'a' + 10
    74  		}
    75  	}
    76  	return b
    77  }
    78  
    79  func strNum(s string) string {
    80  	return string(byteNum(s))
    81  }
    82  
    83  func TestDecimalString(t *testing.T) {
    84  	for _, test := range []struct {
    85  		x    Decimal
    86  		want string
    87  	}{
    88  		{want: "0"},
    89  		{Decimal{digits: digits{Digits: nil, Exp: 1000}}, "0"}, // exponent of 1000 is ignored
    90  		{Decimal{digits: digits{Digits: byteNum("12345"), Exp: 0}}, "0.12345"},
    91  		{Decimal{digits: digits{Digits: byteNum("12345"), Exp: -3}}, "0.00012345"},
    92  		{Decimal{digits: digits{Digits: byteNum("12345"), Exp: +3}}, "123.45"},
    93  		{Decimal{digits: digits{Digits: byteNum("12345"), Exp: +10}}, "1234500000"},
    94  	} {
    95  		if got := test.x.String(); got != test.want {
    96  			t.Errorf("%v == %q; want %q", test.x, got, test.want)
    97  		}
    98  	}
    99  }
   100  
   101  func TestRounding(t *testing.T) {
   102  	testCases := []struct {
   103  		x string
   104  		n int
   105  		// modes is the result for modes. Signs are left out of the result.
   106  		// The results are stored in the following order:
   107  		// zero, negInf
   108  		// nearZero, nearEven, nearAway
   109  		// away, posInf
   110  		modes [numModes]string
   111  	}{
   112  		{"0", 1, [numModes]string{
   113  			"0", "0",
   114  			"0", "0", "0",
   115  			"0", "0"}},
   116  		{"1", 1, [numModes]string{
   117  			"1", "1",
   118  			"1", "1", "1",
   119  			"1", "1"}},
   120  		{"5", 1, [numModes]string{
   121  			"5", "5",
   122  			"5", "5", "5",
   123  			"5", "5"}},
   124  		{"15", 1, [numModes]string{
   125  			"10", "10",
   126  			"10", "20", "20",
   127  			"20", "20"}},
   128  		{"45", 1, [numModes]string{
   129  			"40", "40",
   130  			"40", "40", "50",
   131  			"50", "50"}},
   132  		{"95", 1, [numModes]string{
   133  			"90", "90",
   134  			"90", "100", "100",
   135  			"100", "100"}},
   136  
   137  		{"12344999", 4, [numModes]string{
   138  			"12340000", "12340000",
   139  			"12340000", "12340000", "12340000",
   140  			"12350000", "12350000"}},
   141  		{"12345000", 4, [numModes]string{
   142  			"12340000", "12340000",
   143  			"12340000", "12340000", "12350000",
   144  			"12350000", "12350000"}},
   145  		{"12345001", 4, [numModes]string{
   146  			"12340000", "12340000",
   147  			"12350000", "12350000", "12350000",
   148  			"12350000", "12350000"}},
   149  		{"12345100", 4, [numModes]string{
   150  			"12340000", "12340000",
   151  			"12350000", "12350000", "12350000",
   152  			"12350000", "12350000"}},
   153  		{"23454999", 4, [numModes]string{
   154  			"23450000", "23450000",
   155  			"23450000", "23450000", "23450000",
   156  			"23460000", "23460000"}},
   157  		{"23455000", 4, [numModes]string{
   158  			"23450000", "23450000",
   159  			"23450000", "23460000", "23460000",
   160  			"23460000", "23460000"}},
   161  		{"23455001", 4, [numModes]string{
   162  			"23450000", "23450000",
   163  			"23460000", "23460000", "23460000",
   164  			"23460000", "23460000"}},
   165  		{"23455100", 4, [numModes]string{
   166  			"23450000", "23450000",
   167  			"23460000", "23460000", "23460000",
   168  			"23460000", "23460000"}},
   169  
   170  		{"99994999", 4, [numModes]string{
   171  			"99990000", "99990000",
   172  			"99990000", "99990000", "99990000",
   173  			"100000000", "100000000"}},
   174  		{"99995000", 4, [numModes]string{
   175  			"99990000", "99990000",
   176  			"99990000", "100000000", "100000000",
   177  			"100000000", "100000000"}},
   178  		{"99999999", 4, [numModes]string{
   179  			"99990000", "99990000",
   180  			"100000000", "100000000", "100000000",
   181  			"100000000", "100000000"}},
   182  
   183  		{"12994999", 4, [numModes]string{
   184  			"12990000", "12990000",
   185  			"12990000", "12990000", "12990000",
   186  			"13000000", "13000000"}},
   187  		{"12995000", 4, [numModes]string{
   188  			"12990000", "12990000",
   189  			"12990000", "13000000", "13000000",
   190  			"13000000", "13000000"}},
   191  		{"12999999", 4, [numModes]string{
   192  			"12990000", "12990000",
   193  			"13000000", "13000000", "13000000",
   194  			"13000000", "13000000"}},
   195  	}
   196  	modes := []RoundingMode{
   197  		ToZero, ToNegativeInf,
   198  		ToNearestZero, ToNearestEven, ToNearestAway,
   199  		AwayFromZero, ToPositiveInf,
   200  	}
   201  	for _, tc := range testCases {
   202  		// Create negative counterpart tests: the sign is reversed and
   203  		// ToPositiveInf and ToNegativeInf swapped.
   204  		negModes := tc.modes
   205  		negModes[1], negModes[6] = negModes[6], negModes[1]
   206  		for i, res := range negModes {
   207  			negModes[i] = "-" + res
   208  		}
   209  		for i, m := range modes {
   210  			t.Run(fmt.Sprintf("x:%s/n:%d/%s", tc.x, tc.n, m), func(t *testing.T) {
   211  				d := mkdec(tc.x)
   212  				d.round(m, tc.n)
   213  				if got := d.String(); got != tc.modes[i] {
   214  					t.Errorf("pos decimal: got %q; want %q", d.String(), tc.modes[i])
   215  				}
   216  
   217  				mult := math.Pow(10, float64(len(tc.x)-tc.n))
   218  				f := mkfloat(tc.x)
   219  				f = m.roundFloat(f/mult) * mult
   220  				if got := fmt.Sprintf("%.0f", f); got != tc.modes[i] {
   221  					t.Errorf("pos float: got %q; want %q", got, tc.modes[i])
   222  				}
   223  
   224  				// Test the negative case. This is the same as the positive
   225  				// case, but with ToPositiveInf and ToNegativeInf swapped.
   226  				d = mkdec(tc.x)
   227  				d.Neg = true
   228  				d.round(m, tc.n)
   229  				if got, want := d.String(), negModes[i]; got != want {
   230  					t.Errorf("neg decimal: got %q; want %q", d.String(), want)
   231  				}
   232  
   233  				f = -mkfloat(tc.x)
   234  				f = m.roundFloat(f/mult) * mult
   235  				if got := fmt.Sprintf("%.0f", f); got != negModes[i] {
   236  					t.Errorf("neg float: got %q; want %q", got, negModes[i])
   237  				}
   238  			})
   239  		}
   240  	}
   241  }
   242  
   243  func TestConvert(t *testing.T) {
   244  	scale2 := RoundingContext{}
   245  	scale2.SetScale(2)
   246  	scale2away := RoundingContext{Mode: AwayFromZero}
   247  	scale2away.SetScale(2)
   248  	inc0_05 := RoundingContext{Increment: 5, IncrementScale: 2}
   249  	inc0_05.SetScale(2)
   250  	inc50 := RoundingContext{Increment: 50}
   251  	prec3 := RoundingContext{}
   252  	prec3.SetPrecision(3)
   253  	roundShift := RoundingContext{DigitShift: 2, MaxFractionDigits: 2}
   254  	testCases := []struct {
   255  		x   interface{}
   256  		rc  RoundingContext
   257  		out string
   258  	}{
   259  		{-0.001, scale2, "-0.00"},
   260  		{0.1234, prec3, "0.123"},
   261  		{1234.0, prec3, "1230"},
   262  		{1.2345e10, prec3, "12300000000"},
   263  
   264  		{int8(-34), scale2, "-34"},
   265  		{int16(-234), scale2, "-234"},
   266  		{int32(-234), scale2, "-234"},
   267  		{int64(-234), scale2, "-234"},
   268  		{int(-234), scale2, "-234"},
   269  		{uint8(234), scale2, "234"},
   270  		{uint16(234), scale2, "234"},
   271  		{uint32(234), scale2, "234"},
   272  		{uint64(234), scale2, "234"},
   273  		{uint(234), scale2, "234"},
   274  		{-1e9, scale2, "-1000000000.00"},
   275  		// The following two causes this result to have a lot of digits:
   276  		// 1) 0.234 cannot be accurately represented as a float64, and
   277  		// 2) as strconv does not support the rounding AwayFromZero, Convert
   278  		//    leaves the rounding to caller.
   279  		{0.234, scale2away,
   280  			"0.2340000000000000135447209004269097931683063507080078125"},
   281  
   282  		{0.0249, inc0_05, "0.00"},
   283  		{0.025, inc0_05, "0.00"},
   284  		{0.0251, inc0_05, "0.05"},
   285  		{0.03, inc0_05, "0.05"},
   286  		{0.049, inc0_05, "0.05"},
   287  		{0.05, inc0_05, "0.05"},
   288  		{0.051, inc0_05, "0.05"},
   289  		{0.0749, inc0_05, "0.05"},
   290  		{0.075, inc0_05, "0.10"},
   291  		{0.0751, inc0_05, "0.10"},
   292  		{324, inc50, "300"},
   293  		{325, inc50, "300"},
   294  		{326, inc50, "350"},
   295  		{349, inc50, "350"},
   296  		{350, inc50, "350"},
   297  		{351, inc50, "350"},
   298  		{374, inc50, "350"},
   299  		{375, inc50, "400"},
   300  		{376, inc50, "400"},
   301  
   302  		// Here the scale is 2, but the digits get shifted left. As we use
   303  		// AppendFloat to do the rounding an exta 0 gets added.
   304  		{0.123, roundShift, "0.1230"},
   305  
   306  		{converter(3), scale2, "100"},
   307  
   308  		{math.Inf(1), inc50, "Inf"},
   309  		{math.Inf(-1), inc50, "-Inf"},
   310  		{math.NaN(), inc50, "NaN"},
   311  		{"clearly not a number", scale2, "NaN"},
   312  	}
   313  	for _, tc := range testCases {
   314  		var d Decimal
   315  		t.Run(fmt.Sprintf("%T:%v-%v", tc.x, tc.x, tc.rc), func(t *testing.T) {
   316  			d.Convert(tc.rc, tc.x)
   317  			if got := d.String(); got != tc.out {
   318  				t.Errorf("got %q; want %q", got, tc.out)
   319  			}
   320  		})
   321  	}
   322  }
   323  
   324  type converter int
   325  
   326  func (c converter) Convert(d *Decimal, r RoundingContext) {
   327  	d.Digits = append(d.Digits, 1, 0, 0)
   328  	d.Exp = 3
   329  }