natconv_test.mx raw

   1  package big
   2  
   3  import (
   4  	"math"
   5  	"testing"
   6  )
   7  
   8  // The decimal expansion of 1.7976931348623157e308: 17 significant digits then
   9  // 292 zeros, a 1027-bit (17-word) numerator. It is the value the relay audit
  10  // recorded as broken under stage4 in both directions - big.Int could not print
  11  // a multi-word decimal, and big.Rat's decimal parse died on the next String().
  12  const maxFloat64Decimal = "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
  13  
  14  func pow10(k int32) (s []byte) {
  15  	s = []byte{:k + 1}
  16  	s[0] = '1'
  17  	for i := int32(1); i <= k; i++ {
  18  		s[i] = '0'
  19  	}
  20  	return
  21  }
  22  
  23  // TestIntDecimalRoundTrip walks the base-10 leaf and the recursive
  24  // divisors/convertWords path (values above leafSize words) and the power-of-two
  25  // branch, in both directions.
  26  func TestIntDecimalRoundTrip(t *testing.T) {
  27  	cases := []string{
  28  		"0",
  29  		"1",
  30  		"123456789012345678901234567890",
  31  		"18446744073709551615",
  32  		"18446744073709551616",
  33  		maxFloat64Decimal,
  34  	}
  35  	for i := int32(0); i < len(cases); i++ {
  36  		want := []byte(cases[i])
  37  		var x Int
  38  		if _, ok := x.SetString(want, 10); !ok {
  39  			t.Fatalf("SetString(%s, 10) failed", want)
  40  		}
  41  		if got := x.String(); got != want {
  42  			t.Fatalf("SetString(%s).String() = %s", want, got)
  43  		}
  44  		hex := x.Text(16)
  45  		var y Int
  46  		if _, ok := y.SetString(hex, 16); !ok {
  47  			t.Fatalf("SetString(%s, 16) failed", hex)
  48  		}
  49  		if got := y.String(); got != want {
  50  			t.Fatalf("hex round trip of %s = %s", want, got)
  51  		}
  52  	}
  53  }
  54  
  55  // TestIntExpReusesAccumulator squares 10^k through expNN for k past 20, where
  56  // the accumulator leaves one word. basicMul accumulates into z and relies on
  57  // its opening clear(z[0:len(x)+len(y)]); when stage4 dropped clear, every
  58  // reused accumulator kept its previous product's digits and Exp(10,20) came
  59  // out as 10^20+10^5.
  60  func TestIntExpReusesAccumulator(t *testing.T) {
  61  	for k := int32(1); k <= 70; k++ {
  62  		var want Int
  63  		want.SetString(pow10(k), 10)
  64  		var got Int
  65  		got.Exp(NewInt(10), NewInt(int64(k)), nil)
  66  		if got.Cmp(&want) != 0 {
  67  			t.Fatalf("Exp(10, %d) = %s, want %s", k, got.String(), want.String())
  68  		}
  69  	}
  70  }
  71  
  72  // TestRatLongDecimal is the audit's Rat repro: SetString of a long decimal
  73  // exponent succeeds, and the next String() formats a 309-digit numerator.
  74  func TestRatLongDecimal(t *testing.T) {
  75  	var r Rat
  76  	if _, ok := r.SetString([]byte("1.7976931348623157e308")); !ok {
  77  		t.Fatal("SetString(1.7976931348623157e308) failed")
  78  	}
  79  	if !r.IsInt() {
  80  		t.Fatal("1.7976931348623157e308 must be an integer")
  81  	}
  82  	if got := r.RatString(); got != []byte(maxFloat64Decimal) {
  83  		t.Fatalf("RatString() = %s", got)
  84  	}
  85  	var back Rat
  86  	if _, ok := back.SetString(r.String()); !ok {
  87  		t.Fatal("re-parsing Rat.String() failed")
  88  	}
  89  	if back.Cmp(&r) != 0 {
  90  		t.Fatal("Rat.String() did not round trip")
  91  	}
  92  }
  93  
  94  // TestRatSmallestSubnormal is the exact value of math.SmallestNonzeroFloat64:
  95  // 1/2^1074, whose decimal denominator needs a divisor above the 17-word table
  96  // (a second-level recursive conversion). The divisor table used to be cached in
  97  // a package global while the nats it holds are arena allocations, so the
  98  // metadata outlived the words: the next conversion divided by a divisor whose
  99  // top word had been reused, `reciprocalWord(0)` was reached, and bits.Div32
 100  // divided by zero.
 101  func TestRatSmallestSubnormal(t *testing.T) {
 102  	one := NewInt(1)
 103  	den := NewInt(2)
 104  	den.Exp(den, NewInt(1074), nil)
 105  	var r Rat
 106  	r.SetFrac(one, den)
 107  	got := string(r.RatString())
 108  	if len(got) != 326 {
 109  		t.Fatalf("RatString(1/2^1074) is %d bytes, want 326", len(got))
 110  	}
 111  	if got[0] != '1' || got[1] != '/' {
 112  		t.Fatalf("RatString(1/2^1074) = %s", got)
 113  	}
 114  	if f, _ := r.Float64(); f != math.SmallestNonzeroFloat64 {
 115  		t.Fatalf("1/2^1074 = %g, want SmallestNonzeroFloat64", f)
 116  	}
 117  }
 118