github.com/minio/simdjson-go@v0.4.6-0.20231116094823-04d21cddf993/parse_number_test.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package simdjson
    18  
    19  import (
    20  	"fmt"
    21  	"math"
    22  	"math/rand"
    23  	"regexp"
    24  	"strconv"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  )
    29  
    30  func TestNumberIsValid(t *testing.T) {
    31  	// From: https://stackoverflow.com/a/13340826
    32  	var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`)
    33  	isValidNumber := func(s string) bool {
    34  		tag, _ := parseNumber([]byte(s))
    35  		return tag != 0
    36  	}
    37  	validTests := []string{
    38  		"0",
    39  		"-0",
    40  		"1",
    41  		"-1",
    42  		"0.1",
    43  		"-0.1",
    44  		"1234",
    45  		"-1234",
    46  		"12.34",
    47  		"-12.34",
    48  		"12E0",
    49  		"12E1",
    50  		"12e34",
    51  		"12E-0",
    52  		"12e+1",
    53  		"12e-34",
    54  		"-12E0",
    55  		"-12E1",
    56  		"-12e34",
    57  		"-12E-0",
    58  		"-12e+1",
    59  		"-12e-34",
    60  		"1.2E0",
    61  		"1.2E1",
    62  		"1.2e34",
    63  		"1.2E-0",
    64  		"1.2e+1",
    65  		"1.2e-34",
    66  		"-1.2E0",
    67  		"-1.2E1",
    68  		"-1.2e34",
    69  		"-1.2E-0",
    70  		"-1.2e+1",
    71  		"-1.2e-34",
    72  		"0E0",
    73  		"0E1",
    74  		"0e34",
    75  		"0E-0",
    76  		"0e+1",
    77  		"0e-34",
    78  		"-0E0",
    79  		"-0E1",
    80  		"-0e34",
    81  		"-0E-0",
    82  		"-0e+1",
    83  		"-0e-34",
    84  	}
    85  
    86  	for _, test := range validTests {
    87  		if !isValidNumber(test) {
    88  			t.Errorf("%s should be valid", test)
    89  		}
    90  
    91  		if !jsonNumberRegexp.MatchString(test) {
    92  			t.Errorf("%s should be valid but regexp does not match", test)
    93  		}
    94  	}
    95  
    96  	invalidTests := []string{
    97  		"",
    98  		"invalid",
    99  		"1.0.1",
   100  		"1..1",
   101  		"-1-2",
   102  		"012a42",
   103  		"01.2",
   104  		"012",
   105  		"12E12.12",
   106  		"1e2e3",
   107  		"1e+-2",
   108  		"1e--23",
   109  		"1e",
   110  		"e1",
   111  		"1e+",
   112  		"1ea",
   113  		"1a",
   114  		"1.a",
   115  		"1.",
   116  		"01",
   117  		"1.e1",
   118  	}
   119  
   120  	for _, test := range invalidTests {
   121  		if isValidNumber(test) {
   122  			t.Errorf("%s should be invalid", test)
   123  		}
   124  
   125  		if jsonNumberRegexp.MatchString(test) {
   126  			t.Errorf("%s should be invalid but matches regexp", test)
   127  		}
   128  	}
   129  }
   130  
   131  func closeEnough(d1, d2 float64) (ce bool) {
   132  	return math.Abs((d1-d2)/(0.5*(d1+d2))) < 1e-20
   133  }
   134  
   135  // The following benchmarking code is borrowed from Golang (https://golang.org/src/strconv/atoi_test.go)
   136  
   137  func BenchmarkParseIntGolang(b *testing.B) {
   138  	b.Run("Pos", func(b *testing.B) {
   139  		benchmarkParseIntGolang(b, 1)
   140  	})
   141  	b.Run("Neg", func(b *testing.B) {
   142  		benchmarkParseIntGolang(b, -1)
   143  	})
   144  }
   145  
   146  type benchCase struct {
   147  	name string
   148  	num  int64
   149  }
   150  
   151  func benchmarkParseIntGolang(b *testing.B, neg int) {
   152  	cases := []benchCase{
   153  		{"63bit", 1<<63 - 1},
   154  	}
   155  	for _, cs := range cases {
   156  		b.Run(cs.name, func(b *testing.B) {
   157  			s := fmt.Sprintf("%d", cs.num*int64(neg))
   158  			for i := 0; i < b.N; i++ {
   159  				out, _ := strconv.ParseInt(s, 10, 64)
   160  				BenchSink += int(out)
   161  			}
   162  		})
   163  	}
   164  }
   165  
   166  var BenchSink int // make sure compiler cannot optimize away benchmarks
   167  
   168  var (
   169  	atofOnce                   sync.Once
   170  	benchmarksRandomBits       [1024]string
   171  	benchmarksRandomNormal     [1024]string
   172  	benchmarksRandomBitsSimd   [1024]string
   173  	benchmarksRandomNormalSimd [1024]string
   174  )
   175  
   176  func initAtof() {
   177  	atofOnce.Do(initAtofOnce)
   178  }
   179  
   180  func initAtofOnce() {
   181  
   182  	// Generate random inputs for tests and benchmarks
   183  	rand.Seed(time.Now().UnixNano())
   184  
   185  	for i := range benchmarksRandomBits {
   186  		bits := uint64(rand.Uint32())<<32 | uint64(rand.Uint32())
   187  		x := math.Float64frombits(bits)
   188  		benchmarksRandomBits[i] = strconv.FormatFloat(x, 'g', -1, 64)
   189  		benchmarksRandomBitsSimd[i] = benchmarksRandomBits[i] + ":"
   190  	}
   191  
   192  	for i := range benchmarksRandomNormal {
   193  		x := rand.NormFloat64()
   194  		benchmarksRandomNormal[i] = strconv.FormatFloat(x, 'g', -1, 64)
   195  		benchmarksRandomNormalSimd[i] = benchmarksRandomNormal[i] + ":"
   196  	}
   197  }