odps/data/decimal.go (97 lines of code) (raw):
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package data
import (
"fmt"
"math/big"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/aliyun/aliyun-odps-go-sdk/odps/datatype"
)
var InvalidDecimalErr = errors.New("invalid decimal")
const parseFromValue = "__ParseFromValue__"
type Decimal struct {
precision int
scale int
value string
Valid bool
intValue *big.Int
}
func (d *Decimal) Precision() int {
return d.precision
}
func (d *Decimal) Scale() int {
return d.scale
}
func (d *Decimal) Value() string {
return d.value
}
// NewDecimal value means readable string of the decimal, because MaxCompute uses string as the serialization method of decimal
func NewDecimal(precision, scale int, value string) *Decimal {
return &Decimal{
precision: precision,
scale: scale,
value: value,
Valid: true,
}
}
// NewDecimalFromValue typical way to construct decimal, use bigint to represent decimal values
func NewDecimalFromValue(precision, scale int, intValue *big.Int) *Decimal {
return &Decimal{
precision: precision,
scale: scale,
intValue: intValue,
Valid: true,
value: parseFromValue,
}
}
func DecimalFromStr(value string) (*Decimal, error) {
parts := strings.Split(value, ".")
if len(parts) > 3 {
return nil, InvalidDecimalErr
}
_, err := strconv.ParseInt(parts[0], 10, 32)
if err != nil {
return nil, InvalidDecimalErr
}
d := Decimal{}
d.precision = len(parts[0])
if d.precision > 38 {
return nil, errors.New("integer is too big, the most numbers of the integer is 38")
}
if len(parts) == 2 {
_, err = strconv.ParseInt(parts[1], 10, 32)
if err != nil {
return nil, InvalidDecimalErr
}
d.scale = len(parts[1])
}
if d.scale > 38 {
return nil, errors.New("fractional is too long, which is longer than 38")
}
d.value = value
d.Valid = true
return &d, nil
}
func (d Decimal) Type() datatype.DataType {
return datatype.NewDecimalType(int32(d.precision), int32(d.scale))
}
func (d Decimal) String() string {
if d.value == parseFromValue {
valueStr := d.intValue.String()
if d.scale > 0 {
if len(valueStr) <= d.scale {
valueStr = "0." + fmt.Sprintf("%0*s", d.scale, valueStr)
} else {
integerPart := valueStr[:len(valueStr)-d.scale]
fractionalPart := valueStr[len(valueStr)-d.scale:]
valueStr = integerPart + "." + fractionalPart
}
}
return fmt.Sprintf("%s", valueStr)
}
return fmt.Sprintf("%s", d.value)
}
func (d Decimal) Sql() string {
return fmt.Sprintf("%sBD", d.String())
}
func (d *Decimal) Scan(value interface{}) error {
return errors.WithStack(tryConvertType(value, d))
}