Browse Source

codec: json: support more precise and performant floating point operations

When encoding floats, we strive to encode with one of [e.] in the string,
so we can know that it is a float when decoding it back.

Now, we get better performance by optimizing how we decipher whether
there are any fractions, and we treat float32 separate from float64.
Ugorji Nwoke 6 năm trước cách đây
mục cha
commit
3f8d8502cb
3 tập tin đã thay đổi với 119 bổ sung60 xóa
  1. 6 8
      codec/decode.go
  2. 42 2
      codec/helper.go
  3. 71 50
      codec/json.go

+ 6 - 8
codec/decode.go

@@ -2965,25 +2965,23 @@ func (d *Decoder) arrayEnd() {
 // decSliceHelper assists when decoding into a slice, from a map or an array in the stream.
 // decSliceHelper assists when decoding into a slice, from a map or an array in the stream.
 // A slice can be set from a map or array in stream. This supports the MapBySlice interface.
 // A slice can be set from a map or array in stream. This supports the MapBySlice interface.
 type decSliceHelper struct {
 type decSliceHelper struct {
-	d *Decoder
-	// ct valueType
+	d     *Decoder
+	ct    valueType
 	array bool
 	array bool
 }
 }
 
 
 func (d *Decoder) decSliceHelperStart() (x decSliceHelper, clen int) {
 func (d *Decoder) decSliceHelperStart() (x decSliceHelper, clen int) {
-	dd := d.d
-	ctyp := dd.ContainerType()
-	switch ctyp {
+	x.ct = d.d.ContainerType()
+	x.d = d
+	switch x.ct {
 	case valueTypeArray:
 	case valueTypeArray:
 		x.array = true
 		x.array = true
 		clen = d.arrayStart()
 		clen = d.arrayStart()
 	case valueTypeMap:
 	case valueTypeMap:
 		clen = d.mapStart() * 2
 		clen = d.mapStart() * 2
 	default:
 	default:
-		d.errorf("only encoded map or array can be decoded into a slice (%d)", ctyp)
+		d.errorf("only encoded map or array can be decoded into a slice (%d)", x.ct)
 	}
 	}
-	// x.ct = ctyp
-	x.d = d
 	return
 	return
 }
 }
 
 

+ 42 - 2
codec/helper.go

@@ -2166,11 +2166,51 @@ func (x checkOverflow) SignedIntV(v uint64) int64 {
 	return int64(v)
 	return int64(v)
 }
 }
 
 
-// ------------------ SORT -----------------
+// ------------------ FLOATING POINT -----------------
 
 
-// func isNaN(f float64) bool   { return f != f }
 func isNaN64(f float64) bool { return f != f }
 func isNaN64(f float64) bool { return f != f }
 func isNaN32(f float32) bool { return f != f }
 func isNaN32(f float32) bool { return f != f }
+func abs32(f float32) float32 {
+	return math.Float32frombits(math.Float32bits(f) &^ (1 << 31))
+}
+
+// Per go spec, floats are represented in memory as
+// IEEE single or double precision floating point values.
+//
+// We also looked at the source for stdlib math/modf.go,
+// reviewed https://github.com/chewxy/math32
+// and read wikipedia documents describing the formats.
+//
+// It became clear that we could easily look at the bits to determine
+// whether any fraction exists.
+//
+// This is all we need for now.
+
+func noFrac64(f float64) (v bool) {
+	x := math.Float64bits(f)
+	e := uint64(x>>52)&0x7FF - 1023 // uint(x>>shift)&mask - bias
+	// clear top 12+e bits, the integer part; if the rest is 0, then no fraction.
+	if e < 52 {
+		// return x&((1<<64-1)>>(12+e)) == 0
+		return x<<(12+e) == 0
+	}
+	return
+}
+func noFrac32(f float32) (v bool) {
+	x := math.Float32bits(f)
+	e := uint32(x>>23)&0xFF - 127 // uint(x>>shift)&mask - bias
+	// clear top 9+e bits, the integer part; if the rest is 0, then no fraction.
+	if e < 23 {
+		// return x&((1<<32-1)>>(9+e)) == 0
+		return x<<(9+e) == 0
+	}
+	return
+}
+
+// func noFrac(f float64) bool {
+// 	_, frac := math.Modf(float64(f))
+// 	return frac == 0
+// }
 
 
 // -----------------------
 // -----------------------
 
 

+ 71 - 50
codec/json.go

@@ -164,10 +164,6 @@ func (e *jsonEncDriverTypical) EncodeBool(b bool) {
 	}
 	}
 }
 }
 
 
-func (e *jsonEncDriverTypical) EncodeFloat64(f float64) {
-	e.encodeFloat(f, 64)
-}
-
 func (e *jsonEncDriverTypical) EncodeInt(v int64) {
 func (e *jsonEncDriverTypical) EncodeInt(v int64) {
 	e.w.writeb(strconv.AppendInt(e.b[:0], v, 10))
 	e.w.writeb(strconv.AppendInt(e.b[:0], v, 10))
 }
 }
@@ -176,15 +172,22 @@ func (e *jsonEncDriverTypical) EncodeUint(v uint64) {
 	e.w.writeb(strconv.AppendUint(e.b[:0], v, 10))
 	e.w.writeb(strconv.AppendUint(e.b[:0], v, 10))
 }
 }
 
 
-func (e *jsonEncDriverTypical) EncodeFloat32(f float32) {
-	e.encodeFloat(float64(f), 32)
+func (e *jsonEncDriverTypical) EncodeFloat64(f float64) {
+	fmt, prec := jsonFloatStrconvFmtPrec64(f)
+	e.w.writeb(strconv.AppendFloat(e.b[:0], f, fmt, int(prec), 64))
+	// e.w.writeb(strconv.AppendFloat(e.b[:0], f, jsonFloatStrconvFmtPrec64(f), 64))
 }
 }
 
 
-func (e *jsonEncDriverTypical) encodeFloat(f float64, bitsize uint8) {
-	fmt, prec := jsonFloatStrconvFmtPrec(f)
-	e.w.writeb(strconv.AppendFloat(e.b[:0], f, fmt, prec, int(bitsize)))
+func (e *jsonEncDriverTypical) EncodeFloat32(f float32) {
+	fmt, prec := jsonFloatStrconvFmtPrec32(f)
+	e.w.writeb(strconv.AppendFloat(e.b[:0], float64(f), fmt, int(prec), 32))
 }
 }
 
 
+// func (e *jsonEncDriverTypical) encodeFloat(f float64, bitsize uint8) {
+// 	fmt, prec := jsonFloatStrconvFmtPrec(f, bitsize == 32)
+// 	e.w.writeb(strconv.AppendFloat(e.b[:0], f, fmt, prec, int(bitsize)))
+// }
+
 // func (e *jsonEncDriverTypical) atEndOfEncode() {
 // func (e *jsonEncDriverTypical) atEndOfEncode() {
 // 	if e.tw {
 // 	if e.tw {
 // 		e.w.writen1(' ')
 // 		e.w.writen1(' ')
@@ -269,21 +272,28 @@ func (e *jsonEncDriverGeneric) EncodeBool(b bool) {
 	}
 	}
 }
 }
 
 
-func (e *jsonEncDriverGeneric) EncodeFloat64(f float64) {
-	// instead of using 'g', specify whether to use 'e' or 'f'
-	fmt, prec := jsonFloatStrconvFmtPrec(f)
-
+func (e *jsonEncDriverGeneric) encodeFloat(f float64, bitsize, fmt byte, prec int8) {
 	var blen int
 	var blen int
 	if e.ks && e.e.c == containerMapKey {
 	if e.ks && e.e.c == containerMapKey {
-		blen = 2 + len(strconv.AppendFloat(e.b[1:1], f, fmt, prec, 64))
+		blen = 2 + len(strconv.AppendFloat(e.b[1:1], f, fmt, int(prec), int(bitsize)))
 		e.b[0] = '"'
 		e.b[0] = '"'
 		e.b[blen-1] = '"'
 		e.b[blen-1] = '"'
 	} else {
 	} else {
-		blen = len(strconv.AppendFloat(e.b[:0], f, fmt, prec, 64))
+		blen = len(strconv.AppendFloat(e.b[:0], f, fmt, int(prec), int(bitsize)))
 	}
 	}
 	e.w.writeb(e.b[:blen])
 	e.w.writeb(e.b[:blen])
 }
 }
 
 
+func (e *jsonEncDriverGeneric) EncodeFloat64(f float64) {
+	fmt, prec := jsonFloatStrconvFmtPrec64(f)
+	e.encodeFloat(f, 64, fmt, prec)
+}
+
+func (e *jsonEncDriverGeneric) EncodeFloat32(f float32) {
+	fmt, prec := jsonFloatStrconvFmtPrec32(f)
+	e.encodeFloat(float64(f), 32, fmt, prec)
+}
+
 func (e *jsonEncDriverGeneric) EncodeInt(v int64) {
 func (e *jsonEncDriverGeneric) EncodeInt(v int64) {
 	x := e.is
 	x := e.is
 	if x == 'A' || x == 'L' && (v > 1<<53 || v < -(1<<53)) || (e.ks && e.e.c == containerMapKey) {
 	if x == 'A' || x == 'L' && (v > 1<<53 || v < -(1<<53)) || (e.ks && e.e.c == containerMapKey) {
@@ -308,13 +318,13 @@ func (e *jsonEncDriverGeneric) EncodeUint(v uint64) {
 	e.w.writeb(strconv.AppendUint(e.b[:0], v, 10))
 	e.w.writeb(strconv.AppendUint(e.b[:0], v, 10))
 }
 }
 
 
-func (e *jsonEncDriverGeneric) EncodeFloat32(f float32) {
-	// e.encodeFloat(float64(f), 32)
-	// always encode all floats as IEEE 64-bit floating point.
-	// It also ensures that we can decode in full precision even if into a float32,
-	// as what is written is always to float64 precision.
-	e.EncodeFloat64(float64(f))
-}
+// func (e *jsonEncDriverGeneric) EncodeFloat32(f float32) {
+// 	// e.encodeFloat(float64(f), 32)
+// 	// always encode all floats as IEEE 64-bit floating point.
+// 	// It also ensures that we can decode in full precision even if into a float32,
+// 	// as what is written is always to float64 precision.
+// 	e.EncodeFloat64(float64(f))
+// }
 
 
 // func (e *jsonEncDriverGeneric) atEndOfEncode() {
 // func (e *jsonEncDriverGeneric) atEndOfEncode() {
 // 	if e.tw {
 // 	if e.tw {
@@ -896,12 +906,12 @@ func (d *jsonDecDriver) decUint64ViaFloat(s string) (u uint64) {
 	return uint64(fi)
 	return uint64(fi)
 }
 }
 
 
-func (d *jsonDecDriver) decodeFloat(bitsize uint8) (f float64) {
+func (d *jsonDecDriver) decodeFloat(bitsize int) (f float64) {
 	bs := d.decNumBytes()
 	bs := d.decNumBytes()
 	if len(bs) == 0 {
 	if len(bs) == 0 {
 		return
 		return
 	}
 	}
-	f, err := strconv.ParseFloat(stringView(bs), int(bitsize))
+	f, err := strconv.ParseFloat(stringView(bs), bitsize)
 	if err != nil {
 	if err != nil {
 		d.d.errorv(err)
 		d.d.errorv(err)
 	}
 	}
@@ -1396,41 +1406,52 @@ func (d *jsonDecDriver) reset() {
 	// d.n.reset()
 	// d.n.reset()
 }
 }
 
 
-func jsonFloatStrconvFmtPrec(f float64) (fmt byte, prec int) {
-	// set prec to 1 iff mod is 0.
-	//     better than using jsonIsFloatBytesB2 to check if a . or E in the float bytes.
-	// this ensures that every float has an e or .0 in it.
+// jsonFloatStrconvFmtPrec ...
+//
+// ensure that every float has an 'e' or '.' in it,/ for easy differentiation from integers.
+// this is better/faster than checking if  encoded value has [e.] and appending if needed.
+
+// func jsonFloatStrconvFmtPrec(f float64, bits32 bool) (fmt byte, prec int) {
+// 	fmt = 'f'
+// 	prec = -1
+// 	var abs = math.Abs(f)
+// 	if abs == 0 || abs == 1 {
+// 		prec = 1
+// 	} else if !bits32 && (abs < 1e-6 || abs >= 1e21) ||
+// 		bits32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
+// 		fmt = 'e'
+// 	} else if _, frac := math.Modf(abs); frac == 0 {
+// 		// ensure that floats have a .0 at the end, for easy identification as floats
+// 		prec = 1
+// 	}
+// 	return
+// }
 
 
+func jsonFloatStrconvFmtPrec64(f float64) (fmt byte, prec int8) {
+	fmt = 'f'
+	prec = -1
 	var abs = math.Abs(f)
 	var abs = math.Abs(f)
 	if abs == 0 || abs == 1 {
 	if abs == 0 || abs == 1 {
-		fmt = 'f'
 		prec = 1
 		prec = 1
 	} else if abs < 1e-6 || abs >= 1e21 {
 	} else if abs < 1e-6 || abs >= 1e21 {
 		fmt = 'e'
 		fmt = 'e'
-		prec = -1
-	} else if abs < 0 {
-		fmt = 'f'
-		prec = -1
-	} else if _, mod := math.Modf(abs); mod == 0 {
-		fmt = 'f'
+	} else if noFrac64(abs) { // _, frac := math.Modf(abs); frac == 0 {
 		prec = 1
 		prec = 1
-	} else {
-		fmt = 'f'
-		prec = -1
 	}
 	}
+	return
+}
 
 
-	// prec = -1
-	// if abs != 0 && (abs < 1e-6 || abs >= 1e21) {
-	// 	fmt = 'e'
-	// } else {
-	// 	fmt = 'f'
-	// 	if abs == 0 || abs == 1 {
-	// 		prec = 1
-	// 	} else if abs < 0 {
-	// 	} else if _, mod := math.Modf(abs); mod == 0 {
-	// 		prec = 1
-	// 	}
-	// }
+func jsonFloatStrconvFmtPrec32(f float32) (fmt byte, prec int8) {
+	fmt = 'f'
+	prec = -1
+	var abs = abs32(f)
+	if abs == 0 || abs == 1 {
+		prec = 1
+	} else if abs < 1e-6 || abs >= 1e21 {
+		fmt = 'e'
+	} else if noFrac32(abs) { // _, frac := math.Modf(abs); frac == 0 {
+		prec = 1
+	}
 	return
 	return
 }
 }