// Number minifies a given byte slice containing a number (see parse.Number) and removes superfluous characters. func Number(num []byte) []byte { // omit first + and register mantissa start and end, whether it's negative and the exponent neg := false start := 0 dot := -1 end := len(num) exp := int64(0) if 0 < len(num) && (num[0] == '+' || num[0] == '-') { if num[0] == '-' { neg = true start++ } else { num = num[1:] end-- } } for i := 0; i < len(num); i++ { c := num[i] if c == '.' { dot = i } else if c == 'e' || c == 'E' { end = i i++ if i < len(num) && num[i] == '+' { i++ } var ok bool if exp, ok = strconv.ParseInt(num[i:]); !ok { return num } break } } if dot == -1 { dot = end } // trim leading zeros but leave at least one digit for start < end-1 && num[start] == '0' { start++ } // trim trailing zeros i := end - 1 for ; i > dot; i-- { if num[i] != '0' { end = i + 1 break } } if i == dot { end = dot if start == end { num[start] = '0' return num[start : start+1] } } else if start == end-1 && num[start] == '0' { return num[start:end] } // shorten mantissa by increasing/decreasing the exponent if end == dot { for i := end - 1; i >= start; i-- { if num[i] != '0' { exp += int64(end - i - 1) end = i + 1 break } } } else { exp -= int64(end - dot - 1) if start == dot { for i = dot + 1; i < end; i++ { if num[i] != '0' { copy(num[dot:], num[i:end]) end -= i - dot break } } } else { copy(num[dot:], num[dot+1:end]) end-- } } // append the exponent or change the mantissa to incorporate the exponent relExp := exp + int64(end-start) // exp when the first non-zero digit is directly after the dot n := strconv.LenInt(exp) // number of exp digits if exp == 0 { if neg { start-- num[start] = '-' } return num[start:end] } else if int(relExp)+n+1 < 0 || 2 < exp { // add exponent for exp 3 and higher and where a lower exp really makes it shorter num[end] = 'e' end++ if exp < 0 { num[end] = '-' end++ exp = -exp } for i := end + n - 1; i >= end; i-- { num[i] = byte(exp%10) + '0' exp /= 10 } end += n } else if exp < 0 { // omit exponent if relExp > 0 { copy(num[start+int(relExp)+1:], num[start+int(relExp):end]) num[start+int(relExp)] = '.' end++ } else { copy(num[start-int(relExp)+1:], num[start:end]) num[start] = '.' for i := 1; i < -int(relExp)+1; i++ { num[start+i] = '0' } end -= int(relExp) - 1 } } else { // for exponent 1 and 2 num[end] = '0' if exp == 2 { num[end+1] = '0' } end += int(exp) } if neg { start-- num[start] = '-' } return num[start:end] }
// Number minifies a given byte slice containing a number (see parse.Number) and removes superfluous characters. func Number(num []byte, prec int) []byte { // omit first + and register mantissa start and end, whether it's negative and the exponent neg := false start := 0 dot := -1 end := len(num) origExp := 0 if 0 < end && (num[0] == '+' || num[0] == '-') { if num[0] == '-' { neg = true } start++ } for i, c := range num[start:] { if c == '.' { dot = start + i } else if c == 'e' || c == 'E' { end = start + i i += start + 1 if i < len(num) && num[i] == '+' { i++ } if tmpOrigExp, n := strconv.ParseInt(num[i:]); n > 0 && tmpOrigExp >= int64(MinInt) && tmpOrigExp <= int64(MaxInt) { // range checks for when int is 32 bit origExp = int(tmpOrigExp) } else { return num } break } } if dot == -1 { dot = end } // trim leading zeros but leave at least one digit for start < end-1 && num[start] == '0' { start++ } // trim trailing zeros i := end - 1 for ; i > dot; i-- { if num[i] != '0' { end = i + 1 break } } if i == dot { end = dot if start == end { num[start] = '0' return num[start : start+1] } } else if start == end-1 && num[start] == '0' { return num[start:end] } // n is the number of significant digits // normExp would be the exponent if it were normalised (0.1 <= f < 1) n := 0 normExp := 0 if dot == start { for i = dot + 1; i < end; i++ { if num[i] != '0' { n = end - i normExp = dot - i + 1 break } } } else if dot == end { normExp = end - start for i = end - 1; i >= start; i-- { if num[i] != '0' { n = i + 1 - start end = i + 1 break } } } else { n = end - start - 1 normExp = dot - start } if origExp < 0 && (normExp < MinInt-origExp || normExp-n < MinInt-origExp) || origExp > 0 && (normExp > MaxInt-origExp || normExp-n > MaxInt-origExp) { return num } normExp += origExp // intExp would be the exponent if it were an integer intExp := normExp - n lenIntExp := 1 if intExp <= -10 || intExp >= 10 { lenIntExp = strconv.LenInt(int64(intExp)) } // there are three cases to consider when printing the number // case 1: without decimals and with an exponent (large numbers) // case 2: with decimals and without an exponent (around zero) // case 3: without decimals and with a negative exponent (small numbers) if normExp >= n { // case 1 if dot < end { if dot == start { start = end - n } else { // TODO: copy the other part if shorter? //fmt.Println("COPY1", end-dot-1) copy(num[dot:], num[dot+1:end]) end-- } } if normExp >= n+3 { num[end] = 'e' end++ for i := end + lenIntExp - 1; i >= end; i-- { num[i] = byte(intExp%10) + '0' intExp /= 10 } end += lenIntExp } else if normExp == n+2 { num[end] = '0' num[end+1] = '0' end += 2 } else if normExp == n+1 { num[end] = '0' end++ } } else if normExp >= -lenIntExp-1 { // case 2 zeroes := -normExp newDot := 0 if zeroes > 0 { // dot placed at the front and add zeroes newDot = end - n - zeroes - 1 if newDot != dot { d := start - newDot if d > 0 { if dot < end { // copy original digits behind the dot backwards //fmt.Println("COPY2", end-dot-1) copy(num[dot+1+d:], num[dot+1:end]) if dot > start { // copy original digits before the dot backwards //fmt.Println("COPY3a", dot-start) copy(num[start+d+1:], num[start:dot]) } } else if dot > start { // copy original digits before the dot backwards //fmt.Println("COPY3b", dot-start) copy(num[start+d:], num[start:dot]) } newDot = start end += d } else { start += -d } num[newDot] = '.' for i := 0; i < zeroes; i++ { num[newDot+1+i] = '0' } } } else { // placed in the middle if dot == start { // TODO: try if placing at the end reduces copying // when there are zeroes after the dot dot = end - n - 1 start = dot } else if dot >= end { // TODO: try if placing at the start reduces copying // when input has no dot in it dot = end end++ } newDot = start + normExp if newDot > dot { // copy digits forwards //fmt.Println("COPY4", newDot-dot) copy(num[dot:], num[dot+1:newDot+1]) } else if newDot < dot { // copy digits backwards //fmt.Println("COPY5", dot-newDot) copy(num[newDot+1:], num[newDot:dot]) } num[newDot] = '.' } // apply precision dot = newDot if prec > -1 && dot+1+prec < end { end = dot + 1 + prec inc := num[end] >= '5' if inc || num[end-1] == '0' { for i := end - 1; i > start; i-- { if i == dot { end-- } else if inc { if num[i] == '9' { end-- } else { num[i]++ inc = false break } } else if i > dot && num[i] == '0' { end-- } } } if dot == start && end == start+1 { if inc { num[start] = '1' } else { num[start] = '0' } } else if inc { if num[start] == '9' { num[start] = '0' copy(num[start+1:], num[start:end]) end++ num[start] = '1' } else { num[start]++ } } } } else { // case 3 if dot < end { if dot == start { //fmt.Println("COPY6", n) copy(num[start:], num[end-n:end]) end = start + n } else { //fmt.Println("COPY7", end-dot-1) copy(num[dot:], num[dot+1:end]) end-- } } num[end] = 'e' num[end+1] = '-' end += 2 intExp = -intExp for i := end + lenIntExp - 1; i >= end; i-- { num[i] = byte(intExp%10) + '0' intExp /= 10 } end += lenIntExp } if neg { start-- num[start] = '-' } return num[start:end] }