uint256: Add text formatting support.
This adds full support for formatting a uint256 along with associated tests to ensure proper functionality. It includes a fmt.Formatter that supports the full suite of the fmt package format flags for integral types, a fmt.Stringer, and a separate Text method that accepts an output base directly and produces the relevant output with fewer allocations than using the standard fmt methods. This is part of a series of commits to fully implement the uint256 package.
This commit is contained in:
parent
37b4c65dcd
commit
4f7eaa879f
@ -6,7 +6,11 @@
|
||||
// integer arithmetic.
|
||||
package uint256
|
||||
|
||||
import "math/bits"
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// References:
|
||||
// [TAOCP2]: The Art of Computer Programming, Volume 2.
|
||||
@ -22,16 +26,13 @@ var (
|
||||
// fixed-precision arithmetic. All operations are performed modulo 2^256, so
|
||||
// callers may rely on "wrap around" semantics.
|
||||
//
|
||||
// It currently implements the primary arithmetic operations (addition,
|
||||
// subtraction, multiplication, squaring, division, negation), bitwise
|
||||
// operations (lsh, rsh, not, or, and, xor), comparison operations (equals,
|
||||
// less, greater, cmp), interpreting and producing big and little endian bytes,
|
||||
// and other convenience methods such as determining the minimum number of bits
|
||||
// required to represent the current value and whether or not the value can be
|
||||
// represented as a uint64 without loss of precision.
|
||||
//
|
||||
// Future commits will implement other convenience methods such as text
|
||||
// formatting with base conversion.
|
||||
// It implements the primary arithmetic operations (addition, subtraction,
|
||||
// multiplication, squaring, division, negation), bitwise operations (lsh, rsh,
|
||||
// not, or, and, xor), comparison operations (equals, less, greater, cmp),
|
||||
// interpreting and producing big and little endian bytes, and other convenience
|
||||
// methods such as determining the minimum number of bits required to represent
|
||||
// the current value, whether or not the value can be represented as a uint64
|
||||
// without loss of precision, and text formatting with base conversion.
|
||||
type Uint256 struct {
|
||||
// The uint256 is represented as 4 unsigned 64-bit integers in base 2^64.
|
||||
//
|
||||
@ -1341,3 +1342,391 @@ func (n *Uint256) BitLen() uint16 {
|
||||
}
|
||||
return uint16(bits.Len64(n.n[0]))
|
||||
}
|
||||
|
||||
// bitsPerInternalWord is the number of bits used for each internal word of the
|
||||
// uint256.
|
||||
const bitsPerInternalWord = 64
|
||||
|
||||
// toBin converts the uint256 to its string representation in base 2.
|
||||
func (n *Uint256) toBin() []byte {
|
||||
if n.IsZero() {
|
||||
return []byte("0")
|
||||
}
|
||||
|
||||
// Create space for the max possible number of output digits.
|
||||
maxOutDigits := n.BitLen()
|
||||
result := make([]byte, maxOutDigits)
|
||||
|
||||
// Convert each internal base 2^64 word to base 2 from least to most
|
||||
// significant. Since the value is guaranteed to be non-zero per a previous
|
||||
// check, there will always be a nonzero most-significant word. Also, note
|
||||
// that no partial digit handling is needed in this case because the shift
|
||||
// amount evenly divides the bits per internal word.
|
||||
const shift = 1
|
||||
const mask = 1<<shift - 1
|
||||
const digitsPerInternalWord = bitsPerInternalWord
|
||||
outputIdx := maxOutDigits - 1
|
||||
numInputWords := n.numDigits()
|
||||
inputWord := n.n[0]
|
||||
for inputIdx := 1; inputIdx < numInputWords; inputIdx++ {
|
||||
for i := 0; i < digitsPerInternalWord; i++ {
|
||||
result[outputIdx] = '0' + byte(inputWord&mask)
|
||||
inputWord >>= shift
|
||||
outputIdx--
|
||||
}
|
||||
inputWord = n.n[inputIdx]
|
||||
}
|
||||
for inputWord != 0 {
|
||||
result[outputIdx] = '0' + byte(inputWord&mask)
|
||||
inputWord >>= shift
|
||||
outputIdx--
|
||||
}
|
||||
|
||||
return result[outputIdx+1:]
|
||||
}
|
||||
|
||||
// toOctal converts the uint256 to its string representation in base 8.
|
||||
func (n *Uint256) toOctal() []byte {
|
||||
if n.IsZero() {
|
||||
return []byte("0")
|
||||
}
|
||||
|
||||
// Create space for the max possible number of output digits using the fact
|
||||
// that 3 bits converts directly to a single octal digit.
|
||||
maxOutDigits := (n.BitLen() + 2) / 3
|
||||
result := make([]byte, maxOutDigits)
|
||||
|
||||
// Convert each internal base 2^64 word to base 8 from least to most
|
||||
// significant. Since the value is guaranteed to be non-zero per a previous
|
||||
// check, there will always be a nonzero most-significant word. Also, note
|
||||
// that partial digit handling is needed in this case because the shift
|
||||
// amount does not evenly divide the bits per internal word.
|
||||
const shift = 3
|
||||
const mask = 1<<shift - 1
|
||||
unconvertedBits := bitsPerInternalWord
|
||||
outputIdx := maxOutDigits - 1
|
||||
numInputWords := n.numDigits()
|
||||
inputWord := n.n[0]
|
||||
for inputIdx := 1; inputIdx < numInputWords; inputIdx++ {
|
||||
// Convert full digits.
|
||||
for ; unconvertedBits >= shift; unconvertedBits -= shift {
|
||||
result[outputIdx] = '0' + byte(inputWord&mask)
|
||||
inputWord >>= shift
|
||||
outputIdx--
|
||||
}
|
||||
|
||||
// Move to the next input word when there are not any remaining
|
||||
// unconverted bits that need to be handled.
|
||||
if unconvertedBits == 0 {
|
||||
inputWord = n.n[inputIdx]
|
||||
unconvertedBits = bitsPerInternalWord
|
||||
continue
|
||||
}
|
||||
|
||||
// Account for the remaining unconverted bits from the current word and
|
||||
// the bits needed from the next word to form a full digit for the next
|
||||
// digit.
|
||||
inputWord |= n.n[inputIdx] << unconvertedBits
|
||||
result[outputIdx] = '0' + byte(inputWord&mask)
|
||||
outputIdx--
|
||||
|
||||
// Move to the next input word while accounting for the bits already
|
||||
// consumed above by shifting it and updating the unconverted bits
|
||||
// accordingly.
|
||||
inputWord = n.n[inputIdx] >> (shift - unconvertedBits)
|
||||
unconvertedBits = bitsPerInternalWord - (shift - unconvertedBits)
|
||||
}
|
||||
for inputWord != 0 {
|
||||
result[outputIdx] = '0' + byte(inputWord&mask)
|
||||
inputWord >>= shift
|
||||
outputIdx--
|
||||
}
|
||||
|
||||
return result[outputIdx+1:]
|
||||
}
|
||||
|
||||
// maxPow10ForInternalWord is the maximum power of 10 that will fit into an
|
||||
// internal word. It is the value 10^floor(64 / log2(10)) and is used when
|
||||
// converting to base 10 in order to significantly reduce the number of
|
||||
// divisions needed.
|
||||
var maxPow10ForInternalWord = new(Uint256).SetUint64(1e19)
|
||||
|
||||
// toDecimal converts the uint256 to its string representation in base 10.
|
||||
func (n *Uint256) toDecimal() []byte {
|
||||
if n.IsZero() {
|
||||
return []byte("0")
|
||||
}
|
||||
|
||||
// Create space for the max possible number of output digits.
|
||||
//
|
||||
// Note that the actual total number of output digits is usually calculated
|
||||
// as:
|
||||
// floor(log2(n) / log2(base)) + 1
|
||||
//
|
||||
// However, in order to avoid more expensive calculation of the full log2 of
|
||||
// the value, the code below instead calculates a value that might overcount
|
||||
// by a max of one digit and trims the result as needed via the following
|
||||
// slightly modified version of the formula:
|
||||
// floor(bitlen(n) / log2(base)) + 1
|
||||
//
|
||||
// The modified formula is guaranteed to be large enough because:
|
||||
// (a) floor(log2(x)) ≤ log2(x) ≤ floor(log2(x)) + 1
|
||||
// (b) bitlen(x) = floor(log2(x)) + 1
|
||||
//
|
||||
// Which implies:
|
||||
// (c) floor(log2(n) / log2(base)) ≤ floor(floor(log2(n))+1) / log2(base))
|
||||
// (d) floor(log2(n) / log2(base)) ≤ floor(bitlen(n)) / log2(base))
|
||||
//
|
||||
// Note that (c) holds since the left hand side of the inequality has a
|
||||
// dividend that is ≤ the right hand side dividend due to (a) while the
|
||||
// divisor is = the right hand side divisor, and then (d) is equal to (c)
|
||||
// per (b). Adding 1 to both sides of (d) yields an inequality where the
|
||||
// left hand side is the typical formula and the right hand side is the
|
||||
// modified formula thereby proving it will never under count.
|
||||
const log2Of10 = 3.321928094887362
|
||||
maxOutDigits := uint8(float64(n.BitLen())/log2Of10) + 1
|
||||
result := make([]byte, maxOutDigits)
|
||||
|
||||
// Convert each internal base 2^64 word to base 10 from least to most
|
||||
// significant. Since the value is guaranteed to be non-zero per a previous
|
||||
// check, there will always be a nonzero most-significant word. Also, note
|
||||
// that partial digit handling is needed in this case because the shift
|
||||
// amount does not evenly divide the bits per internal word.
|
||||
var quo, rem, t Uint256
|
||||
var r uint64
|
||||
outputIdx := maxOutDigits - 1
|
||||
quo = *n
|
||||
for !quo.IsZero() {
|
||||
rem.Set(&quo)
|
||||
quo.Div(maxPow10ForInternalWord)
|
||||
t.Mul2(&quo, maxPow10ForInternalWord)
|
||||
inputWord := rem.Sub(&t).Uint64()
|
||||
for inputWord != 0 {
|
||||
inputWord, r = inputWord/10, inputWord%10
|
||||
result[outputIdx] = '0' + byte(r)
|
||||
outputIdx--
|
||||
}
|
||||
}
|
||||
|
||||
return result[outputIdx+1:]
|
||||
}
|
||||
|
||||
// toHex converts the uint256 to its string representation in lowercase base 16.
|
||||
func (n *Uint256) toHex() []byte {
|
||||
if n.IsZero() {
|
||||
return []byte("0")
|
||||
}
|
||||
|
||||
// Create space for the max possible number of output digits using the fact
|
||||
// that a nibble converts directly to a single hex digit.
|
||||
maxOutDigits := (n.BitLen() + 3) / 4
|
||||
result := make([]byte, maxOutDigits)
|
||||
|
||||
// Convert each internal base 2^64 word to base 16 from least to most
|
||||
// significant. Since the value is guaranteed to be non-zero per a
|
||||
// previous check, there will always be a nonzero most-significant word.
|
||||
// Also, note that no partial digit handling is needed in this case
|
||||
// because the shift amount evenly divides the bits per internal word.
|
||||
const alphabet = "0123456789abcdef"
|
||||
const shift = 4
|
||||
const mask = 1<<shift - 1
|
||||
const digitsPerInternalWord = bitsPerInternalWord / shift
|
||||
outputIdx := maxOutDigits - 1
|
||||
numInputWords := n.numDigits()
|
||||
inputWord := n.n[0]
|
||||
for inputIdx := 1; inputIdx < numInputWords; inputIdx++ {
|
||||
for i := 0; i < digitsPerInternalWord; i++ {
|
||||
result[outputIdx] = alphabet[inputWord&mask]
|
||||
inputWord >>= shift
|
||||
outputIdx--
|
||||
}
|
||||
inputWord = n.n[inputIdx]
|
||||
}
|
||||
for inputWord != 0 {
|
||||
result[outputIdx] = alphabet[inputWord&mask]
|
||||
inputWord >>= shift
|
||||
outputIdx--
|
||||
}
|
||||
|
||||
return result[outputIdx+1:]
|
||||
}
|
||||
|
||||
// OutputBase represents a specific base to use for the string representation of
|
||||
// a number.
|
||||
type OutputBase int
|
||||
|
||||
// These constants define the supported output bases.
|
||||
const (
|
||||
// OutputBaseBinary indicates a string representation of a uint256 in
|
||||
// base 2.
|
||||
OutputBaseBinary OutputBase = 2
|
||||
|
||||
// OutputBaseOctal indicates a string representation of a uint256 in base 8.
|
||||
OutputBaseOctal OutputBase = 8
|
||||
|
||||
// OutputBaseDecimal indicates a string representation of a uint256 in base
|
||||
// 10.
|
||||
OutputBaseDecimal OutputBase = 10
|
||||
|
||||
// OutputBaseHex indicates a string representation of a uint256 in base 16.
|
||||
OutputBaseHex OutputBase = 16
|
||||
)
|
||||
|
||||
// Text returns the string representation of the uint256 in the given base which
|
||||
// must be on of the supported bases as defined by the OutputBase type.
|
||||
//
|
||||
// It will return "<nil>" when the uint256 pointer is nil and a message that
|
||||
// indicates the base is not supported along with the value in base 10 in the
|
||||
// case the caller goes out of its way to call it with an invalid base.
|
||||
func (n *Uint256) Text(base OutputBase) string {
|
||||
if n == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
switch base {
|
||||
case OutputBaseHex:
|
||||
return string(n.toHex())
|
||||
case OutputBaseDecimal:
|
||||
return string(n.toDecimal())
|
||||
case OutputBaseBinary:
|
||||
return string(n.toBin())
|
||||
case OutputBaseOctal:
|
||||
return string(n.toOctal())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("base %d not supported (Uint256=%s)", int(base), n)
|
||||
}
|
||||
|
||||
// String returns the scalar as a human-readable decimal string.
|
||||
func (n Uint256) String() string {
|
||||
return string(n.toDecimal())
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter. It accepts the following format verbs:
|
||||
//
|
||||
// 'v' default format which is decimal
|
||||
// 's' default string format which is decimal
|
||||
// 'b' binary
|
||||
// 'o' octal with 0 prefix when accompanied by #
|
||||
// 'O' octal with 0o prefix
|
||||
// 'd' decimal
|
||||
// 'x' lowercase hexadecimal
|
||||
// 'X' uppercase hexadecimal
|
||||
//
|
||||
// It also supports the full suite of the fmt package format flags for integral
|
||||
// types:
|
||||
//
|
||||
// '#' output base prefix:
|
||||
// binary: 0b (%#b)
|
||||
// octal: 0 (%#o)
|
||||
// hex: 0x (%#x) or 0X (%#X)
|
||||
// '-' pad with spaces on the right (left-justify field)
|
||||
// '0' pad with leading zeros rather than spaces
|
||||
//
|
||||
// Finally, it supports specification of the minimum number of digits
|
||||
// (precision) and output field width. Examples:
|
||||
//
|
||||
// %#.64x default width, precision 64, lowercase hex with 0x prefix
|
||||
// %256b width 256, default precision, binary with leading zeros
|
||||
// %12.3O width 12, precision 3, octal with 0o prefix
|
||||
func (n Uint256) Format(s fmt.State, ch rune) {
|
||||
// Determine output digits for the output base.
|
||||
var digits []byte
|
||||
switch ch {
|
||||
case 'b':
|
||||
digits = n.toBin()
|
||||
case 'o', 'O':
|
||||
digits = n.toOctal()
|
||||
case 'd', 's', 'v':
|
||||
digits = n.toDecimal()
|
||||
case 'x':
|
||||
digits = n.toHex()
|
||||
case 'X':
|
||||
digits = n.toHex()
|
||||
for i, d := range digits {
|
||||
if d >= 'a' && d <= 'f' {
|
||||
digits[i] = 'A' + (d - 'a')
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Fprintf(s, "%%!%c(Uint256=%s)", ch, n.String())
|
||||
return
|
||||
}
|
||||
|
||||
// Determine prefix characters for the output base as needed.
|
||||
var prefix string
|
||||
if s.Flag('#') {
|
||||
switch ch {
|
||||
case 'b':
|
||||
prefix = "0b"
|
||||
case 'o':
|
||||
prefix = "0"
|
||||
case 'x':
|
||||
prefix = "0x"
|
||||
case 'X':
|
||||
prefix = "0X"
|
||||
}
|
||||
}
|
||||
if ch == 'O' {
|
||||
prefix = "0o"
|
||||
}
|
||||
|
||||
// Determine how many zeros to pad with based on whether or not a minimum
|
||||
// number of digits to output is specified.
|
||||
//
|
||||
// Also, do not output anything when the minimum number of digits to output
|
||||
// is zero and the uint256 is zero.
|
||||
//
|
||||
// Note that the zero padding might also be set below when the zero pad
|
||||
// ('0') flag is specified and neither a precision nor the right justify
|
||||
// ('-') flag is specified.
|
||||
var zeroPad int
|
||||
minDigits, isPrecisionSet := s.Precision()
|
||||
if isPrecisionSet {
|
||||
switch {
|
||||
case len(digits) < minDigits:
|
||||
zeroPad = minDigits - len(digits)
|
||||
case minDigits == 0 && n.IsZero():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the left or right padding depending on whether or not a minimum
|
||||
// number of characters to output is specified as well as the flags.
|
||||
//
|
||||
// A '-' flag indicates the output should be right justified and takes
|
||||
// precedence over the zero pad ('0') flag. Per the above, the zero pad
|
||||
// flag is ignored when a minimum number of digits is specified.
|
||||
var leftPad, rightPad int
|
||||
digitsPlusPrefixLen := len(prefix) + zeroPad + len(digits)
|
||||
width, isWidthSet := s.Width()
|
||||
if isWidthSet && digitsPlusPrefixLen < width {
|
||||
switch pad := width - digitsPlusPrefixLen; {
|
||||
case s.Flag('-'):
|
||||
rightPad = pad
|
||||
case s.Flag('0') && !isPrecisionSet:
|
||||
zeroPad = pad
|
||||
default:
|
||||
leftPad = pad
|
||||
}
|
||||
}
|
||||
|
||||
// Produce the following output:
|
||||
//
|
||||
// [left pad][prefix][zero pad][digits][right pad]
|
||||
var buf bytes.Buffer
|
||||
buf.Grow(leftPad + len(prefix) + zeroPad + len(digits) + rightPad)
|
||||
for i := 0; i < leftPad; i++ {
|
||||
buf.WriteRune(' ')
|
||||
}
|
||||
buf.WriteString(prefix)
|
||||
for i := 0; i < zeroPad; i++ {
|
||||
buf.WriteRune('0')
|
||||
}
|
||||
buf.Write(digits)
|
||||
for i := 0; i < rightPad; i++ {
|
||||
buf.WriteRune(' ')
|
||||
}
|
||||
s.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
@ -3397,3 +3397,648 @@ func TestUint256BitLen(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestUint256Text ensures the converting uint256s to the supported output bases
|
||||
// via the Text method works as intended that that it also handles nil pointers
|
||||
// as intended.
|
||||
func TestUint256Text(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string // test description
|
||||
n string // hex encoded test value
|
||||
base OutputBase // base to output
|
||||
want string // expected output
|
||||
}{{
|
||||
name: "binary",
|
||||
n: "01abc",
|
||||
base: OutputBaseBinary,
|
||||
want: "1101010111100",
|
||||
}, {
|
||||
name: "octal",
|
||||
n: "01abc",
|
||||
base: OutputBaseOctal,
|
||||
want: "15274",
|
||||
}, {
|
||||
name: "decimal",
|
||||
n: "01abc",
|
||||
base: OutputBaseDecimal,
|
||||
want: "6844",
|
||||
}, {
|
||||
name: "hex",
|
||||
n: "01abc",
|
||||
base: OutputBaseHex,
|
||||
want: "1abc",
|
||||
}, {
|
||||
name: "unsupported base",
|
||||
n: "01abc",
|
||||
base: OutputBase(100),
|
||||
want: "base 100 not supported (Uint256=6844)",
|
||||
}}
|
||||
|
||||
var nNil *Uint256
|
||||
for _, test := range tests {
|
||||
// Ensure nil pointers are handled as expected.
|
||||
got := nNil.Text(test.base)
|
||||
want := "<nil>"
|
||||
if got != want {
|
||||
t.Errorf("%q: unexpected nil result -- got: %s, want: %s",
|
||||
test.name, got, want)
|
||||
}
|
||||
|
||||
// Parse test hex and ensure expected output for test output base.
|
||||
n := hexToUint256(test.n)
|
||||
got = n.Text(test.base)
|
||||
if got != test.want {
|
||||
t.Errorf("%q: unexpected result -- got: %s, want: %s", test.name,
|
||||
got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestUint256Format ensures that formatting a uint256 via its fmt.Formatter
|
||||
// works as intended including things such as the supported output bases,
|
||||
// flags for alternate format (e.g. output bases, leading zeros), padding, and
|
||||
// precision.
|
||||
func TestUint256Format(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string // test description
|
||||
n string // hex encoded test value
|
||||
fmt string // format string
|
||||
want string // expected output
|
||||
}{{
|
||||
// ---------------------------------------------------------------------
|
||||
// Zero for all supported bases with and without base prefix.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
name: "0 binary",
|
||||
n: "0",
|
||||
fmt: "%b",
|
||||
want: "0",
|
||||
}, {
|
||||
name: "0 binary with base prefix",
|
||||
n: "0",
|
||||
fmt: "%#b",
|
||||
want: "0b0",
|
||||
}, {
|
||||
name: "0 octal",
|
||||
n: "0",
|
||||
fmt: "%o",
|
||||
want: "0",
|
||||
}, {
|
||||
name: "0 octal with '0' base prefix",
|
||||
n: "0",
|
||||
fmt: "%#o",
|
||||
want: "00",
|
||||
}, {
|
||||
name: "0 octal with '0o' base prefix",
|
||||
n: "0",
|
||||
fmt: "%O",
|
||||
want: "0o0",
|
||||
}, {
|
||||
name: "0 decimal",
|
||||
n: "0",
|
||||
fmt: "%d",
|
||||
want: "0",
|
||||
}, {
|
||||
name: "0 decimal with base prefix (no effect)",
|
||||
n: "0",
|
||||
fmt: "%#d",
|
||||
want: "0",
|
||||
}, {
|
||||
name: "0 hex",
|
||||
n: "0",
|
||||
fmt: "%x",
|
||||
want: "0",
|
||||
}, {
|
||||
name: "0 hex with lowercase base prefix",
|
||||
n: "0",
|
||||
fmt: "%#x",
|
||||
want: "0x0",
|
||||
}, {
|
||||
name: "0 hex with uppercase base prefix",
|
||||
n: "0",
|
||||
fmt: "%#X",
|
||||
want: "0X0",
|
||||
}, {
|
||||
// ---------------------------------------------------------------------
|
||||
// Binary output for various values and base prefix combinations.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
name: "2^8 - 1 binary",
|
||||
n: "ff",
|
||||
fmt: "%b",
|
||||
want: "11111111",
|
||||
}, {
|
||||
name: "2^8 - 1 binary with base prefix",
|
||||
n: "ff",
|
||||
fmt: "%#b",
|
||||
want: "0b11111111",
|
||||
}, {
|
||||
name: "2^16 - 1 binary",
|
||||
n: "ffff",
|
||||
fmt: "%b",
|
||||
want: "1111111111111111",
|
||||
}, {
|
||||
name: "2^32 - 1 binary",
|
||||
n: "ffffffff",
|
||||
fmt: "%b",
|
||||
want: "11111111111111111111111111111111",
|
||||
}, {
|
||||
name: "2^64 - 1 binary",
|
||||
n: "ffffffffffffffff",
|
||||
fmt: "%b",
|
||||
want: "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
}, {
|
||||
name: "2^128 - 1 binary",
|
||||
n: "ffffffffffffffffffffffffffffffff",
|
||||
fmt: "%b",
|
||||
want: "1111111111111111111111111111111111111111111111111111111111111111" +
|
||||
"1111111111111111111111111111111111111111111111111111111111111111",
|
||||
}, {
|
||||
name: "2^256 - 1 binary",
|
||||
n: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
fmt: "%b",
|
||||
want: "1111111111111111111111111111111111111111111111111111111111111111" +
|
||||
"1111111111111111111111111111111111111111111111111111111111111111" +
|
||||
"1111111111111111111111111111111111111111111111111111111111111111" +
|
||||
"1111111111111111111111111111111111111111111111111111111111111111",
|
||||
}, {
|
||||
// ---------------------------------------------------------------------
|
||||
// Octal output for various values and base prefix combinations.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
name: "2^8 - 1 octal",
|
||||
n: "ff",
|
||||
fmt: "%o",
|
||||
want: "377",
|
||||
}, {
|
||||
name: "2^8 - 1 octal with '0' base prefix",
|
||||
n: "ff",
|
||||
fmt: "%#o",
|
||||
want: "0377",
|
||||
}, {
|
||||
name: "2^16 - 1 octal",
|
||||
n: "ffff",
|
||||
fmt: "%o",
|
||||
want: "177777",
|
||||
}, {
|
||||
name: "2^32 - 1 octal",
|
||||
n: "ffffffff",
|
||||
fmt: "%o",
|
||||
want: "37777777777",
|
||||
}, {
|
||||
name: "2^64 - 1 octal with '0o' base prefix",
|
||||
n: "ffffffffffffffff",
|
||||
fmt: "%O",
|
||||
want: "0o1777777777777777777777",
|
||||
}, {
|
||||
name: "2^128 - 1 octal",
|
||||
n: "ffffffffffffffffffffffffffffffff",
|
||||
fmt: "%o",
|
||||
want: "3777777777777777777777777777777777777777777",
|
||||
}, {
|
||||
name: "2^256 - 1 octal",
|
||||
n: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
fmt: "%o",
|
||||
want: "17777777777777777777777777777777777777777777777777777777777777" +
|
||||
"777777777777777777777777",
|
||||
}, {
|
||||
// ---------------------------------------------------------------------
|
||||
// Decimal output for various values via %d, %s, and %v.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
name: "2^8 - 1 decimal",
|
||||
n: "ff",
|
||||
fmt: "%d",
|
||||
want: "255",
|
||||
}, {
|
||||
name: "2^16 - 1 decimal",
|
||||
n: "ffff",
|
||||
fmt: "%d",
|
||||
want: "65535",
|
||||
}, {
|
||||
name: "2^32 - 1 decimal",
|
||||
n: "ffffffff",
|
||||
fmt: "%d",
|
||||
want: "4294967295",
|
||||
}, {
|
||||
name: "2^64 - 1 decimal",
|
||||
n: "ffffffffffffffff",
|
||||
fmt: "%d",
|
||||
want: "18446744073709551615",
|
||||
}, {
|
||||
name: "2^128 - 1 decimal",
|
||||
n: "ffffffffffffffffffffffffffffffff",
|
||||
fmt: "%d",
|
||||
want: "340282366920938463463374607431768211455",
|
||||
}, {
|
||||
name: "2^256 - 1 decimal",
|
||||
n: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
fmt: "%d",
|
||||
want: "11579208923731619542357098500868790785326998466564056403945758" +
|
||||
"4007913129639935",
|
||||
}, {
|
||||
name: "10^9 decimal via %s",
|
||||
n: "3b9aca00",
|
||||
fmt: "%s",
|
||||
want: "1000000000",
|
||||
}, {
|
||||
name: "123456789 decimal via %v",
|
||||
n: "75bcd15",
|
||||
fmt: "%v",
|
||||
want: "123456789",
|
||||
}, {
|
||||
// ---------------------------------------------------------------------
|
||||
// Hex output for various values and base prefix combinations.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
name: "2^8 - 1 hex",
|
||||
n: "ff",
|
||||
fmt: "%x",
|
||||
want: "ff",
|
||||
}, {
|
||||
name: "2^8 - 1 hex with lowercase base prefix",
|
||||
n: "ff",
|
||||
fmt: "%#x",
|
||||
want: "0xff",
|
||||
}, {
|
||||
name: "2^8 - 1 hex with uppercase base prefix",
|
||||
n: "ff",
|
||||
fmt: "%#X",
|
||||
want: "0XFF",
|
||||
}, {
|
||||
name: "2^16 - 1 hex",
|
||||
n: "ffff",
|
||||
fmt: "%x",
|
||||
want: "ffff",
|
||||
}, {
|
||||
name: "2^32 - 1 hex",
|
||||
n: "ffffffff",
|
||||
fmt: "%x",
|
||||
want: "ffffffff",
|
||||
}, {
|
||||
name: "2^64 - 1 hex",
|
||||
n: "ffffffffffffffff",
|
||||
fmt: "%x",
|
||||
want: "ffffffffffffffff",
|
||||
}, {
|
||||
name: "2^128 - 1 hex with lowercase base prefix",
|
||||
n: "ffffffffffffffffffffffffffffffff",
|
||||
fmt: "%#x",
|
||||
want: "0xffffffffffffffffffffffffffffffff",
|
||||
}, {
|
||||
name: "2^256 - 1 hex",
|
||||
n: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
fmt: "%x",
|
||||
want: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
}, {
|
||||
// ---------------------------------------------------------------------
|
||||
// Min precision with and without base prefix / zero pad flag.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
name: "binary min 8 bits w/ val < 8 bits",
|
||||
n: "a",
|
||||
fmt: "%.8b",
|
||||
want: "00001010",
|
||||
}, {
|
||||
name: "octal min 6 digits w/ val < 6 digits",
|
||||
n: "4551",
|
||||
fmt: "%.6o",
|
||||
want: "042521",
|
||||
}, {
|
||||
name: "decimal min 5 digits w/ val < 5 digits",
|
||||
n: "270f",
|
||||
fmt: "%.5d",
|
||||
want: "09999",
|
||||
}, {
|
||||
name: "hex min 12 digits w/ val < 12 digits",
|
||||
n: "abcdef",
|
||||
fmt: "%.12x",
|
||||
want: "000000abcdef",
|
||||
}, {
|
||||
name: "binary min 8 bits w/ val < 8 bits zero pad",
|
||||
n: "a",
|
||||
fmt: "%0.8b",
|
||||
want: "00001010",
|
||||
}, {
|
||||
name: "octal min 6 digits w/ val 6 digits zero pad",
|
||||
n: "8555",
|
||||
fmt: "%0.6o",
|
||||
want: "102525",
|
||||
}, {
|
||||
name: "decimal min 5 digits w/ val 5 digits zero pad",
|
||||
n: "2710",
|
||||
fmt: "%0.5d",
|
||||
want: "10000",
|
||||
}, {
|
||||
name: "hex min 12 digits w/ val 12 digits",
|
||||
n: "abcdefabcdef",
|
||||
fmt: "%.12x",
|
||||
want: "abcdefabcdef",
|
||||
}, {
|
||||
name: "binary min 8 bits w/ val > 8 bits zero pad",
|
||||
n: "100",
|
||||
fmt: "%0.8b",
|
||||
want: "100000000",
|
||||
}, {
|
||||
name: "octal min 6 digits w/ val > 6 digits",
|
||||
n: "40000",
|
||||
fmt: "%.6o",
|
||||
want: "1000000",
|
||||
}, {
|
||||
name: "decimal min 5 digits w/ val > 5 digits",
|
||||
n: "186a0",
|
||||
fmt: "%.5d",
|
||||
want: "100000",
|
||||
}, {
|
||||
name: "hex min 12 digits w/ val > 12 digits",
|
||||
n: "1000000000000",
|
||||
fmt: "%.12x",
|
||||
want: "1000000000000",
|
||||
}, {
|
||||
name: "binary min 8 bits w/ val < 8 bits and base prefix",
|
||||
n: "a",
|
||||
fmt: "%#.8b",
|
||||
want: "0b00001010",
|
||||
}, {
|
||||
name: "octal min 6 digits w/ val < 6 digits and '0' base prefix",
|
||||
n: "4551",
|
||||
fmt: "%#.6o",
|
||||
want: "0042521",
|
||||
}, {
|
||||
name: "octal min 6 digits w/ val < 6 digits and '0o' base prefix",
|
||||
n: "4551",
|
||||
fmt: "%0.6O",
|
||||
want: "0o042521",
|
||||
}, {
|
||||
name: "decimal min 5 digits w/ val < 5 digits and base prefix",
|
||||
n: "270f",
|
||||
fmt: "%#.5d",
|
||||
want: "09999",
|
||||
}, {
|
||||
name: "hex min 12 digits w/ val < 12 digits and base prefix lowercase",
|
||||
n: "abcdef",
|
||||
fmt: "%#.12x",
|
||||
want: "0x000000abcdef",
|
||||
}, {
|
||||
name: "hex min 12 digits w/ val < 12 digits and base prefix uppercase",
|
||||
n: "abcdef",
|
||||
fmt: "%#.12X",
|
||||
want: "0X000000ABCDEF",
|
||||
}, {
|
||||
// ---------------------------------------------------------------------
|
||||
// Min width with and without base prefix / zero pad flag.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
name: "binary min width 8 w/ val < 8 bits zero pad",
|
||||
n: "a",
|
||||
fmt: "%08b",
|
||||
want: "00001010",
|
||||
}, {
|
||||
name: "octal min width 6 w/ val < 6 digits zero pad",
|
||||
n: "4551",
|
||||
fmt: "%06o",
|
||||
want: "042521",
|
||||
}, {
|
||||
name: "decimal min width 5 w/ val < 5 digits zero pad",
|
||||
n: "270f",
|
||||
fmt: "%05d",
|
||||
want: "09999",
|
||||
}, {
|
||||
name: "hex min width 12 w/ val < 12 digits zero pad",
|
||||
n: "abcdef",
|
||||
fmt: "%012x",
|
||||
want: "000000abcdef",
|
||||
}, {
|
||||
name: "binary min width 8 w/ val > 8 bits zero pad",
|
||||
n: "100",
|
||||
fmt: "%08b",
|
||||
want: "100000000",
|
||||
}, {
|
||||
name: "octal min width 6 w/ val > 6 digits zero pad",
|
||||
n: "40000",
|
||||
fmt: "%06o",
|
||||
want: "1000000",
|
||||
}, {
|
||||
name: "decimal min width 5 w/ val > 5 digits zero pad",
|
||||
n: "186a0",
|
||||
fmt: "%05d",
|
||||
want: "100000",
|
||||
}, {
|
||||
name: "hex min width 12 w/ val > 12 digits zero pad",
|
||||
n: "1000000000000",
|
||||
fmt: "%012x",
|
||||
want: "1000000000000",
|
||||
}, {
|
||||
name: "binary min width 8 w/ val < 8 bits left pad",
|
||||
n: "a",
|
||||
fmt: "%8b",
|
||||
want: " 1010",
|
||||
}, {
|
||||
name: "octal min width 6 w/ val < 6 digits left pad",
|
||||
n: "4551",
|
||||
fmt: "%6o",
|
||||
want: " 42521",
|
||||
}, {
|
||||
name: "decimal min width 5 w/ val < 5 digits left pad",
|
||||
n: "270f",
|
||||
fmt: "%5d",
|
||||
want: " 9999",
|
||||
}, {
|
||||
name: "hex min width 12 w/ val < 12 digits left pad",
|
||||
n: "abcdef",
|
||||
fmt: "%12x",
|
||||
want: " abcdef",
|
||||
}, {
|
||||
name: "binary min width 8 w/ val > 8 bits left pad",
|
||||
n: "100",
|
||||
fmt: "%8b",
|
||||
want: "100000000",
|
||||
}, {
|
||||
name: "octal min width 6 w/ val > 6 digits left pad",
|
||||
n: "40000",
|
||||
fmt: "%6o",
|
||||
want: "1000000",
|
||||
}, {
|
||||
name: "decimal min width 5 w/ val > 5 digits left pad",
|
||||
n: "186a0",
|
||||
fmt: "%5d",
|
||||
want: "100000",
|
||||
}, {
|
||||
name: "hex min width 12 w/ val > 12 digits left pad",
|
||||
n: "1000000000000",
|
||||
fmt: "%12x",
|
||||
want: "1000000000000",
|
||||
}, {
|
||||
name: "binary min width 8 w/ val < 8 bits right pad",
|
||||
n: "a",
|
||||
fmt: "%-8b",
|
||||
want: "1010 ",
|
||||
}, {
|
||||
name: "octal min width 6 w/ val < 6 digits right pad",
|
||||
n: "4551",
|
||||
fmt: "%-6o",
|
||||
want: "42521 ",
|
||||
}, {
|
||||
name: "decimal min width 5 w/ val < 5 digits right pad",
|
||||
n: "270f",
|
||||
fmt: "%-5d",
|
||||
want: "9999 ",
|
||||
}, {
|
||||
name: "hex min width 12 w/ val < 12 digits right pad",
|
||||
n: "abcdef",
|
||||
fmt: "%-12x",
|
||||
want: "abcdef ",
|
||||
}, {
|
||||
name: "binary min width 8 w/ val > 8 bits right pad",
|
||||
n: "100",
|
||||
fmt: "%-8b",
|
||||
want: "100000000",
|
||||
}, {
|
||||
name: "octal min width 6 w/ val > 6 digits right pad",
|
||||
n: "40000",
|
||||
fmt: "%-6o",
|
||||
want: "1000000",
|
||||
}, {
|
||||
name: "decimal min width 5 w/ val > 5 digits right pad",
|
||||
n: "186a0",
|
||||
fmt: "%-5d",
|
||||
want: "100000",
|
||||
}, {
|
||||
name: "hex min width 12 w/ val > 12 digits right pad",
|
||||
n: "1000000000000",
|
||||
fmt: "%-12x",
|
||||
want: "1000000000000",
|
||||
}, {
|
||||
name: "binary min width 8 w/ val < 8 bits and base prefix left pad",
|
||||
n: "a",
|
||||
fmt: "%#8b",
|
||||
want: " 0b1010",
|
||||
}, {
|
||||
name: "octal min width 6 w/ val < 6 digits and base prefix left pad",
|
||||
n: "4551",
|
||||
fmt: "%#6o",
|
||||
want: "042521",
|
||||
}, {
|
||||
name: "decimal min width 5 w/ val < 5 digits and base prefix left pad",
|
||||
n: "270f",
|
||||
fmt: "%#5d",
|
||||
want: " 9999",
|
||||
}, {
|
||||
name: "hex min width 12 w/ val < 12 digits and base prefix left pad",
|
||||
n: "abcdef",
|
||||
fmt: "%#12x",
|
||||
want: " 0xabcdef",
|
||||
}, {
|
||||
name: "binary min width 8 w/ val > 8 bits and base prefix left pad",
|
||||
n: "100",
|
||||
fmt: "%#8b",
|
||||
want: "0b100000000",
|
||||
}, {
|
||||
name: "octal min width 6 w/ val > 6 digits and base prefix left pad",
|
||||
n: "40000",
|
||||
fmt: "%#6o",
|
||||
want: "01000000",
|
||||
}, {
|
||||
name: "decimal min width 5 w/ val > 5 digits and base prefix left pad",
|
||||
n: "186a0",
|
||||
fmt: "%#5d",
|
||||
want: "100000",
|
||||
}, {
|
||||
name: "hex min width 12 w/ val > 12 digits and base prefix left pad",
|
||||
n: "1000000000000",
|
||||
fmt: "%#12x",
|
||||
want: "0x1000000000000",
|
||||
}, {
|
||||
name: "binary min width 8 w/ val < 8 bits and base prefix zero pad",
|
||||
n: "a",
|
||||
fmt: "%#08b",
|
||||
want: "0b001010",
|
||||
}, {
|
||||
name: "octal min width 6 w/ val < 6 digits and base prefix zero pad",
|
||||
n: "4551",
|
||||
fmt: "%#06o",
|
||||
want: "042521",
|
||||
}, {
|
||||
name: "decimal min width 5 w/ val < 5 digits and base prefix zero pad",
|
||||
n: "270f",
|
||||
fmt: "%#05d",
|
||||
want: "09999",
|
||||
}, {
|
||||
name: "hex min width 12 w/ val < 12 digits and base prefix zero pad",
|
||||
n: "abcdef",
|
||||
fmt: "%#012x",
|
||||
want: "0x0000abcdef",
|
||||
}, {
|
||||
// ---------------------------------------------------------------------
|
||||
// Mixed min width and precision with and without base prefix.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
name: "binary min width 8 min 6 bits w/ val 4 bits",
|
||||
n: "9",
|
||||
fmt: "%8.6b",
|
||||
want: " 001001",
|
||||
}, {
|
||||
name: "octal min width 10 min 3 digits w/ val 2 digits and '0o' prefix",
|
||||
n: "d",
|
||||
fmt: "%10.3O",
|
||||
want: " 0o015",
|
||||
}, {
|
||||
name: "decimal min width 12 min 7 digits w/ val 5 digits",
|
||||
n: "c34f",
|
||||
fmt: "%12.7d",
|
||||
want: " 0049999",
|
||||
}, {
|
||||
name: "hex min width 32 min 16 digits w/ val 9 digits and base prefix",
|
||||
n: "89abcdef0",
|
||||
fmt: "%#32.16x",
|
||||
want: " 0x000000089abcdef0",
|
||||
}, {
|
||||
// ---------------------------------------------------------------------
|
||||
// Zero digits via precision with value == 0.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
name: "binary min 0 implied digits w/ val == 0",
|
||||
n: "0",
|
||||
fmt: "%.b",
|
||||
want: "",
|
||||
}, {
|
||||
name: "octal min 0 digits w/ val == 0",
|
||||
n: "0",
|
||||
fmt: "%.0o",
|
||||
want: "",
|
||||
}, {
|
||||
name: "decimal min 0 digits w/ val == 0 zero pad",
|
||||
n: "0",
|
||||
fmt: "%0.0d",
|
||||
want: "",
|
||||
}, {
|
||||
name: "hex min 0 digits w/ val == 0 and base prefix",
|
||||
n: "0",
|
||||
fmt: "%#.0x",
|
||||
want: "",
|
||||
}, {
|
||||
// ---------------------------------------------------------------------
|
||||
// Misc.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
name: "unsupported format verb",
|
||||
n: "1000",
|
||||
fmt: "%f",
|
||||
want: "%!f(Uint256=4096)",
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
// Parse test hex.
|
||||
n := hexToUint256(test.n)
|
||||
|
||||
got := fmt.Sprintf(test.fmt, n)
|
||||
if got != test.want {
|
||||
t.Errorf("%q: unexpected result -- got: %s, want: %s", test.name,
|
||||
got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user