You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

330 lines
8.6 KiB

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