# Stint # Copyright 2018 Status Research & Development GmbH # Licensed under either of # # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) # * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) # # at your option. This file may not be copied, modified, or distributed except according to those terms. import ./private/datatypes, ./private/int_negabs, ./private/initialization, ./private/[as_words, as_signed_words], ./int_public, ./uint_public, typetraits, algorithm template static_check_size(T: typedesc[SomeInteger], bits: static[int]) = # To avoid a costly runtime check, we refuse storing into StUint types smaller # than the input type. static: assert sizeof(T) * 8 <= bits, "Input type (" & $T & ") cannot be stored in a multi-precision " & $bits & "-bit integer." & "\nUse a smaller input type instead. This is a compile-time check" & " to avoid a costly run-time bit_length check at each StUint initialization." func stuint*[T: SomeInteger](n: T, bits: static[int]): StUint[bits] {.inline.}= assert n >= 0.T when result.data is UintImpl: static_check_size(T, bits) let r_ptr = cast[ptr array[bits div (sizeof(T) * 8), T]](result.addr) when system.cpuEndian == littleEndian: # "Least significant byte is at the beginning" r_ptr[0] = n else: r_ptr[r_ptr[].len - 1] = n else: result.data = (type result.data)(n) func stint*[T: SomeInteger](n: T, bits: static[int]): StInt[bits] {.inline.}= when result.data is IntImpl: static_check_size(T, bits) let r_ptr = cast[ptr array[bits div (sizeof(T) * 8), T]](result.addr) when system.cpuEndian == littleEndian: # "Least significant byte is at the beginning" if n < 0: r_ptr[0] = -n result = -result else: r_ptr[0] = n else: if n < 0: r_ptr[r_ptr[].len - 1] = -n result = -result else: r_ptr[r_ptr[].len - 1] = n else: result.data = (type result.data)(n) func toInt*(num: Stint or StUint): int {.inline.}= # Returns as int. Result is modulo 2^(sizeof(int) num.data.least_significant_word.int func readHexChar(c: char): int8 {.inline.}= ## Converts an hex char to an int case c of '0'..'9': result = int8 ord(c) - ord('0') of 'a'..'f': result = int8 ord(c) - ord('a') + 10 of 'A'..'F': result = int8 ord(c) - ord('A') + 10 else: raise newException(ValueError, $c & "is not a hexadecimal character") func skipPrefixes(current_idx: var int, str: string, base: range[2..16]) {.inline.} = ## Returns the index of the first meaningful char in `hexStr` by skipping ## "0x" prefix assert current_idx == 0, "skipPrefixes only works for prefixes (position 0 and 1 of the string)" if str[0] == '0': if str[1] in {'x', 'X'}: assert base == 16, "Parsing mismatch, 0x prefix is only valid for a hexadecimal number (base 16)" current_idx = 2 elif str[1] in {'o', 'O'}: assert base == 8, "Parsing mismatch, 0o prefix is only valid for an octal number (base 8)" current_idx = 2 elif str[1] in {'b', 'B'}: assert base == 2, "Parsing mismatch, 0b prefix is only valid for a binary number (base 2)" current_idx = 2 func nextNonBlank(current_idx: var int, s: string) {.inline.} = ## Move the current index, skipping white spaces and "_" characters. const blanks = {' ', '_'} inc current_idx while current_idx < s.len and s[current_idx] in blanks: inc current_idx func readDecChar(c: range['0'..'9']): int {.inline.}= ## Converts a decimal char to an int # specialization without branching for base <= 10. ord(c) - ord('0') func unsafeConv[bits: static[int]](n: range[0..16], T: typedesc[Stint[bits]|Stuint[bits]]): T = ## Fast convert a small int to a Stint/Stuint ## This assumes that the int always fit. ## Purpose: ## - Converting bases in the range [2..16] ## - Converting decimal/hexadecimal in range [0..15] let r_ptr = cast[ptr array[bits div 8, byte]](result.addr) when system.cpuEndian == littleEndian: r_ptr[0] = n.byte else: r_ptr[r_ptr[].len - 1] = n.byte func parse*[bits: static[int]](input: string, T: typedesc[Stuint[bits]], base: static[int]): T = ## Parse a string and store the result in a Stint[bits] or Stuint[bits]. static: assert (base >= 2) and base <= 16, "Only base from 2..16 are supported" # TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26) # TODO: we can special case hex result/input as an array of bytes # and be much faster let radix = unsafeConv(base, T) var curr = 0 # Current index in the string skipPrefixes(curr, input, base) while curr < input.len: # TODO: overflow detection when base <= 10: result = result * radix + input[curr].readDecChar.unsafeConv(T) else: result = result * radix + input[curr].readHexChar.unsafeConv(T) nextNonBlank(curr, input) func parse*[bits: static[int]](input: string, T: typedesc[Stint[bits]], base: static[int]): T = ## Parse a string and store the result in a Stint[bits] or Stuint[bits]. static: assert (base >= 2) and base <= 16, "Only base from 2..16 are supported" # TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26) # TODO: we can special case hex result/input as an array of bytes # and be much faster # For conversion we require overflowing operations (for example for negative hex numbers) let radix = unsafeConv(base, Stuint[bits]) var curr = 0 # Current index in the string isNeg = false no_overflow: Stuint[bits] if input[curr] == '-': assert base == 10, "Negative numbers are only supported with base 10 input." isNeg = true inc curr else: skipPrefixes(curr, input, base) while curr < input.len: # TODO: overflow detection when base <= 10: no_overflow = no_overflow * radix + input[curr].readDecChar.unsafeConv(Stuint[bits]) else: no_overflow = no_overflow * radix + input[curr].readHexChar.unsafeConv(Stuint[bits]) nextNonBlank(curr, input) # TODO: we can't create the lowest int this way if isNeg: result = -cast[Stint[bits]](no_overflow) else: result = cast[Stint[bits]](no_overflow) func parse*[bits: static[int]](input: string, T: typedesc[Stint[bits]|Stuint[bits]]): T {.inline.}= ## Parse a string and store the result in a Stint[bits] or Stuint[bits]. ## Input is considered a decimal string. # TODO: Have a default static argument in the previous proc. Currently we get # "Cannot evaluate at compile-time" in several places (2018-04-26). parse(input, T, 10) func hexToUint*[bits: static[int]](hexString: string): Stuint[bits] {.inline.} = ## Convert an hex string to the corresponding unsigned integer parse(hexString, type result, base = 16) func toString*[bits: static[int]](num: StUint[bits], base: static[int]): string = ## Convert a Stint or Stuint to string. ## In case of negative numbers: ## - they are prefixed with "-" for base 10. ## - if not base 10, they are returned raw in two-complement form. static: assert (base >= 2) and base <= 16, "Only base from 2..16 are supported" # TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26) const hexChars = "0123456789abcdef" let radix = unsafeConv(base, type num) result = "" var (q, r) = divmod(num, radix) while true: result.add hexChars[r.toInt] if q.isZero: break (q, r) = divmod(q, radix) reverse(result) func toString*[bits: static[int]](num: Stint[bits], base: static[int]): string = ## Convert a Stint or Stuint to string. ## In case of negative numbers: ## - they are prefixed with "-" for base 10. ## - if not base 10, they are returned raw in two-complement form. static: assert (base >= 2) and base <= 16, "Only base from 2..16 are supported" # TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26) const hexChars = "0123456789abcdef" let radix = unsafeConv(base, type num) result = "" let isNeg = num.isNegative let num = if base == 10 and isNeg: -num else: num var (q, r) = divmod(num, radix) while true: result.add hexChars[r.toInt] if q.isZero: break (q, r) = divmod(q, radix) if isNeg: result.add '-' reverse(result) func toString*[bits: static[int]](num: Stint[bits] or StUint[bits]): string {.inline.}= ## Convert to a string. ## Output is considered a decimal string. # # TODO: Have a default static argument in the previous proc. Currently we get # "Error: type mismatch: got , required type static[int]" toString(num, 10) func dumpHex*(x: Stint or StUint, order: static[Endianness]): string = ## Stringify an int to hex. ## Note. Leading 0 are not removed. Use toString(n, base = 16) instead. ## ## You can specify bigEndian or littleEndian order. ## i.e. in bigEndian: ## - 1.uint64 will be 00000001 ## - (2.uint128)^64 + 1 will be 0000000100000001 ## ## in littleEndian: ## - 1.uint64 will be 01000000 ## - (2.uint128)^64 + 1 will be 0100000001000000 const hexChars = "0123456789abcdef" size = getSize(x.data) div 8 {.pragma: restrict, codegenDecl: "$# __restrict $#".} let bytes {.restrict.}= cast[ptr array[size, byte]](x.unsafeaddr) result = newString(2*size) for i in 0 ..< size: when order == system.cpuEndian: result[2*i] = hexChars[int bytes[i] shr 4 and 0xF] result[2*i+1] = hexChars[int bytes[i] and 0xF] else: result[2*i] = hexChars[int bytes[bytes[].high - i] shr 4 and 0xF] result[2*i+1] = hexChars[int bytes[bytes[].high - i] and 0xF] func dumpHex*(x: Stint or StUint): string {.inline.}= ## Stringify an int to hex. ## By default, dump is done in bigEndian order. dumpHex(x, bigEndian) # TODO: Have a default static argument in the previous proc. Currently we get # "Cannot evaluate at compile-time". func readUintBE*[bits: static[int]](ba: openarray[byte]): Stuint[bits] = ## Convert a big-endian array of (bits div 8) Bytes to an UInt[bits] (in native host endianness) ## Input: ## - a big-endian openarray of size (bits div 8) at least ## Returns: ## - A unsigned integer of the same size with `bits` bits ## ## ⚠ If the openarray length is bigger than bits div 8, part converted is undefined behaviour. const N = bits div 8 assert(ba.len == N) let r_ptr = cast[ptr array[N, byte]](result.addr) for i, val in ba: when system.cpuEndian == bigEndian: # TODO: due to https://github.com/status-im/nim-stint/issues/38 # We can't cast a stack byte array to stuint with a convenient proc signature. r_ptr[i] = val else: r_ptr[N-1 - i] = val func toByteArrayBE*[bits: static[int]](n: StUint[bits]): array[bits div 8, byte] = ## Convert a uint[bits] to to a big-endian array of bits div 8 bytes ## Input: ## - an unsigned integer ## Returns: ## - a big-endian array of the same size const N = bits div 8 when system.cpuEndian == bigEndian: result = cast[type result](n) else: let n_ptr = cast[ptr array[N, byte]](n.unsafeAddr) for i in 0 ..< N: result[N-1 - i] = n_ptr[i]