Skip to content

Commit

Permalink
String conversion from decimal (#108) (#122)
Browse files Browse the repository at this point in the history
This change adds various string-conversion methods, mainly from decimal. 
It also aligns some setters, into the following: 

Public "convenience" constructors
    FromBig(*big.Int)
    FromHex(string)
    FromBase10(string)
Instance setters:
    (z *Int) SetFromBase10(string) (possibly)
    (z *Int) SetFromBig(*big.Int)
    (z *Int) SetBytes([]byte)
    (z *Int) SetString(string, int)


Co-authored-by: a <[email protected]>
Co-authored-by: Tjudice <[email protected]>
Co-authored-by: Martin Holst Swende <[email protected]>
  • Loading branch information
4 people authored Feb 8, 2023
1 parent 9c4de98 commit c6c6f8d
Show file tree
Hide file tree
Showing 8 changed files with 584 additions and 67 deletions.
58 changes: 12 additions & 46 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ commands:
jobs:

go117:
go119:
docker:
- image: cimg/go:1.17
- image: cimg/go:1.19
steps:
- run:
name: "Install tools"
command: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.42.0
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.50.1
- checkout
- run:
name: "Lint"
Expand All @@ -40,18 +40,15 @@ jobs:
command: bash <(curl -s https://codecov.io/bash)
- restore_cache:
keys:
- corpus-v2
- corpus-v3
- run:
name: "Fuzzing"
command: |
go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build
go-fuzz-build
timeout --preserve-status --signal INT 1m go-fuzz -procs=2
test ! "$(ls crashers)"
GOCACHE=/home/circleci/project/corpus-v3 go test . -run - -fuzz . -fuzztime 1m
- save_cache:
key: corpus-v2-{{ epoch }}
key: corpus-v3-{{ epoch }}
paths:
- corpus
- corpus-v3
- run:
name: "Benchmark"
command: go test -run=- -bench=. -benchmem
Expand All @@ -78,53 +75,22 @@ jobs:
name: "Test (PPC64 emulation)"
command: qemu-ppc64-static uint256.test.ppc64 -test.v

go116:
go118:
docker:
- image: cimg/go:1.16
- image: cimg/go:1.18
steps:
- checkout
- test

go115:
docker:
- image: cimg/go:1.15
steps:
- checkout
- test

go114:
docker:
- image: cimg/go:1.14
steps:
- checkout
- test

go113:
docker:
- image: cimg/go:1.13
steps:
- checkout
- test

go112:
docker:
- image: cimg/go:1.12
steps:
- checkout
- test



workflows:
version: 2
uint256:
jobs:
- go117
- go116
- go115
- go114
- go113
- go112
- go119
- go118
- bigendian:
requires:
- go117
- go119
83 changes: 81 additions & 2 deletions conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
package uint256

import (
"database/sql"
"database/sql/driver"
"encoding"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"math/big"
"math/bits"
"strings"
)

const (
Expand All @@ -24,6 +29,16 @@ const (
_ uint = -(maxWords & ^(4 | 8)) // maxWords is 4 or 8.
)

// Compile time interface checks
var (
_ driver.Valuer = (*Int)(nil)
_ sql.Scanner = (*Int)(nil)
_ encoding.TextMarshaler = (*Int)(nil)
_ encoding.TextUnmarshaler = (*Int)(nil)
_ json.Marshaler = (*Int)(nil)
_ json.Unmarshaler = (*Int)(nil)
)

// ToBig returns a big.Int version of z.
func (z *Int) ToBig() *big.Int {
b := new(big.Int)
Expand Down Expand Up @@ -51,12 +66,24 @@ func FromBig(b *big.Int) (*Int, bool) {
return z, overflow
}

// SetFromHex sets z from the given string, interpreted as a hexadecimal number.
// OBS! This method is _not_ strictly identical to the (*big.Int).SetString(..., 16) method.
// Notable differences:
// - This method _require_ "0x" or "0X" prefix.
// - This method does not accept zero-prefixed hex, e.g. "0x0001"
// - This method does not accept underscore input, e.g. "100_000",
// - This method does not accept negative zero as valid, e.g "-0x0",
// - (this method does not accept any negative input as valid)
func (z *Int) SetFromHex(hex string) error {
z.Clear()
return z.fromHex(hex)
}

// fromHex is the internal implementation of parsing a hex-string.
func (z *Int) fromHex(hex string) error {
if err := checkNumberS(hex); err != nil {
return err
}

if len(hex) > 66 {
return ErrBig256Range
}
Expand Down Expand Up @@ -92,6 +119,7 @@ func FromHex(hex string) (*Int, error) {

// UnmarshalText implements encoding.TextUnmarshaler
func (z *Int) UnmarshalText(input []byte) error {
z.Clear()
return z.fromHex(string(input))
}

Expand Down Expand Up @@ -147,7 +175,6 @@ func (z *Int) SetFromBig(b *big.Int) bool {
// specification of minimum digits precision, output field
// width, space or zero padding, and '-' for left or right
// justification.
//
func (z *Int) Format(s fmt.State, ch rune) {
z.ToBig().Format(s, ch)
}
Expand Down Expand Up @@ -470,6 +497,11 @@ func (z *Int) MarshalText() ([]byte, error) {
return []byte(z.Hex()), nil
}

// MarshalJSON implements json.Marshaler.
func (z *Int) MarshalJSON() ([]byte, error) {
return []byte(`"` + z.Hex() + `"`), nil
}

// UnmarshalJSON implements json.Unmarshaler.
func (z *Int) UnmarshalJSON(input []byte) error {
if len(input) < 2 || input[0] != '"' || input[len(input)-1] != '"' {
Expand Down Expand Up @@ -525,6 +557,53 @@ func (z *Int) Hex() string {
return string(output[64-nibbles:])
}

// Scan implements the database/sql Scanner interface.
// It decodes a string, because that is what postgres uses for its numeric type
func (dst *Int) Scan(src interface{}) error {
if src == nil {
dst.Clear()
return nil
}
switch src := src.(type) {
case string:
splt := strings.SplitN(src, "e", 2)
if len(splt) == 1 {
return dst.SetFromDecimal(src)
}
if err := dst.SetFromDecimal(splt[0]); err != nil {
return err
}
if splt[1] == "0" {
return nil
}
exp := new(Int)
if err := exp.SetFromDecimal(splt[1]); err != nil {
return err
}
if !exp.IsUint64() || exp.Uint64() > uint64(len(twoPow256Sub1)) {
return ErrBig256Range
}
exp.Exp(NewInt(10), exp)
_, overflow := dst.MulOverflow(dst, exp)
if overflow {
return ErrBig256Range
}
return nil
case []byte:
return dst.SetFromDecimal(string(src))
}
return fmt.Errorf("cannot scan %T", src)
}

// Value implements the database/sql/driver Valuer interface.
// It encodes a base 10 string.
// In Postgres, this will work with both integer and the Numeric/Decimal types
// In MariaDB/MySQL, this will work with the Numeric/Decimal types up to 65 digits, however any more and you should use either VarChar or Char(79)
// In SqLite, use TEXT
func (src *Int) Value() (driver.Value, error) {
return src.ToBig().String(), nil
}

var (
ErrEmptyString = errors.New("empty hex string")
ErrSyntax = errors.New("invalid hex string")
Expand Down
Loading

0 comments on commit c6c6f8d

Please sign in to comment.