mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
This will let low-level things depend on the canonical routines, even for floating-point printing. Change-Id: I31207dc6584ad90d4e365dbe6eaf20f8662ed22d Reviewed-on: https://go-review.googlesource.com/c/go/+/716000 Reviewed-by: David Chase <drchase@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
166 lines
4.8 KiB
Go
166 lines
4.8 KiB
Go
// Copyright 2020 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package strconv
|
|
|
|
// This file implements the Eisel-Lemire ParseFloat algorithm, published in
|
|
// 2020 and discussed extensively at
|
|
// https://nigeltao.github.io/blog/2020/eisel-lemire.html
|
|
//
|
|
// The original C++ implementation is at
|
|
// https://github.com/lemire/fast_double_parser/blob/644bef4306059d3be01a04e77d3cc84b379c596f/include/fast_double_parser.h#L840
|
|
//
|
|
// This Go re-implementation closely follows the C re-implementation at
|
|
// https://github.com/google/wuffs/blob/ba3818cb6b473a2ed0b38ecfc07dbbd3a97e8ae7/internal/cgen/base/floatconv-submodule-code.c#L990
|
|
//
|
|
// Additional testing (on over several million test strings) is done by
|
|
// https://github.com/nigeltao/parse-number-fxx-test-data/blob/5280dcfccf6d0b02a65ae282dad0b6d9de50e039/script/test-go-strconv.go
|
|
|
|
import (
|
|
"math/bits"
|
|
)
|
|
|
|
func eiselLemire64(man uint64, exp10 int, neg bool) (f float64, ok bool) {
|
|
// The terse comments in this function body refer to sections of the
|
|
// https://nigeltao.github.io/blog/2020/eisel-lemire.html blog post.
|
|
|
|
// Exp10 Range.
|
|
if man == 0 {
|
|
if neg {
|
|
f = float64frombits(0x8000000000000000) // Negative zero.
|
|
}
|
|
return f, true
|
|
}
|
|
pow, exp2, ok := pow10(exp10)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
|
|
// Normalization.
|
|
clz := bits.LeadingZeros64(man)
|
|
man <<= uint(clz)
|
|
retExp2 := uint64(exp2+64-float64Bias) - uint64(clz)
|
|
|
|
// Multiplication.
|
|
xHi, xLo := bits.Mul64(man, pow.Hi)
|
|
|
|
// Wider Approximation.
|
|
if xHi&0x1FF == 0x1FF && xLo+man < man {
|
|
yHi, yLo := bits.Mul64(man, pow.Lo)
|
|
mergedHi, mergedLo := xHi, xLo+yHi
|
|
if mergedLo < xLo {
|
|
mergedHi++
|
|
}
|
|
if mergedHi&0x1FF == 0x1FF && mergedLo+1 == 0 && yLo+man < man {
|
|
return 0, false
|
|
}
|
|
xHi, xLo = mergedHi, mergedLo
|
|
}
|
|
|
|
// Shifting to 54 Bits.
|
|
msb := xHi >> 63
|
|
retMantissa := xHi >> (msb + 9)
|
|
retExp2 -= 1 ^ msb
|
|
|
|
// Half-way Ambiguity.
|
|
if xLo == 0 && xHi&0x1FF == 0 && retMantissa&3 == 1 {
|
|
return 0, false
|
|
}
|
|
|
|
// From 54 to 53 Bits.
|
|
retMantissa += retMantissa & 1
|
|
retMantissa >>= 1
|
|
if retMantissa>>53 > 0 {
|
|
retMantissa >>= 1
|
|
retExp2 += 1
|
|
}
|
|
// retExp2 is a uint64. Zero or underflow means that we're in subnormal
|
|
// float64 space. 0x7FF or above means that we're in Inf/NaN float64 space.
|
|
//
|
|
// The if block is equivalent to (but has fewer branches than):
|
|
// if retExp2 <= 0 || retExp2 >= 0x7FF { etc }
|
|
if retExp2-1 >= 0x7FF-1 {
|
|
return 0, false
|
|
}
|
|
retBits := retExp2<<float64MantBits | retMantissa&(1<<float64MantBits-1)
|
|
if neg {
|
|
retBits |= 0x8000000000000000
|
|
}
|
|
return float64frombits(retBits), true
|
|
}
|
|
|
|
func eiselLemire32(man uint64, exp10 int, neg bool) (f float32, ok bool) {
|
|
// The terse comments in this function body refer to sections of the
|
|
// https://nigeltao.github.io/blog/2020/eisel-lemire.html blog post.
|
|
//
|
|
// That blog post discusses the float64 flavor (11 exponent bits with a
|
|
// -1023 bias, 52 mantissa bits) of the algorithm, but the same approach
|
|
// applies to the float32 flavor (8 exponent bits with a -127 bias, 23
|
|
// mantissa bits). The computation here happens with 64-bit values (e.g.
|
|
// man, xHi, retMantissa) before finally converting to a 32-bit float.
|
|
|
|
// Exp10 Range.
|
|
if man == 0 {
|
|
if neg {
|
|
f = float32frombits(0x80000000) // Negative zero.
|
|
}
|
|
return f, true
|
|
}
|
|
pow, exp2, ok := pow10(exp10)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
|
|
// Normalization.
|
|
clz := bits.LeadingZeros64(man)
|
|
man <<= uint(clz)
|
|
retExp2 := uint64(exp2+64-float32Bias) - uint64(clz)
|
|
|
|
// Multiplication.
|
|
xHi, xLo := bits.Mul64(man, pow.Hi)
|
|
|
|
// Wider Approximation.
|
|
if xHi&0x3FFFFFFFFF == 0x3FFFFFFFFF && xLo+man < man {
|
|
yHi, yLo := bits.Mul64(man, pow.Lo)
|
|
mergedHi, mergedLo := xHi, xLo+yHi
|
|
if mergedLo < xLo {
|
|
mergedHi++
|
|
}
|
|
if mergedHi&0x3FFFFFFFFF == 0x3FFFFFFFFF && mergedLo+1 == 0 && yLo+man < man {
|
|
return 0, false
|
|
}
|
|
xHi, xLo = mergedHi, mergedLo
|
|
}
|
|
|
|
// Shifting to 54 Bits (and for float32, it's shifting to 25 bits).
|
|
msb := xHi >> 63
|
|
retMantissa := xHi >> (msb + 38)
|
|
retExp2 -= 1 ^ msb
|
|
|
|
// Half-way Ambiguity.
|
|
if xLo == 0 && xHi&0x3FFFFFFFFF == 0 && retMantissa&3 == 1 {
|
|
return 0, false
|
|
}
|
|
|
|
// From 54 to 53 Bits (and for float32, it's from 25 to 24 bits).
|
|
retMantissa += retMantissa & 1
|
|
retMantissa >>= 1
|
|
if retMantissa>>24 > 0 {
|
|
retMantissa >>= 1
|
|
retExp2 += 1
|
|
}
|
|
// retExp2 is a uint64. Zero or underflow means that we're in subnormal
|
|
// float32 space. 0xFF or above means that we're in Inf/NaN float32 space.
|
|
//
|
|
// The if block is equivalent to (but has fewer branches than):
|
|
// if retExp2 <= 0 || retExp2 >= 0xFF { etc }
|
|
if retExp2-1 >= 0xFF-1 {
|
|
return 0, false
|
|
}
|
|
retBits := retExp2<<float32MantBits | retMantissa&(1<<float32MantBits-1)
|
|
if neg {
|
|
retBits |= 0x80000000
|
|
}
|
|
return float32frombits(uint32(retBits)), true
|
|
}
|