You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

190 lines
3.3 KiB

package jx
import (
"bytes"
"fmt"
"math/big"
"github.com/go-faster/errors"
)
// Num represents number, which can be raw json number or number string.
//
// Same as Raw, but with number invariants.
//
// Examples:
// 123.45 // Str: false, IsInt: false
// "123.45" // Str: true, IsInt: false
// "12345" // Str: true, IsInt: true
// 12345 // Str: false, IsInt: true
type Num []byte
func (n Num) dec() Decoder {
head := 0
if n.Str() {
head = 1
}
return Decoder{
buf: n,
tail: len(n),
head: head,
}
}
// Str reports whether Num is string number.
func (n Num) Str() bool {
return len(n) > 0 && n[0] == '"'
}
func (n Num) floatAsInt() error {
// Allow decoding floats with zero fractional, like 1.0 as 1.
var dot bool
for _, c := range n {
if c == '.' {
dot = true
continue
}
if !dot {
continue
}
switch c {
case '0', '"': // ok
default:
return errors.Wrap(badToken(c), "non-zero fractional part")
}
}
return nil
}
// Int64 decodes number as a signed 64-bit integer.
// Works on floats with zero fractional part.
func (n Num) Int64() (int64, error) {
if err := n.floatAsInt(); err != nil {
return 0, errors.Wrap(err, "float as int")
}
d := n.dec()
return d.Int64()
}
// IsInt reports whether number is integer.
func (n Num) IsInt() bool {
if len(n) == 0 {
return false
}
b := n
if b[0] == '"' {
b = b[1 : len(b)-1]
}
if b[0] == '-' {
b = b[1:]
}
for _, c := range b {
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': // ok
default:
return false
}
}
return true
}
// Uint64 decodes number as an unsigned 64-bit integer.
// Works on floats with zero fractional part.
func (n Num) Uint64() (uint64, error) {
if err := n.floatAsInt(); err != nil {
return 0, errors.Wrap(err, "float as int")
}
d := n.dec()
return d.UInt64()
}
// Float64 decodes number as 64-bit floating point.
func (n Num) Float64() (float64, error) {
d := n.dec()
return d.Float64()
}
// Equal reports whether numbers are strictly equal, including their formats.
func (n Num) Equal(v Num) bool {
return bytes.Equal(n, v)
}
func (n Num) String() string {
if len(n) == 0 {
return "<invalid>"
}
return string(n)
}
// Format implements fmt.Formatter.
func (n Num) Format(f fmt.State, verb rune) {
switch verb {
case 's', 'v':
_, _ = f.Write(n)
case 'd':
d, err := n.Int64()
if err != nil {
fmt.Fprintf(f, "%%!invalid(Num=%s)", n.String())
return
}
v := big.NewInt(d)
v.Format(f, verb)
case 'f':
d, err := n.Float64()
if err != nil {
fmt.Fprintf(f, "%%!invalid(Num=%s)", n.String())
return
}
v := big.NewFloat(d)
v.Format(f, verb)
}
}
// Sign reports sign of number.
//
// 0 is zero, 1 is positive, -1 is negative.
func (n Num) Sign() int {
if len(n) == 0 {
return 0
}
c := n[0]
if c == '"' {
if len(n) < 2 {
return 0
}
c = n[1]
}
switch c {
case '-':
return -1
case '0':
return 0
default:
return 1
}
}
// Positive reports whether number is positive.
func (n Num) Positive() bool { return n.Sign() > 0 }
// Negative reports whether number is negative.
func (n Num) Negative() bool { return n.Sign() < 0 }
// Zero reports whether number is zero.
func (n Num) Zero() bool {
if len(n) == 0 {
return false
}
if len(n) == 1 {
return n[0] == '0'
}
for _, c := range n {
switch c {
case '.', '0', '-':
continue
default:
return false
}
}
return true
}