1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package apd
16
17 import (
18 "bufio"
19 "bytes"
20 "flag"
21 "fmt"
22 "io"
23 "os"
24 "path/filepath"
25 "sort"
26 "strconv"
27 "strings"
28 "sync"
29 "testing"
30 "time"
31 )
32
33 const testDir = "testdata"
34
35 var (
36 flagFailFast = flag.Bool("fast", false, "stop work after first error; disables parallel testing")
37 flagIgnore = flag.Bool("ignore", false, "print ignore lines on errors")
38 flagNoParallel = flag.Bool("noparallel", false, "disables parallel testing")
39 flagTime = flag.Duration("time", 0, "interval at which to print long-running functions; 0 disables")
40 )
41
42 type TestCase struct {
43 Precision int
44 MaxExponent, MinExponent int
45 Rounding string
46 Extended, Clamp bool
47
48 ID string
49 Operation string
50 Operands []string
51 Result string
52 Conditions []string
53 }
54
55 func (tc TestCase) HasNull() bool {
56 if tc.Result == "#" {
57 return true
58 }
59 for _, o := range tc.Operands {
60 if o == "#" {
61 return true
62 }
63 }
64 return false
65 }
66
67 func (tc TestCase) SkipPrecision() bool {
68 switch tc.Operation {
69 case "tosci", "toeng", "apply":
70 return false
71 default:
72 return true
73 }
74 }
75
76 func ParseDecTest(r io.Reader) ([]TestCase, error) {
77 scanner := bufio.NewScanner(r)
78 tc := TestCase{
79 Extended: true,
80 }
81 var err error
82 var res []TestCase
83
84 for scanner.Scan() {
85 text := scanner.Text()
86
87 if strings.Contains(text, "#") {
88 continue
89 }
90 line := strings.Fields(strings.ToLower(text))
91 for i, t := range line {
92 if strings.HasPrefix(t, "--") {
93 line = line[:i]
94 break
95 }
96 }
97 if len(line) == 0 {
98 continue
99 }
100 if strings.HasSuffix(line[0], ":") {
101 if len(line) != 2 {
102 return nil, fmt.Errorf("expected 2 tokens, got %q", text)
103 }
104 switch directive := line[0]; directive[:len(directive)-1] {
105 case "precision":
106 tc.Precision, err = strconv.Atoi(line[1])
107 if err != nil {
108 return nil, err
109 }
110 case "maxexponent":
111 tc.MaxExponent, err = strconv.Atoi(line[1])
112 if err != nil {
113 return nil, err
114 }
115 case "minexponent":
116 tc.MinExponent, err = strconv.Atoi(line[1])
117 if err != nil {
118 return nil, err
119 }
120 case "rounding":
121 tc.Rounding = line[1]
122 case "version":
123
124 case "extended":
125 tc.Extended = line[1] == "1"
126 case "clamp":
127 tc.Clamp = line[1] == "1"
128 default:
129 return nil, fmt.Errorf("unsupported directive: %s", directive)
130 }
131 } else {
132 if len(line) < 5 {
133 return nil, fmt.Errorf("short test case line: %q", text)
134 }
135 tc.ID = line[0]
136 tc.Operation = line[1]
137 tc.Operands = nil
138 var ops []string
139 line = line[2:]
140 for i, o := range line {
141 if o == "->" {
142 tc.Operands = ops
143 line = line[i+1:]
144 break
145 }
146 o = cleanNumber(o)
147 ops = append(ops, o)
148 }
149 if tc.Operands == nil || len(line) < 1 {
150 return nil, fmt.Errorf("bad test case line: %q", text)
151 }
152 tc.Result = strings.ToUpper(cleanNumber(line[0]))
153 tc.Conditions = line[1:]
154 res = append(res, tc)
155 }
156 }
157 if err := scanner.Err(); err != nil {
158 return nil, err
159 }
160 return res, nil
161 }
162
163 func cleanNumber(s string) string {
164 if len(s) > 1 && s[0] == '\'' && s[len(s)-1] == '\'' {
165 s = s[1 : len(s)-1]
166 s = strings.Replace(s, `''`, `'`, -1)
167 } else if len(s) > 1 && s[0] == '"' && s[len(s)-1] == '"' {
168 s = s[1 : len(s)-1]
169 s = strings.Replace(s, `""`, `"`, -1)
170 }
171 return s
172 }
173
174
175
176 func ReadDir(dirname string) ([]os.FileInfo, error) {
177 f, err := os.Open(dirname)
178 if err != nil {
179 return nil, err
180 }
181 list, err := f.Readdir(-1)
182 f.Close()
183 if err != nil {
184 return nil, err
185 }
186 sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
187 return list, nil
188 }
189
190 func TestParseDecTest(t *testing.T) {
191 files, err := ReadDir(testDir)
192 if err != nil {
193 t.Fatal(err)
194 }
195 for _, fi := range files {
196 t.Run(fi.Name(), func(t *testing.T) {
197 f, err := os.Open(filepath.Join(testDir, fi.Name()))
198 if err != nil {
199 t.Fatal(err)
200 }
201 defer f.Close()
202 _, err = ParseDecTest(f)
203 if err != nil {
204 t.Fatal(err)
205 }
206 })
207 }
208 }
209
210 var GDAfiles = []string{
211 "abs",
212 "add",
213 "base",
214 "compare",
215 "comparetotal",
216 "divide",
217 "divideint",
218 "exp",
219 "ln",
220 "log10",
221 "minus",
222 "multiply",
223 "plus",
224 "power",
225 "powersqrt",
226 "quantize",
227 "randoms",
228 "reduce",
229 "remainder",
230 "rounding",
231 "squareroot",
232 "subtract",
233 "tointegral",
234 "tointegralx",
235
236
237 "cuberoot-apd",
238 }
239
240 func TestGDA(t *testing.T) {
241 var buf bytes.Buffer
242 fmt.Fprintf(&buf, "%10s%8s%8s%8s%8s%8s%8s\n", "name", "total", "success", "fail", "ignore", "skip", "missing")
243 for _, fname := range GDAfiles {
244 succeed := t.Run(fname, func(t *testing.T) {
245 path, tcs := readGDA(t, fname)
246 gdaTest(t, path, tcs)
247 })
248 if !succeed && *flagFailFast {
249 break
250 }
251 }
252 }
253
254 func (tc TestCase) Run(c *Context, done chan error, d, x, y *Decimal) (res Condition, err error) {
255 switch tc.Operation {
256 case "abs":
257 res, err = c.Abs(d, x)
258 case "add":
259 res, err = c.Add(d, x, y)
260 case "compare":
261 res, err = c.Cmp(d, x, y)
262 case "cuberoot":
263 res, err = c.Cbrt(d, x)
264 case "divide":
265 res, err = c.Quo(d, x, y)
266 case "divideint":
267 res, err = c.QuoInteger(d, x, y)
268 case "exp":
269 res, err = c.Exp(d, x)
270 case "ln":
271 res, err = c.Ln(d, x)
272 case "log10":
273 res, err = c.Log10(d, x)
274 case "minus":
275 res, err = c.Neg(d, x)
276 case "multiply":
277 res, err = c.Mul(d, x, y)
278 case "plus":
279 res, err = c.Add(d, x, decimalZero)
280 case "power":
281 res, err = c.Pow(d, x, y)
282 case "quantize":
283 res, err = c.Quantize(d, x, y.Exponent)
284 case "reduce":
285 _, res, err = c.Reduce(d, x)
286 case "remainder":
287 res, err = c.Rem(d, x, y)
288 case "squareroot":
289 res, err = c.Sqrt(d, x)
290 case "subtract":
291 res, err = c.Sub(d, x, y)
292 case "tointegral":
293 res, err = c.RoundToIntegralValue(d, x)
294 case "tointegralx":
295 res, err = c.RoundToIntegralExact(d, x)
296
297
298 case "comparetotal":
299 x.CmpTotal(y)
300 case "tosci":
301 _ = x.String()
302
303 default:
304 done <- fmt.Errorf("unknown operation: %s", tc.Operation)
305 }
306 return
307 }
308
309
310
311
312 func BenchmarkGDA(b *testing.B) {
313 for _, fname := range GDAfiles {
314 b.Run(fname, func(b *testing.B) {
315 type benchCase struct {
316 tc TestCase
317 ctx *Context
318 ops [2]*Decimal
319 }
320 _, tcs := readGDA(b, fname)
321 bcs := make([]benchCase, 0, len(tcs))
322 Loop:
323 for _, tc := range tcs {
324 if GDAignore[tc.ID] || tc.Result == "?" || tc.HasNull() {
325 continue
326 }
327 switch tc.Operation {
328 case "apply", "toeng":
329 continue
330 }
331 bc := benchCase{
332 tc: tc,
333 ctx: tc.Context(b),
334 }
335 for i, o := range tc.Operands {
336 d, _, err := NewFromString(o)
337 if err != nil {
338 continue Loop
339 }
340 bc.ops[i] = d
341 }
342 bcs = append(bcs, bc)
343 }
344
345
346 op1s := make([]Decimal, len(bcs))
347 op2s := make([]Decimal, len(bcs))
348 res := make([]Decimal, b.N*len(bcs))
349 for i, bc := range bcs {
350 op1s[i].Set(bc.ops[0])
351 if bc.ops[1] != nil {
352 op2s[i].Set(bc.ops[1])
353 }
354 }
355
356 b.ResetTimer()
357 for i := 0; i < b.N; i++ {
358 for j, bc := range bcs {
359
360 _, _ = bc.tc.Run(bc.ctx, nil, &res[i*len(bcs)+j], &op1s[j], &op2s[j])
361 }
362 }
363 })
364 }
365 }
366
367 func readGDA(t testing.TB, name string) (string, []TestCase) {
368 path := filepath.Join(testDir, name+".decTest")
369 f, err := os.Open(path)
370 if err != nil {
371 t.Fatal(err)
372 }
373 defer f.Close()
374 tcs, err := ParseDecTest(f)
375 if err != nil {
376 t.Fatal(err)
377 }
378 return path, tcs
379 }
380
381 func (tc TestCase) Context(t testing.TB) *Context {
382 rounding := Rounder(tc.Rounding)
383 if _, ok := roundings[rounding]; !ok {
384 t.Fatalf("unsupported rounding mode %s", tc.Rounding)
385 }
386 c := &Context{
387 Precision: uint32(tc.Precision),
388 MaxExponent: int32(tc.MaxExponent),
389 MinExponent: int32(tc.MinExponent),
390 Rounding: rounding,
391 Traps: 0,
392 }
393 return c
394 }
395
396 func gdaTest(t *testing.T, path string, tcs []TestCase) {
397 for _, tc := range tcs {
398 tc := tc
399 succeed := t.Run(tc.ID, func(t *testing.T) {
400 if *flagTime > 0 {
401 timeDone := make(chan struct{}, 1)
402 go func() {
403 start := time.Now()
404 for {
405 select {
406 case <-timeDone:
407 return
408 case <-time.After(*flagTime):
409 fmt.Println(tc.ID, "running for", time.Since(start))
410 }
411 }
412 }()
413 defer func() { timeDone <- struct{}{} }()
414 }
415 defer func() {
416 if t.Failed() {
417 if *flagIgnore {
418 tc.PrintIgnore()
419 }
420 }
421 }()
422 if GDAignore[tc.ID] {
423 t.Skip("ignored")
424 }
425 if tc.HasNull() {
426 t.Skip("has null")
427 }
428 switch tc.Operation {
429 case "toeng", "apply":
430 t.Skip("unsupported")
431 }
432 if !*flagNoParallel && !*flagFailFast {
433 t.Parallel()
434 }
435
436 t.Logf("%s:/^%s ", path, tc.ID)
437 t.Logf("%s %s = %s (%s)", tc.Operation, strings.Join(tc.Operands, " "), tc.Result, strings.Join(tc.Conditions, " "))
438 t.Logf("prec: %d, round: %s, Emax: %d, Emin: %d", tc.Precision, tc.Rounding, tc.MaxExponent, tc.MinExponent)
439 operands := make([]*Decimal, 2)
440 c := tc.Context(t)
441 var res, opres Condition
442 opctx := c
443 if tc.SkipPrecision() {
444 opctx = opctx.WithPrecision(1000)
445 opctx.MaxExponent = MaxExponent
446 opctx.MinExponent = MinExponent
447 }
448 for i, o := range tc.Operands {
449 d, ores, err := opctx.NewFromString(o)
450 expectError := tc.Result == "NAN" && strings.Join(tc.Conditions, "") == "conversion_syntax"
451 if err != nil {
452 if expectError {
453
454 return
455 }
456 switch tc.Operation {
457 case "tosci":
458
459 if strings.Contains(err.Error(), "value out of range") {
460 return
461 }
462 }
463 testExponentError(t, err)
464 if tc.Result == "?" {
465 return
466 }
467 t.Logf("%v, %v, %v", tc.Result, tc.Conditions, tc.Operation)
468 t.Fatalf("operand %d: %s: %+v", i, o, err)
469 } else if expectError {
470 t.Fatalf("expected error, got %s", d)
471 }
472 operands[i] = d
473 opres |= ores
474 }
475 switch tc.Operation {
476 case "power":
477 tmp := new(Decimal).Abs(operands[1])
478
479 if tmp.Cmp(New(MaxExponent, 0)) >= 0 {
480 t.Skip("x ** large y")
481 }
482 if tmp.Cmp(New(int64(c.MaxExponent), 0)) >= 0 {
483 t.Skip("x ** large y")
484 }
485 case "quantize":
486 if operands[1].Form != Finite {
487 t.Skip("quantize requires finite second operand")
488 }
489 }
490 var s string
491
492 d := &Decimal{
493 Form: -2,
494 Negative: true,
495 Exponent: -6437897,
496 }
497
498
499 var d1, d2 *Decimal
500 d.Coeff.SetInt64(9221)
501 start := time.Now()
502 defer func() {
503 t.Logf("duration: %s", time.Since(start))
504 }()
505
506 done := make(chan error, 1)
507 var err error
508 go func() {
509 switch tc.Operation {
510 case "tosci":
511 s = operands[0].String()
512
513 if !tc.Extended && operands[0].IsZero() {
514 s = "0"
515 }
516
517 d.Set(operands[0])
518
519 d1 = d
520 case "comparetotal":
521 var c int
522 c = operands[0].CmpTotal(operands[1])
523 d.SetInt64(int64(c))
524 default:
525 var wg sync.WaitGroup
526 wg.Add(2)
527
528
529 go func() {
530 d1 = new(Decimal).Set(operands[0])
531 tc.Run(c, done, d1, d1, operands[1])
532 wg.Done()
533 }()
534 go func() {
535 if operands[1] != nil {
536 d2 = new(Decimal).Set(operands[1])
537 tc.Run(c, done, d2, operands[0], d2)
538 }
539 wg.Done()
540 }()
541 res, err = tc.Run(c, done, d, operands[0], operands[1])
542 wg.Wait()
543 }
544 done <- nil
545 }()
546 select {
547 case err := <-done:
548 if err != nil {
549 t.Fatal(err)
550 }
551 case <-time.After(time.Second * 20):
552 t.Fatalf("timeout")
553 }
554 if d.Coeff.Sign() < 0 {
555 t.Fatalf("negative coeff: %s", d.Coeff.String())
556 }
557
558 if d.Form < 0 {
559 t.Fatalf("unexpected form: %#v", d)
560 }
561
562 for i, o := range tc.Operands {
563 v := newDecimal(t, opctx, o)
564 if v.CmpTotal(operands[i]) != 0 {
565 t.Fatalf("operand %d changed from %s to %s", i, o, operands[i])
566 }
567 }
568
569 if d1 != nil && d.CmpTotal(d1) != 0 {
570 t.Errorf("first operand as result mismatch: got %s, expected %s", d1, d)
571 }
572 if d2 != nil && d.CmpTotal(d2) != 0 {
573 t.Errorf("second operand as result mismatch: got %s, expected %s", d2, d)
574 }
575 if !GDAignoreFlags[tc.ID] {
576 var rcond Condition
577 for _, cond := range tc.Conditions {
578 switch cond {
579 case "underflow":
580 rcond |= Underflow
581 case "inexact":
582 rcond |= Inexact
583 case "overflow":
584 rcond |= Overflow
585 case "subnormal":
586 rcond |= Subnormal
587 case "division_undefined":
588 rcond |= DivisionUndefined
589 case "division_by_zero":
590 rcond |= DivisionByZero
591 case "division_impossible":
592 rcond |= DivisionImpossible
593 case "invalid_operation":
594 rcond |= InvalidOperation
595 case "rounded":
596 rcond |= Rounded
597 case "clamped":
598 rcond |= Clamped
599
600 case "invalid_context":
601
602
603 default:
604 t.Fatalf("unknown condition: %s", cond)
605 }
606 }
607
608 switch tc.Operation {
609 case "tosci":
610
611 res |= opres
612 }
613
614 t.Logf("want flags (%d): %s", rcond, rcond)
615 t.Logf("have flags (%d): %s", res, res)
616
617
618
619
620
621 res &= ^Rounded
622 rcond &= ^Rounded
623
624 switch tc.Operation {
625 case "log10":
626
627
628
629
630 rcond &= ^Inexact
631 res &= ^Inexact
632 }
633
634
635 res &= ^SystemOverflow
636 res &= ^SystemUnderflow
637
638 if (res.Overflow() || res.Underflow()) && (strings.HasPrefix(tc.ID, "rpow") ||
639 strings.HasPrefix(tc.ID, "powr")) {
640 t.Skip("overflow")
641 }
642
643
644 if tc.Result == "?" {
645 rcond &= ^Clamped
646 res &= ^Clamped
647 }
648
649 if rcond != res {
650 if tc.Operation == "power" && (res.Overflow() || res.Underflow()) {
651 t.Skip("power overflow")
652 }
653 t.Logf("got: %s (%#v)", d, d)
654 t.Logf("error: %+v", err)
655 t.Errorf("expected flags %q (%d); got flags %q (%d)", rcond, rcond, res, res)
656 }
657 }
658
659 if tc.Result == "?" {
660 if err != nil {
661 return
662 }
663 t.Fatalf("expected error, got %s", d)
664 }
665 if err != nil {
666 testExponentError(t, err)
667 if tc.Operation == "power" && (res.Overflow() || res.Underflow()) {
668 t.Skip("power overflow")
669 }
670 t.Fatalf("%+v", err)
671 }
672 switch tc.Operation {
673 case "tosci", "toeng":
674 if strings.HasPrefix(tc.Result, "-NAN") {
675 tc.Result = "-NaN"
676 }
677 if strings.HasPrefix(tc.Result, "-SNAN") {
678 tc.Result = "-sNaN"
679 }
680 if strings.HasPrefix(tc.Result, "NAN") {
681 tc.Result = "NaN"
682 }
683 if strings.HasPrefix(tc.Result, "SNAN") {
684 tc.Result = "sNaN"
685 }
686 expected := tc.Result
687
688
689 if pos, neg := strings.HasPrefix(expected, "0E-"), strings.HasPrefix(expected, "-0E-"); pos || neg {
690 startIdx := 3
691 if neg {
692 startIdx = 4
693 }
694 p, err := strconv.ParseInt(expected[startIdx:], 10, 64)
695 if err != nil {
696 t.Fatalf("unexpected error converting int: %v", err)
697 }
698 if p <= -lowestZeroNegativeCoefficientCockroach {
699 expected = ""
700 if neg {
701 expected = "-"
702 }
703 expected += "0." + strings.Repeat("0", int(p))
704 }
705 }
706 if !strings.EqualFold(s, expected) {
707 t.Fatalf("expected %s, got %s", expected, s)
708 }
709 return
710 }
711 r := newDecimal(t, testCtx, tc.Result)
712 var equal bool
713 if d.Form == Finite {
714
715 equal = d.Cmp(r) == 0 && d.Negative == r.Negative
716 } else {
717 equal = d.CmpTotal(r) == 0
718 }
719 if !equal {
720 t.Logf("want: %s", tc.Result)
721 t.Logf("got: %s (%#v)", d, d)
722
723 switch tc.Operation {
724 case "exp", "ln", "log10", "power":
725 nc := c.WithPrecision(0)
726 nc.Sub(d, d, r)
727 if d.Coeff.Cmp(bigOne) == 0 {
728 t.Logf("pass: within 1ulp: %s, %s", d, r)
729 return
730 }
731 }
732 t.Fatalf("unexpected result")
733 } else {
734 t.Logf("got: %s (%#v)", d, d)
735 }
736 })
737 if !succeed {
738 if *flagFailFast {
739 break
740 }
741 }
742 }
743 }
744
745 func (tc TestCase) PrintIgnore() {
746 fmt.Printf(" \"%s\": true,\n", tc.ID)
747 }
748
749 var GDAignore = map[string]bool{
750
751 "expx901": true,
752 "expx902": true,
753 "expx903": true,
754 "expx905": true,
755 "lnx901": true,
756 "lnx902": true,
757 "lnx903": true,
758 "lnx905": true,
759 "logx901": true,
760 "logx902": true,
761 "logx903": true,
762 "logx905": true,
763 "powx4001": true,
764 "powx4002": true,
765 "powx4003": true,
766 "powx4005": true,
767
768
769 "basx725": true,
770 "basx745": true,
771
772
773 "cotx970": true,
774 "cotx973": true,
775 "cotx974": true,
776 "cotx977": true,
777 "cotx980": true,
778 "cotx983": true,
779 "cotx984": true,
780 "cotx987": true,
781 "cotx994": true,
782
783
784 "quax525": true,
785 "quax531": true,
786 "quax805": true,
787 "quax806": true,
788 "quax807": true,
789 "quax808": true,
790 "quax809": true,
791 "quax810": true,
792 "quax811": true,
793 "quax812": true,
794 "quax813": true,
795 "quax814": true,
796 "quax815": true,
797 "quax816": true,
798 "quax817": true,
799 "quax818": true,
800 "quax819": true,
801 "quax820": true,
802 "quax821": true,
803 "quax822": true,
804 "quax861": true,
805 "quax862": true,
806 "quax866": true,
807
808
809
810
811 "powx4125": true,
812 "powx4145": true,
813
814
815 "expx291": true,
816 "expx292": true,
817 "expx293": true,
818 "expx294": true,
819 "expx295": true,
820 "expx296": true,
821
822
823 "addx1633": true,
824 "addx1634": true,
825 "addx1638": true,
826 "addx61633": true,
827 "addx61634": true,
828 "addx61638": true,
829
830
831 "addx1613": true,
832 "addx1614": true,
833 "addx1618": true,
834 "addx61613": true,
835 "addx61614": true,
836 "addx61618": true,
837
838
839 "sqtx8636": true,
840 "sqtx8644": true,
841 "sqtx8646": true,
842 "sqtx8647": true,
843 "sqtx8648": true,
844 "sqtx8650": true,
845 "sqtx8651": true,
846 }
847
848 var GDAignoreFlags = map[string]bool{
849
850 "sqtx9024": true,
851 "sqtx9025": true,
852 "sqtx9026": true,
853 "sqtx9027": true,
854 "sqtx9038": true,
855 "sqtx9039": true,
856 "sqtx9040": true,
857 "sqtx9045": true,
858 }
859
View as plain text