From 4f7eaa879f5f95aeaa21a8ab63cdc4d3d67be3fa Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sat, 6 Nov 2021 16:07:43 -0500 Subject: [PATCH] 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. --- .../staging/primitives/uint256/uint256.go | 411 ++++++++++- .../primitives/uint256/uint256_test.go | 645 ++++++++++++++++++ 2 files changed, 1045 insertions(+), 11 deletions(-) diff --git a/internal/staging/primitives/uint256/uint256.go b/internal/staging/primitives/uint256/uint256.go index 7ae21811..d7a6629d 100644 --- a/internal/staging/primitives/uint256/uint256.go +++ b/internal/staging/primitives/uint256/uint256.go @@ -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 + 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; 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 + 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 "" 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 "" + } + + 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()) +} diff --git a/internal/staging/primitives/uint256/uint256_test.go b/internal/staging/primitives/uint256/uint256_test.go index d12b08c0..590d8d11 100644 --- a/internal/staging/primitives/uint256/uint256_test.go +++ b/internal/staging/primitives/uint256/uint256_test.go @@ -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 := "" + 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) + } + } +}