From 2b28b1a23612245970349acab5bf230ba6b0a934 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Thu, 27 Sep 2018 14:01:15 +0300 Subject: [PATCH] Add support for float64 --- rlp.nim | 28 ++++++++++++++++++---------- rlp/writer.nim | 17 ++++++++++++++++- tests/test_api_usage.nim | 20 +++++++++++++++++++- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/rlp.nim b/rlp.nim index 62a483e..8221f61 100644 --- a/rlp.nim +++ b/rlp.nim @@ -176,10 +176,10 @@ proc isInt*(self: Rlp): bool = return bytes[offset] != 0 return false -template maxBytes*(o: typedesc[Ordinal | uint64 | uint]): int = sizeof(o) +template maxBytes*(o: type[Ordinal | uint64 | uint]): int = sizeof(o) -proc toInt*(self: Rlp, IntType: typedesc): IntType = - # XXX: work-around a Nim issue with typedesc parameters +proc toInt*(self: Rlp, IntType: type): IntType = + # XXX: work-around a Nim issue with type parameters type OutputType = IntType mixin maxBytes, to @@ -289,14 +289,22 @@ proc readImpl(rlp: var Rlp, T: type Integer): Integer = result = rlp.toInt(T) rlp.skipElem -proc readImpl(rlp: var Rlp, T: typedesc[enum]): T = +proc readImpl(rlp: var Rlp, T: type[enum]): T = result = type(result)(rlp.toInt(int)) rlp.skipElem -proc readImpl(rlp: var Rlp, T: typedesc[bool]): T = +proc readImpl(rlp: var Rlp, T: type bool): T = result = rlp.toInt(int) != 0 rlp.skipElem +proc readImpl(rlp: var Rlp, T: type float64): T = + # This is not covered in the RLP spec, but Geth uses Go's + # `math.Float64bits`, which is defined here: + # https://github.com/gopherjs/gopherjs/blob/master/compiler/natives/src/math/math.go + let uint64bits = rlp.toInt(uint64) + var uint32parts = [uint32(uint64bits), uint32(uint64bits shr 32)] + return cast[ptr float64](unsafeAddr uint32parts)[] + proc readImpl[R, E](rlp: var Rlp, T: type array[R, E]): T = mixin read @@ -345,7 +353,7 @@ proc readImpl[E](rlp: var Rlp, T: type seq[E]): T = proc readImpl[E](rlp: var Rlp, T: type openarray[E]): seq[E] = result = readImpl(rlp, seq[E]) -proc readImpl(rlp: var Rlp, T: typedesc[object|tuple], +proc readImpl(rlp: var Rlp, T: type[object|tuple], wrappedInList = wrapObjectsInList): T = mixin enumerateRlpFields, read @@ -380,7 +388,7 @@ proc toNodes*(self: var Rlp): RlpNode = # We define a single `read` template with a pretty low specifity # score in order to facilitate easier overloading with user types: -template read*(rlp: var Rlp, T: typedesc): auto = +template read*(rlp: var Rlp, T: type): auto = readImpl(rlp, T) proc decode*(bytes: openarray[byte]): RlpNode = @@ -389,16 +397,16 @@ proc decode*(bytes: openarray[byte]): RlpNode = rlp = rlpFromBytes(bytesCopy.toRange()) return rlp.toNodes -template decode*(bytes: BytesRange, T: typedesc): untyped = +template decode*(bytes: BytesRange, T: type): untyped = mixin read var rlp = rlpFromBytes(bytes) rlp.read(T) -template decode*(bytes: openarray[byte], T: typedesc): T = +template decode*(bytes: openarray[byte], T: type): T = var bytesCopy = @bytes decode(bytesCopy.toRange, T) -template decode*(bytes: seq[byte], T: typedesc): untyped = +template decode*(bytes: seq[byte], T: type): untyped = decode(bytes.toRange, T) proc append*(writer: var RlpWriter; rlp: Rlp) = diff --git a/rlp/writer.nim b/rlp/writer.nim index bb08e4e..90e7566 100644 --- a/rlp/writer.nim +++ b/rlp/writer.nim @@ -185,6 +185,14 @@ proc appendInt(self; i: Integer) = self.maybeClosePendingLists() +proc appendFloat(self; data: float64) = + # This is not covered in the RLP spec, but Geth uses Go's + # `math.Float64bits`, which is defined here: + # https://github.com/gopherjs/gopherjs/blob/master/compiler/natives/src/math/math.go + let uintWords = cast[ptr UncheckedArray[uint32]](unsafeAddr data) + let uint64bits = (uint64(uintWords[1]) shl 32) or uint64(uintWords[0]) + self.appendInt(uint64bits) + template appendImpl(self; i: Integer) = appendInt(self, i) @@ -238,7 +246,14 @@ proc appendImpl(self; data: tuple, wrapInList = wrapObjectsInList) {.inline.} = # We define a single `append` template with a pretty low specifity # score in order to facilitate easier overloading with user types: -template append*[T](self; data: T) = appendImpl(self, data) +template append*[T](self; data: T) = + when data is float64: + # XXX: This works around an overloading bug. + # Apparently, integer literals will be converted to `float64` + # values with higher precedence than the generic match to Integer + appendFloat(self, data) + else: + appendImpl(self, data) proc initRlpList*(listSize: int): RlpWriter = result = initRlpWriter() diff --git a/tests/test_api_usage.nim b/tests/test_api_usage.nim index b66910e..dd25330 100644 --- a/tests/test_api_usage.nim +++ b/tests/test_api_usage.nim @@ -1,5 +1,5 @@ import - unittest, strutils, + math, unittest, strutils, rlp, util/json_testing proc q(s: string): string = "\"" & s & "\"" @@ -172,3 +172,21 @@ test "empty byte arrays": rlp = rlpFromBytes rlp.encode("").toRange b = rlp.toBytes check $b == "R[]" + +test "encode/decode floats": + for f in [high(float64), low(float64), 0.1, 122.23, + 103487315.128934, + 1943935743563457201.391754032785692, + 0, -0, + Inf, NegInf, NaN]: + + template isNaN(n): bool = + classify(n) == fcNaN + + template chk(input) = + let restored = decode(encode(input), float64) + check restored == input or (input.isNaN and restored.isNaN) + + chk f + chk -f +