Skip to content

Commit

Permalink
types: let round take effect in converting float string to int string. (
Browse files Browse the repository at this point in the history
  • Loading branch information
lzmhhh123 authored and zz-jason committed Nov 26, 2018
1 parent 4b55ad0 commit 8783877
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 16 deletions.
33 changes: 28 additions & 5 deletions expression/builtin_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -4540,7 +4540,20 @@ func (c *makeTimeFunctionClass) getFunction(ctx sessionctx.Context, args []Expre
default:
flen, decimal = 17, 6
}
bf := newBaseBuiltinFuncWithTp(ctx, args, types.ETDuration, types.ETInt, types.ETInt, types.ETReal)
arg0Type, arg1Type := args[0].GetType().EvalType(), args[1].GetType().EvalType()
// For ETString type, arg must be evaluated rounding down to int64
// For other types, arg is evaluated rounding to int64
if arg0Type == types.ETString {
arg0Type = types.ETReal
} else {
arg0Type = types.ETInt
}
if arg1Type == types.ETString {
arg1Type = types.ETReal
} else {
arg1Type = types.ETInt
}
bf := newBaseBuiltinFuncWithTp(ctx, args, types.ETDuration, arg0Type, arg1Type, types.ETReal)
bf.tp.Flen, bf.tp.Decimal = flen, decimal
sig := &builtinMakeTimeSig{bf}
return sig, nil
Expand All @@ -4556,18 +4569,28 @@ func (b *builtinMakeTimeSig) Clone() builtinFunc {
return newSig
}

// calculate for hour and minute
func (b *builtinMakeTimeSig) getIntParam(arg Expression, row chunk.Row) (int64, bool, error) {
if arg.GetType().EvalType() == types.ETReal {
fRes, isNull, err := arg.EvalReal(b.ctx, row)
return int64(fRes), isNull, errors.Trace(handleInvalidTimeError(b.ctx, err))
}
iRes, isNull, err := arg.EvalInt(b.ctx, row)
return iRes, isNull, errors.Trace(handleInvalidTimeError(b.ctx, err))
}

// evalDuration evals a builtinMakeTimeIntSig.
// See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_maketime
func (b *builtinMakeTimeSig) evalDuration(row chunk.Row) (types.Duration, bool, error) {
dur := types.ZeroDuration
dur.Fsp = types.MaxFsp
hour, isNull, err := b.args[0].EvalInt(b.ctx, row)
hour, isNull, err := b.getIntParam(b.args[0], row)
if isNull || err != nil {
return dur, isNull, errors.Trace(handleInvalidTimeError(b.ctx, err))
return dur, isNull, err
}
minute, isNull, err := b.args[1].EvalInt(b.ctx, row)
minute, isNull, err := b.getIntParam(b.args[1], row)
if isNull || err != nil {
return dur, isNull, errors.Trace(handleInvalidTimeError(b.ctx, err))
return dur, isNull, err
}
if minute < 0 || minute >= 60 {
return dur, true, nil
Expand Down
73 changes: 64 additions & 9 deletions types/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,32 @@ func getValidIntPrefix(sc *stmtctx.StatementContext, str string) (string, error)
return floatStrToIntStr(sc, floatPrefix, str)
}

// roundIntStr is to round int string base on the number following dot.
func roundIntStr(numNextDot byte, intStr string) string {
if numNextDot < '5' {
return intStr
}
retStr := []byte(intStr)
for i := len(intStr) - 1; i >= 0; i-- {
if retStr[i] != '9' {
retStr[i]++
break
}
if i == 0 {
retStr[i] = '1'
retStr = append(retStr, '0')
break
}
retStr[i] = '0'
}
return string(retStr)
}

// floatStrToIntStr converts a valid float string into valid integer string which can be parsed by
// strconv.ParseInt, we can't parse float first then convert it to string because precision will
// be lost.
func floatStrToIntStr(sc *stmtctx.StatementContext, validFloat string, oriStr string) (string, error) {
// be lost. For example, the string value "18446744073709551615" which is the max number of unsigned
// int will cause some precision to lose. intStr[0] may be a positive and negative sign like '+' or '-'.
func floatStrToIntStr(sc *stmtctx.StatementContext, validFloat string, oriStr string) (intStr string, _ error) {
var dotIdx = -1
var eIdx = -1
for i := 0; i < len(validFloat); i++ {
Expand All @@ -257,7 +279,25 @@ func floatStrToIntStr(sc *stmtctx.StatementContext, validFloat string, oriStr st
if dotIdx == -1 {
return validFloat, nil
}
return validFloat[:dotIdx], nil
var digits []byte
if validFloat[0] == '-' || validFloat[0] == '+' {
dotIdx--
digits = []byte(validFloat[1:])
} else {
digits = []byte(validFloat)
}
if dotIdx == 0 {
intStr = "0"
} else {
intStr = string(digits)[:dotIdx]
}
if len(digits) > dotIdx+1 {
intStr = roundIntStr(digits[dotIdx+1], intStr)
}
if (len(intStr) > 1 || intStr[0] != '0') && validFloat[0] == '-' {
intStr = "-" + intStr
}
return intStr, nil
}
var intCnt int
digits := make([]byte, 0, len(validFloat))
Expand All @@ -280,14 +320,29 @@ func floatStrToIntStr(sc *stmtctx.StatementContext, validFloat string, oriStr st
}
intCnt += exp
if intCnt <= 0 {
return "0", nil
intStr = "0"
if intCnt == 0 && len(digits) > 0 {
dotIdx = -1
intStr = roundIntStr(digits[0], intStr)
}
return intStr, nil
}
if intCnt == 1 && (digits[0] == '-' || digits[0] == '+') {
return "0", nil
intStr = "0"
dotIdx = 0
if len(digits) > 1 {
intStr = roundIntStr(digits[1], intStr)
}
if intStr[0] == '1' {
intStr = string(digits[:1]) + intStr
}
return intStr, nil
}
var validInt string
if intCnt <= len(digits) {
validInt = string(digits[:intCnt])
intStr = string(digits[:intCnt])
if intCnt < len(digits) {
intStr = roundIntStr(digits[intCnt], intStr)
}
} else {
// convert scientific notation decimal number
extraZeroCount := intCnt - len(digits)
Expand All @@ -296,9 +351,9 @@ func floatStrToIntStr(sc *stmtctx.StatementContext, validFloat string, oriStr st
sc.AppendWarning(ErrOverflow.GenWithStackByArgs("BIGINT", oriStr))
return validFloat[:eIdx], nil
}
validInt = string(digits) + strings.Repeat("0", extraZeroCount)
intStr = string(digits) + strings.Repeat("0", extraZeroCount)
}
return validInt, nil
return intStr, nil
}

// StrToFloat converts a string to a float64 at the best-effort.
Expand Down
6 changes: 4 additions & 2 deletions types/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,8 +591,10 @@ func (s *testTypeConvertSuite) TestConvert(c *C) {
signedAccept(c, mysql.TypeLong, " .002e3 ", "2")
signedAccept(c, mysql.TypeLong, " 20e-2 ", "0")
signedAccept(c, mysql.TypeLong, " -20e-2 ", "0")
signedAccept(c, mysql.TypeLong, " +2.51 ", "2")
signedAccept(c, mysql.TypeLong, " -3.58", "-3")
signedAccept(c, mysql.TypeLong, " +2.51 ", "3")
signedAccept(c, mysql.TypeLong, " -9999.5 ", "-10000")
signedAccept(c, mysql.TypeLong, " 999.4", "999")
signedAccept(c, mysql.TypeLong, " -3.58", "-4")
signedDeny(c, mysql.TypeLong, " 1a ", "1")
signedDeny(c, mysql.TypeLong, " +1+ ", "1")

Expand Down

0 comments on commit 8783877

Please sign in to comment.