Skip to content

Commit

Permalink
Add strconv.Decimal
Browse files Browse the repository at this point in the history
  • Loading branch information
tdewolff committed May 26, 2023
1 parent 59a9ea8 commit 0053f21
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 19 deletions.
63 changes: 63 additions & 0 deletions strconv/decimal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package strconv

import (
"math"
)

func ParseDecimal(b []byte) (float64, int) {
i := 0
start := i
dot := -1
trunk := -1
n := uint64(0)
for ; i < len(b); i++ {
c := b[i]
if '0' <= c && c <= '9' {
if trunk == -1 {
if math.MaxUint64/10 < n {
trunk = i
} else {
n *= 10
n += uint64(c - '0')
}
}
} else if dot == -1 && c == '.' {
dot = i
} else {
break
}
}
if i == start || i == start+1 && dot == start {
return 0.0, 0
}

f := float64(n)
mantExp := int64(0)
if dot != -1 {
if trunk == -1 {
trunk = i
}
mantExp = int64(trunk - dot - 1)
} else if trunk != -1 {
mantExp = int64(trunk - i)
}
exp := -mantExp

// copied from strconv/atof.go
if exp == 0 {
return f, i
} else if 0 < exp && exp <= 15+22 { // int * 10^k
// If exponent is big but number of digits is not,
// can move a few zeros into the integer part.
if 22 < exp {
f *= float64pow10[exp-22]
exp = 22
}
if -1e15 <= f && f <= 1e15 {
return f * float64pow10[exp], i
}
} else if exp < 0 && -22 <= exp { // int / 10^k
return f / float64pow10[-exp], i
}
return f * math.Pow10(int(-mantExp)), i
}
46 changes: 46 additions & 0 deletions strconv/decimal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package strconv

import (
"fmt"
"testing"

"github.com/tdewolff/test"
)

func TestParseDecimal(t *testing.T) {
tests := []struct {
f string
expected float64
}{
{"5", 5},
{"5.1", 5.1},
{"18446744073709551620", 18446744073709551620.0},
}
for _, tt := range tests {
t.Run(fmt.Sprint(tt.f), func(t *testing.T) {
f, n := ParseDecimal([]byte(tt.f))
test.T(t, n, len(tt.f))
test.T(t, f, tt.expected)
})
}
}

func TestParseDecimalError(t *testing.T) {
tests := []struct {
f string
n int
expected float64
}{
{"+1", 0, 0},
{"-1", 0, 0},
{".", 0, 0},
{"1e1", 1, 1},
}
for _, tt := range tests {
t.Run(fmt.Sprint(tt.f), func(t *testing.T) {
f, n := ParseDecimal([]byte(tt.f))
test.T(t, n, tt.n)
test.T(t, f, tt.expected)
})
}
}
30 changes: 15 additions & 15 deletions strconv/float.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ func ParseFloat(b []byte) (float64, int) {
n := uint64(0)
for ; i < len(b); i++ {
c := b[i]
if c >= '0' && c <= '9' {
if '0' <= c && c <= '9' {
if trunk == -1 {
if n > math.MaxUint64/10 {
if math.MaxUint64/10 < n {
trunk = i
} else {
n *= 10
Expand Down Expand Up @@ -62,7 +62,7 @@ func ParseFloat(b []byte) (float64, int) {
if i < len(b) && (b[i] == 'e' || b[i] == 'E') {
startExp := i
i++
if e, expLen := ParseInt(b[i:]); expLen > 0 {
if e, expLen := ParseInt(b[i:]); 0 < expLen {
expExp = e
i += expLen
} else {
Expand All @@ -74,17 +74,17 @@ func ParseFloat(b []byte) (float64, int) {
// copied from strconv/atof.go
if exp == 0 {
return f, i
} else if exp > 0 && exp <= 15+22 { // int * 10^k
} else if 0 < exp && exp <= 15+22 { // int * 10^k
// If exponent is big but number of digits is not,
// can move a few zeros into the integer part.
if exp > 22 {
if 22 < exp {
f *= float64pow10[exp-22]
exp = 22
}
if f <= 1e15 && f >= -1e15 {
if -1e15 <= f && f <= 1e15 {
return f * float64pow10[exp], i
}
} else if exp < 0 && exp >= -22 { // int / 10^k
} else if -22 <= exp && exp < 0 { // int / 10^k
return f / float64pow10[-exp], i
}
f *= math.Pow10(int(-mantExp))
Expand Down Expand Up @@ -135,7 +135,7 @@ func AppendFloat(b []byte, f float64, prec int) ([]byte, bool) {
// expLen is zero for positive exponents, because positive exponents are determined later on in the big conversion loop
exp := 0
expLen := 0
if mantExp > 0 {
if 0 < mantExp {
// positive exponent is determined in the loop below
// but if we initially decreased the exponent to fit in an integer, we can't set the new exponent in the loop alone,
// since the number of zeros at the end determines the positive exponent in the loop, and we just artificially lost zeros
Expand All @@ -156,7 +156,7 @@ func AppendFloat(b []byte, f float64, prec int) ([]byte, bool) {
if neg {
maxLen++
}
if i+maxLen > cap(b) {
if cap(b) < i+maxLen {
b = append(b, make([]byte, maxLen)...)
} else {
b = b[:i+maxLen]
Expand All @@ -175,17 +175,17 @@ func AppendFloat(b []byte, f float64, prec int) ([]byte, bool) {
last := i + mantLen // right-most position of digit that is non-zero + dot
dot := last - prec - exp // position of dot
j := last
for mant > 0 {
for 0 < mant {
if j == dot {
b[j] = '.'
j--
}
newMant := mant / 10
digit := mant - 10*newMant
if zero && digit > 0 {
if zero && 0 < digit {
// first non-zero digit, if we are still behind the dot we can trim the end to this position
// otherwise trim to the dot (including the dot)
if j > dot {
if dot < j {
i = j + 1
// decrease negative exponent further to get rid of dot
if exp < 0 {
Expand All @@ -209,9 +209,9 @@ func AppendFloat(b []byte, f float64, prec int) ([]byte, bool) {
mant = newMant
}

if j > dot {
if dot < j {
// extra zeros behind the dot
for j > dot {
for dot < j {
b[j] = '0'
j--
}
Expand Down Expand Up @@ -244,7 +244,7 @@ func AppendFloat(b []byte, f float64, prec int) ([]byte, bool) {
}
i += LenInt(int64(exp))
j := i
for exp > 0 {
for 0 < exp {
newExp := exp / 10
digit := exp - 10*newExp
j--
Expand Down
8 changes: 4 additions & 4 deletions strconv/price.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ func AppendPrice(b []byte, price int64, dec bool, milSeparator byte, decSeparato
// rounding
if !dec {
firstDec := (price / 10) % 10
if firstDec >= 5 {
if 5 <= firstDec {
price += 100
}
}

// calculate size
n := LenInt(price) - 2
if n > 0 {
if 0 < n {
n += (n - 1) / 3 // mil separator
} else {
n = 1
Expand All @@ -38,7 +38,7 @@ func AppendPrice(b []byte, price int64, dec bool, milSeparator byte, decSeparato

// resize byte slice
i := len(b)
if i+n > cap(b) {
if cap(b) < i+n {
b = append(b, make([]byte, n)...)
} else {
b = b[:i+n]
Expand Down Expand Up @@ -66,7 +66,7 @@ func AppendPrice(b []byte, price int64, dec bool, milSeparator byte, decSeparato

// print integer-part
j := 0
for price > 0 {
for 0 < price {
if j == 3 {
b[i] = milSeparator
i--
Expand Down

0 comments on commit 0053f21

Please sign in to comment.