]> Cypherpunks repositories - keks.git/commitdiff
FLOAT support master
authorSergey Matveev <stargrave@stargrave.org>
Fri, 20 Jun 2025 20:21:08 +0000 (23:21 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Tue, 24 Jun 2025 14:14:46 +0000 (17:14 +0300)
39 files changed:
c/cmd/deatomiser/deatomiser.c
c/cmd/pp/pp.c
c/cmd/test-vector/test-vector.c
c/lib/atom.h
c/lib/dec.c
c/lib/enc.c
c/lib/enc.h
c/lib/err.c
c/lib/err.h
c/lib/fp.c [new file with mode: 0644]
c/lib/fp.h [new file with mode: 0644]
c/lib/items.c
c/lib/o.list
go/README
go/atom-decode.go
go/atom-encode.go
go/atomtype_string.go
go/cmd/pp/printer.go
go/cmd/test-vector-anys/main.go
go/cmd/test-vector-manual/main.go
go/ctx.go
go/encode.go
go/float.go [new file with mode: 0644]
go/float_test.go
go/iter.go
go/parse.go
go/type.go
go/unmarshal.go
py3/README
py3/keks.py
py3/test-vector.py
py3/tests/test_float.py
spec/encoding/FLOAT
spec/encoding/FullTable
spec/encoding/index
tcl/README
tcl/keks.tcl
tcl/mk-fuzz-inputs
tcl/test-vector.tcl

index ba3c21d1301549cb8acddda0dd057bdba95ce4f94e74743a57c0e8fd510449c1..6b188686502cf545f99226cc3cfaadacce8ff16a412a848761d65c531170fa93 100644 (file)
@@ -108,7 +108,7 @@ main(void)
             printf("BLOB(l=%zu\n", atom.v.blob.chunkLen);
             break;
         case KEKSItemFloat:
-            fputs("FLOAT: TODO\n", stdout);
+            printf("FLOAT(%f)\n", atom.v.fp);
             break;
         case KEKSItemTAI64:
             err = PrintTAI64(atom.v.str.ptr, atom.v.str.len);
index 5cdd4bd418423562ef9a5e3622d9eecea7c3a5cd96c96ad8e9801ec2d14ba296..6891d3476ca127abddcb9eab3edebdd287112ac5d2153cf9ec8e10d671a28fe2 100644 (file)
@@ -213,7 +213,7 @@ printer( // NOLINT(misc-no-recursion)
         fputs("]\n", stdout);
         break;
     case KEKSItemFloat:
-        fputs("FLOAT: TODO\n", stdout);
+        fprintf(stdout, "FLOAT(%f)\n", item->atom.v.fp);
         break;
     case KEKSItemTAI64: {
         err = PrintTAI64(item->atom.v.str.ptr, item->atom.v.str.len);
index ecb204c13280807a0a2d645aed1aaaf7f87c1f6ee8267091e26424881de82340..ea8c5af9bc6b8b5d0d90f11b06dad90716b686c30a31b207ee6ece825249c5d2 100644 (file)
@@ -14,6 +14,7 @@
 // License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #include <assert.h>
+#include <math.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdlib.h>
@@ -228,6 +229,7 @@ main(void)
     {
         struct timespec ts;
         ts.tv_sec = 1234567890;
+        ts.tv_nsec = 0;
         assert(KEKSTimespecToTAI(&ts));
         unsigned char tai[12] = {0};
         assert(KEKSTimespecToTAI64(tai, &ts));
@@ -266,11 +268,25 @@ main(void)
         &Got, buf + Off, len - Off, (const unsigned char *)"floats", 6));
     adder(KEKSAtomListEncode(&Got, buf + Off, len - Off)); // .floats
     {
-        buf[Off] = KEKSAtomFloat32;
-        Off++;
-        size_t l = 4;
-        memcpy(buf + Off, (const unsigned char *)"\x01\x02\x03\x04", l);
-        Off += l;
+        adder(KEKSAtomFloatEncode(&Got, buf + Off, len - Off, (double)NAN));
+        adder(KEKSAtomFloatEncode(&Got, buf + Off, len - Off, (double)INFINITY));
+        adder(KEKSAtomFloatEncode(&Got, buf + Off, len - Off, -(double)INFINITY));
+        adder(KEKSAtomFloatEncode(&Got, buf + Off, len - Off, 0.0));
+        adder(KEKSAtomFloatEncode(&Got, buf + Off, len - Off, -45.25));
+        adder(KEKSAtomFloatEncode(&Got, buf + Off, len - Off, 0.15625));
+        adder(KEKSAtomFloatMEEncode(
+            &Got, buf + Off, len - Off, -8687443681197687, -46)); // -123.456
+        {
+            buf[Off] = KEKSAtomFloat;
+            Off++;
+            size_t l = 18;
+            memcpy(
+                buf + Off,
+                (const unsigned char
+                     *)"\x0C\x8C\x27\xE4\x1B\x32\x46\xBE\xC9\xB1\x6E\x39\x81\x15\x0D\x82\x30\x38",
+                l);
+            Off += l;
+        }
     }
     adder(KEKSAtomEOCEncode(&Got, buf + Off, len - Off)); // .floats
 
index 74b7c56327088be0588da2151f077c6936666c131a1d9f7e953647eddcba3519..173916fa13ca444b90eac0a368792169cd08555ad95816c4b979b41f60010d40 100644 (file)
@@ -16,11 +16,10 @@ enum KEKSAtomType {
     KEKSAtomBlob = 0x0B,
     KEKSAtomPint = 0x0C,
     KEKSAtomNint = 0x0D,
-    KEKSAtomFloat16 = 0x10,
-    KEKSAtomFloat32 = 0x11,
-    KEKSAtomFloat64 = 0x12,
-    KEKSAtomFloat128 = 0x13,
-    KEKSAtomFloat256 = 0x14,
+    KEKSAtomFloatNaN = 0x10,
+    KEKSAtomFloatPinf = 0x11,
+    KEKSAtomFloatNinf = 0x12,
+    KEKSAtomFloat = 0x13,
     KEKSAtomTAI64 = 0x18,
     KEKSAtomTAI64N = 0x19,
     KEKSAtomTAI64NA = 0x1A,
@@ -83,6 +82,8 @@ enum KEKSItemType {
 //     Value of the positive integer.
 // @item .v.nint
 //     Value of the negative integer.
+// @item .v.fp
+//     Value of the floating point.
 // @item .v.list
 //     That value is filled only when dealing with @ref{Items, items}
 //     for lists and maps.
@@ -95,7 +96,7 @@ enum KEKSItemType {
 //     @code{.chunkLen} is the length of the chunk. @code{.chunks} is
 //     the number of chunks, including the terminating binary string.
 // @item .v.str
-//     @code{.ptr} points to the start of the binary/UTF-8/TAI64*/Magic
+//     @code{.ptr} points to the start of the binary/UTF-8/TAI64*/Magic/hexlet
 //     string. @code{.len} is its length in bytes.
 //     Raw values use it as a payload.
 // @end table
@@ -104,6 +105,7 @@ struct KEKSAtom {
     union {
         uint64_t pint;
         int64_t nint;
+        double fp;
         struct {
             size_t head;
             size_t len;
index 8df815fd274f06d837691f0adf5ed204924c99691d10573e2ddc85529373840a..8ee27947fe714199dd74c11814ce0de420f4debde1f062aeef411a56048729fa 100644 (file)
@@ -14,6 +14,7 @@
 // License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #include <assert.h>
+#include <math.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 #include "atom.h"
 #include "dec.h"
 #include "err.h"
+#include "fp.h"
 #include "frombe.h"
 #include "utf8.h"
 
+static bool
+atomIsInt(struct KEKSAtom *atom)
+{
+    if ((atom->typ == KEKSItemPint) || (atom->typ == KEKSItemNint)) {
+        return true;
+    }
+    if (atom->typ != KEKSItemRaw) {
+        return false;
+    }
+    if (atom->v.str.len == 0) {
+        return false;
+    }
+    return (atom->v.str.ptr[0] == KEKSAtomPint) || (atom->v.str.ptr[0] == KEKSAtomNint);
+}
+
 enum KEKSErr
 KEKSAtomDecode( // NOLINT(misc-no-recursion)
     size_t *got,
@@ -209,38 +226,59 @@ KEKSAtomDecode( // NOLINT(misc-no-recursion)
         return KEKSErrNo;
     }
 
-    case KEKSAtomFloat16:
-    case KEKSAtomFloat32:
-    case KEKSAtomFloat64:
-    case KEKSAtomFloat128:
-    case KEKSAtomFloat256: {
-        size_t l = 0;
-        switch (tag) {
-        case KEKSAtomFloat16:
-            l = 2;
-            break;
-        case KEKSAtomFloat32:
-            l = 4;
-            break;
-        case KEKSAtomFloat64:
-            l = 8;
-            break;
-        case KEKSAtomFloat128:
-            l = 16;
-            break;
-        case KEKSAtomFloat256:
-            l = 32;
-            break;
-        default:
-            assert(false);
-        }
+    case KEKSAtomFloatNaN:
         atom->typ = KEKSItemFloat;
-        (*got) += l;
-        if (len < (*got)) {
-            return KEKSErrNotEnough;
+        atom->v.fp = (double)NAN;
+        break;
+    case KEKSAtomFloatPinf:
+        atom->typ = KEKSItemFloat;
+        atom->v.fp = (double)INFINITY;
+        break;
+    case KEKSAtomFloatNinf:
+        atom->typ = KEKSItemFloat;
+        atom->v.fp = -((double)INFINITY);
+        break;
+    case KEKSAtomFloat: {
+        atom->typ = KEKSItemFloat;
+        size_t mGot = 0;
+        struct KEKSAtom m;
+        memset(&m, 0, sizeof(struct KEKSAtom));
+        enum KEKSErr err = KEKSAtomDecode(&mGot, &m, buf + 1, len - 1);
+        if (err != KEKSErrNo) {
+            return err;
+        }
+        (*got) += mGot;
+        if (!atomIsInt(&m)) {
+            return KEKSErrFloatNonInt;
+        }
+
+        size_t eGot = 0;
+        struct KEKSAtom e;
+        memset(&e, 0, sizeof(struct KEKSAtom));
+        err = KEKSAtomDecode(&eGot, &e, buf + 1 + mGot, len - 1 - mGot);
+        if (err != KEKSErrNo) {
+            return err;
+        }
+        (*got) += eGot;
+        if (!atomIsInt(&e)) {
+            return KEKSErrFloatNonInt;
+        }
+
+        if (((m.typ == KEKSItemPint) && ((m.v.pint >> 63) > 0)) ||
+            ((e.typ == KEKSItemPint) && ((e.v.pint >> 63) > 0))) {
+            goto FloatRaw;
         }
+
+        if (keksMEToDouble(
+                &(atom->v.fp),
+                (m.typ == KEKSItemPint) ? (int64_t)m.v.pint : m.v.nint,
+                (e.typ == KEKSItemPint) ? (int64_t)e.v.pint : e.v.nint)) {
+            break;
+        }
+
+    FloatRaw:
         atom->typ = KEKSItemRaw;
-        atom->v.str.len = l + 1;
+        atom->v.str.len = 1 + mGot + eGot;
         atom->v.str.ptr = buf;
         break;
     }
index 5d2cabc2a75389c081874a60b91306994e8714e28d91fb66669e3d07b3259dcf..683a9e3ead34db8cd7b70f9aa6a6b1fad54be8be5b095b2ffa8a77114b3adb21 100644 (file)
@@ -14,6 +14,7 @@
 // License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #include <assert.h>
+#include <math.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
@@ -21,6 +22,7 @@
 
 #include "atom.h"
 #include "enc.h"
+#include "fp.h"
 #include "tobe.h"
 
 bool
@@ -158,6 +160,68 @@ KEKSAtomSintEncode(size_t *len, unsigned char *buf, const size_t cap, const int6
     return ok;
 }
 
+bool
+KEKSAtomFloatMEEncode(
+    size_t *len,
+    unsigned char *buf,
+    const size_t cap,
+    const int64_t m,
+    const int e)
+{
+    if (cap < 1) {
+        return false;
+    }
+    assert(len != NULL);
+    assert(buf != NULL);
+    (*len) = 1;
+    buf[0] = KEKSAtomFloat;
+    size_t mLen = 0;
+    if (!KEKSAtomSintEncode(&mLen, buf + 1, cap - 1, m)) {
+        return false;
+    }
+    (*len) += mLen;
+    size_t eLen = 0;
+    if (!KEKSAtomSintEncode(&eLen, buf + 1 + mLen, cap - 1 - mLen, (int64_t)e)) {
+        return false;
+    }
+    (*len) += eLen;
+    return true;
+}
+
+bool
+KEKSAtomFloatEncode(size_t *len, unsigned char *buf, const size_t cap, const double v)
+{
+    if (cap < 1) {
+        return false;
+    }
+    assert(len != NULL);
+    assert(buf != NULL);
+    int64_t m = 0;
+    int e = 0;
+    switch (fpclassify(v)) {
+    case FP_INFINITE:
+        (*len) = 1;
+        buf[0] = (v > 0) ? KEKSAtomFloatPinf : KEKSAtomFloatNinf;
+        return true;
+    case FP_NAN:
+        (*len) = 1;
+        buf[0] = KEKSAtomFloatNaN;
+        return true;
+    case FP_NORMAL:
+        if (!keksDoubleToME(&m, &e, v)) {
+            return false;
+        }
+        break;
+    case FP_SUBNORMAL:
+        return false;
+    case FP_ZERO:
+        break;
+    default:
+        assert(false);
+    }
+    return KEKSAtomFloatMEEncode(len, buf, cap, m, e);
+}
+
 bool
 KEKSAtomListEncode(size_t *len, unsigned char *buf, const size_t cap)
 {
index 908eb252e785ca42e1713f242b705b6a05458ff1d6ab4fca735d84c365f316f7..1360114c0d8469471d78baf3808ee8f2d0ea3adb2c364548383eb9fee75db209 100644 (file)
@@ -88,6 +88,32 @@ KEKSAtomUintEncode(size_t *len, unsigned char *buf, const size_t cap, const uint
 bool
 KEKSAtomSintEncode(size_t *len, unsigned char *buf, const size_t cap, const int64_t v);
 
+// TEXINFO: KEKSAtomFloatEncode
+// @deftypefun bool KEKSAtomFloatEncode @
+//     (size_t *len, unsigned char *buf, const size_t cap, const double v)
+// Encode double float in provided @var{buf} with capacity of @var{cap}.
+// In case of success, true is returned and @var{len} will hold how many
+// bytes were written to buffer.
+// @end deftypefun
+bool
+KEKSAtomFloatEncode(size_t *len, unsigned char *buf, const size_t cap, const double);
+
+// TEXINFO: KEKSAtomFloatMEEncode
+// @deftypefun bool KEKSAtomFloatMEEncode @
+//     (size_t *len, unsigned char *buf, const size_t cap, @
+//      const int64_t m, const int e)
+// Encode float's mantissa and exponent in provided @var{buf} with
+// capacity of @var{cap}. In case of success, true is returned and
+// @var{len} will hold how many bytes were written to buffer.
+// @end deftypefun
+bool
+KEKSAtomFloatMEEncode(
+    size_t *len,
+    unsigned char *buf,
+    const size_t cap,
+    const int64_t m,
+    const int e);
+
 // TEXINFO: KEKSAtomListEncode
 // @deftypefun bool KEKSAtomListEncode @
 //     (size_t *len, unsigned char *buf, const size_t cap)
index c4b1b75579a75d48fa9785bc264cee857bc948ed363ed201aefecd7c0f615a9c..09bad288184292d8ddf219db2c796ba22ff556243b94a8afaa0305ca8a5fc1f3 100644 (file)
@@ -50,6 +50,8 @@ KEKSErr2Str(const enum KEKSErr err)
         return "UnexpectedEOC";
     case KEKSErrBadMagic:
         return "BadMagic";
+    case KEKSErrFloatNonInt:
+        return "FloatNonInt";
     default:
         return "unknown";
     }
index f3b60f620b4cde062016dad9ed4a9aafae3ca361563aae93352c99bcf74598b1..946f3fe06a922f2e6231d276e2f75532db45b5b55b40e434d43b578f186c4be7 100644 (file)
@@ -69,6 +69,7 @@ enum KEKSErr {
     KEKSErrDeepRecursion,
     KEKSErrUnexpectedEOC,
     KEKSErrBadMagic,
+    KEKSErrFloatNonInt,
 };
 
 // TEXINFO: KEKSErr2Str
diff --git a/c/lib/fp.c b/c/lib/fp.c
new file mode 100644 (file)
index 0000000..6dfd8ac
--- /dev/null
@@ -0,0 +1,119 @@
+// That code is fully based on sqlite-src-3450100/ext/misc/ieee754.c.
+// Excerpt from its source code:
+//
+//     2013-04-17
+//
+//     The author disclaims copyright to this source code.  In place of
+//     a legal notice, here is a blessing:
+//
+//        May you do good and not evil.
+//        May you find forgiveness for yourself and forgive others.
+//        May you share freely, never taking more than you give.
+
+#include <assert.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "fp.h"
+
+bool
+keksMEToDouble(double *d, int64_t m, int64_t e)
+{
+    if ((m == 0) && (e == 0)) {
+        (*d) = 0.0;
+        return true;
+    }
+    if ((e > 1023) || (e < -1022)) {
+        return false;
+    }
+    bool neg = false;
+    uint64_t um = 0;
+    if (m < 0) {
+        neg = true;
+        um = (uint64_t)(-m);
+    } else {
+        um = (uint64_t)m;
+    }
+    // if (um == 0 && e > -1000 && e < 1000) {
+    //     (*d) = 0.0;
+    //     return true;
+    // }
+    if (um >= 9007199254740992) {
+        return false;
+    }
+    while ((um >> 32) & 0xFFE00000) {
+        um >>= 1;
+        e++;
+    }
+    while (um != 0 && ((um >> 32) & 0xFFF00000) == 0) {
+        um <<= 1;
+        e--;
+    }
+    e += 1075;
+    if (e <= 0) {
+        if (1 - e >= 64) { // subnormal
+            um = 0;
+        } else {
+            um >>= (uint64_t)(1 - e);
+        }
+        e = 0;
+    } else if (e > 0x7FF) {
+        e = 0x07FF;
+    }
+    uint64_t a = um & ((((uint64_t)1) << 52) - 1);
+    a |= ((uint64_t)e) << 52;
+    if (neg) {
+        a |= ((uint64_t)1) << 63;
+    }
+    memcpy(d, &a, sizeof(double));
+    return true;
+}
+
+bool
+keksDoubleToME(int64_t *m, int *e, double d)
+{
+    switch (fpclassify(d)) {
+    case FP_NORMAL:
+        break;
+    case FP_ZERO:
+        (*m) = 0;
+        (*e) = 0;
+        return true;
+    case FP_INFINITE:
+    case FP_NAN:
+    case FP_SUBNORMAL:
+        return false;
+    default:
+        assert(false);
+    }
+    bool neg = false;
+    if (d < 0.0) {
+        neg = true;
+        d = -d;
+    }
+    uint64_t a = 0;
+    memcpy(&a, &d, sizeof(a));
+    uint64_t _m = 0;
+    int _e = 0;
+    if (a == 0) {
+        _e = 0;
+        _m = 0;
+    } else {
+        _e = a >> 52;
+        _m = a & ((((uint64_t)1) << 52) - 1);
+        if (_e == 0) {
+            _m <<= 1;
+        } else {
+            _m |= ((uint64_t)1) << 52;
+        }
+        while (_e < 1075 && _m > 0 && (_m & 1) == 0) {
+            _m >>= 1;
+            _e++;
+        }
+    }
+    (*m) = neg ? -((int64_t)_m) : (int64_t)_m;
+    (*e) = _e - 1075;
+    return true;
+}
diff --git a/c/lib/fp.h b/c/lib/fp.h
new file mode 100644 (file)
index 0000000..b5e3c75
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef KEKS_FP_H
+#define KEKS_FP_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+bool
+keksMEToDouble(double *, int64_t m, int64_t e);
+
+bool
+keksDoubleToME(int64_t *m, int *e, double);
+
+#endif // KEKS_FP_H
index 64ca4058ec12ccd5c63de1df3dfba91d280c2ed83010bed11e038cb4f4b1a4cd..37cebaeaed669b49a61d79ff932413d16457f5f510c63f0c68d7f6fc9807444a 100644 (file)
@@ -355,6 +355,9 @@ KEKSItemsEncode( // NOLINT(misc-no-recursion)
     case KEKSItemNint:
         ok = KEKSAtomSintEncode(&got, buf + *off, cap - (*off), item->atom.v.nint);
         break;
+    case KEKSItemFloat:
+        ok = KEKSAtomFloatEncode(&got, buf + *off, cap - (*off), item->atom.v.fp);
+        break;
     case KEKSItemList:
         ok = KEKSAtomListEncode(&got, buf + *off, cap - (*off));
         if (!ok) {
@@ -431,8 +434,6 @@ KEKSItemsEncode( // NOLINT(misc-no-recursion)
             &got, buf + *off, cap - (*off), item->atom.v.str.ptr, item->atom.v.str.len);
         break;
     }
-    case KEKSItemFloat:
-        return false;
     case KEKSItemTAI64:
         ok = KEKSAtomTAI64Encode(
             &got, buf + *off, cap - (*off), item->atom.v.str.ptr, item->atom.v.str.len);
index 2d87f0c547f69213877929c486523f9a20735bac6ad91bca686811e945585e8b..fd9b2f7b5c975f1607b46a1276a75e94a1e4cc49538b12133f413901c1607733 100644 (file)
@@ -3,6 +3,7 @@ dectai.o
 enc.o
 enctai.o
 err.o
+fp.o
 frombe.o
 items.o
 leapsecs.o
index 4ea83ffbf6f695b9a54f9beea0148fd10a6cb39eed83d3ccddd93148eee71458..7d3ba5da14affb98019c133c9374ba686036d5e9973092be274355da0a51a7c5 100644 (file)
--- a/go/README
+++ b/go/README
@@ -1,5 +1,5 @@
 Go implementation of the KEKS codec, KEKS/Schema validator and KEKS/CM.
 
-No FLOAT* support. They are stored/decoded just as a raw value.
+Partial FLOAT support.
 
 It is free software: see the file COPYING.LESSER for copying conditions.
index e5eff1b42495dd28209332b76a4c8bf622ee6042723ede58617d77363fcd17ce..013840989a3b8c3e83d3aa0e9a5aa30e1bf37d774a216da8d1e5b4bde8751b66 100644 (file)
@@ -166,28 +166,18 @@ func (ctx *Decoder) DecodeAtom() (t types.Type, err error) {
                                ctx.ints = append(ctx.ints, -1-int64(i))
                        }
                }
-       case AtomFloat16, AtomFloat32, AtomFloat64, AtomFloat128, AtomFloat256:
-               var l int
-               switch AtomType(tag) {
-               case AtomFloat16:
-                       l = 2
-               case AtomFloat32:
-                       l = 4
-               case AtomFloat64:
-                       l = 8
-               case AtomFloat128:
-                       l = 16
-               case AtomFloat256:
-                       l = 32
-               }
-               var s string
-               s, err = ctx.getBytes(l)
-               if err != nil {
-                       return
-               }
-               t = types.Raw
-               ctx.rawTypes = append(ctx.rawTypes, AtomType(tag))
-               ctx.strs = append(ctx.strs, s)
+       case AtomFloatNaN:
+               t = types.Float
+               ctx.floats = append(ctx.floats, &Float{NaN: true})
+       case AtomFloatPinf:
+               t = types.Float
+               ctx.floats = append(ctx.floats, &Float{Pinf: true})
+       case AtomFloatNinf:
+               t = types.Float
+               ctx.floats = append(ctx.floats, &Float{Ninf: true})
+       case AtomFloat:
+               t = types.Float
+               ctx.floats = append(ctx.floats, &Float{})
        case AtomTAI64, AtomTAI64N, AtomTAI64NA:
                var l int
                switch AtomType(tag) {
index 7468f5f1feb608112b474431e4bed13fb3a5eb071b2e73fe57b8861f51f07bfe..7987c4fffccb844e3c4b511a5ba73e1bc00558b514fa5ff0ccb5fa55d1a774e2 100644 (file)
@@ -126,6 +126,33 @@ func BigIntEncode(w io.Writer, v *big.Int) (written int64, err error) {
        return
 }
 
+// Write an encoded FLOAT atom.
+func FloatEncode(w io.Writer, v *Float) (written int64, err error) {
+       if v.NaN {
+               return ByteEncode(w, byte(AtomFloatNaN))
+       }
+       if v.Pinf {
+               return ByteEncode(w, byte(AtomFloatPinf))
+       }
+       if v.Ninf {
+               return ByteEncode(w, byte(AtomFloatNinf))
+       }
+       _, err = w.Write([]byte{byte(AtomFloat)})
+       if err != nil {
+               return
+       }
+       written++
+       var wr int64
+       wr, err = BigIntEncode(w, v.M)
+       written += wr
+       if err != nil {
+               return
+       }
+       wr, err = BigIntEncode(w, v.E)
+       written += wr
+       return
+}
+
 // Write an encoded BLOB atom.
 func BlobAtomEncode(w io.Writer, chunkLen int64) (written int64, err error) {
        l := make([]byte, 9)
index 303c569e31f3a17b15722ed11548f52c293a09630af58382e3b44d77d4d08aea..5eba9cffdaf1d60be432a34504c3a1b45964f3eada3006cca40d9f9304e1f703 100644 (file)
@@ -18,11 +18,10 @@ func _() {
        _ = x[AtomBLOB-11]
        _ = x[AtomPInt-12]
        _ = x[AtomNInt-13]
-       _ = x[AtomFloat16-16]
-       _ = x[AtomFloat32-17]
-       _ = x[AtomFloat64-18]
-       _ = x[AtomFloat128-19]
-       _ = x[AtomFloat256-20]
+       _ = x[AtomFloatNaN-16]
+       _ = x[AtomFloatPinf-17]
+       _ = x[AtomFloatNinf-18]
+       _ = x[AtomFloat-19]
        _ = x[AtomTAI64-24]
        _ = x[AtomTAI64N-25]
        _ = x[AtomTAI64NA-26]
@@ -33,7 +32,7 @@ const (
        _AtomType_name_0 = "AtomEOCAtomNILAtomFalseAtomTrueAtomHexlet"
        _AtomType_name_1 = "AtomListAtomMap"
        _AtomType_name_2 = "AtomBLOBAtomPIntAtomNInt"
-       _AtomType_name_3 = "AtomFloat16AtomFloat32AtomFloat64AtomFloat128AtomFloat256"
+       _AtomType_name_3 = "AtomFloatNaNAtomFloatPinfAtomFloatNinfAtomFloat"
        _AtomType_name_4 = "AtomTAI64AtomTAI64NAtomTAI64NA"
        _AtomType_name_5 = "AtomMagic"
 )
@@ -42,7 +41,7 @@ var (
        _AtomType_index_0 = [...]uint8{0, 7, 14, 23, 31, 41}
        _AtomType_index_1 = [...]uint8{0, 8, 15}
        _AtomType_index_2 = [...]uint8{0, 8, 16, 24}
-       _AtomType_index_3 = [...]uint8{0, 11, 22, 33, 45, 57}
+       _AtomType_index_3 = [...]uint8{0, 12, 25, 38, 47}
        _AtomType_index_4 = [...]uint8{0, 9, 19, 30}
 )
 
@@ -56,7 +55,7 @@ func (i AtomType) String() string {
        case 11 <= i && i <= 13:
                i -= 11
                return _AtomType_name_2[_AtomType_index_2[i]:_AtomType_index_2[i+1]]
-       case 16 <= i && i <= 20:
+       case 16 <= i && i <= 19:
                i -= 16
                return _AtomType_name_3[_AtomType_index_3[i]:_AtomType_index_3[i+1]]
        case 24 <= i && i <= 26:
index 6d91d5c49ea7d15646ff9d1a047ce6b068d8dacfa1658eab1b151eb879b3c1dc..56de1f94fe6502e14479f5a744d3ae9553033ee6053ed2b4217cbb6d5c8a8e34 100644 (file)
@@ -140,6 +140,17 @@ func printer(iter *keks.Iterator, where []string, count int, inList, inMap bool)
                        fmt.Println(iter.Int())
                case types.BigInt:
                        fmt.Println(iter.BigInt())
+               case types.Float:
+                       f := iter.Float()
+                       if f.NaN {
+                               fmt.Println("NaN")
+                       } else if f.Pinf {
+                               fmt.Println("+inf")
+                       } else if f.Ninf {
+                               fmt.Println("-inf")
+                       } else {
+                               fmt.Printf("%s (m=%d e=%d)\n", f.BigFloat().String(), f.M, f.E)
+                       }
                case types.Blob:
                        blob := iter.Blob()
                        if *onlyV {
index 80ca7b320fdfc0df1ecfda3b78d548c06105bbef6ed3f6e5dc7115f3160fc8fa..21e0d629ffabf2051ca3b0939ea41c595e8ddd3000d990605ae88e42d0456629 100644 (file)
@@ -110,7 +110,14 @@ func main() {
                        tai64n.TAI64NA(mustHexDec("40000000499602F40006F855075BCD15")),
                },
                "floats": []any{
-                       keks.Raw(append([]byte{byte(keks.AtomFloat32)}, mustHexDec("01020304")...)),
+                       keks.Float{NaN: true},
+                       keks.Float{Pinf: true},
+                       keks.Float{Ninf: true},
+                       keks.Float{M: big.NewInt(0), E: big.NewInt(0)},
+                       keks.Float{M: big.NewInt(-181), E: big.NewInt(-2)},
+                       keks.Float{M: big.NewInt(5), E: big.NewInt(-5)},
+                       keks.Float{M: big.NewInt(-8687443681197687), E: big.NewInt(-46)},
+                       keks.Float{M: big.NewInt(0).SetBytes([]byte{0x27, 0xE4, 0x1B, 0x32, 0x46, 0xBE, 0xC9, 0xB1, 0x6E, 0x39, 0x81, 0x15}), E: big.NewInt(-12345)},
                },
                "uuid": uuid.MustParse("0e875e3f-d385-49eb-87b4-be42d641c367"),
                "ip":   net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"),
index 6e71f397a54df290f5842d274a59484354732629f0a56647e317151da6b22ad4..d9b81fe19078524f71cfc0d2c59de699fd3f6f42ebe5aa0d9789297fb090f106 100644 (file)
@@ -184,10 +184,14 @@ func main() {
                {
                        mustEncode(keks.StrEncode(&buf, "floats"))
                        mustEncode(keks.ByteEncode(&buf, byte(keks.AtomList)))
-                       mustEncode(io.Copy(&buf, bytes.NewReader(append(
-                               []byte{byte(keks.AtomFloat32)},
-                               []byte("\x01\x02\x03\x04")...,
-                       ))))
+                       mustEncode(keks.FloatEncode(&buf, &keks.Float{NaN: true}))
+                       mustEncode(keks.FloatEncode(&buf, &keks.Float{Pinf: true}))
+                       mustEncode(keks.FloatEncode(&buf, &keks.Float{Ninf: true}))
+                       mustEncode(keks.FloatEncode(&buf, &keks.Float{M: big.NewInt(0), E: big.NewInt(0)}))
+                       mustEncode(keks.FloatEncode(&buf, &keks.Float{M: big.NewInt(-181), E: big.NewInt(-2)}))
+                       mustEncode(keks.FloatEncode(&buf, &keks.Float{M: big.NewInt(5), E: big.NewInt(-5)}))
+                       mustEncode(keks.FloatEncode(&buf, &keks.Float{M: big.NewInt(-8687443681197687), E: big.NewInt(-46)}))
+                       mustEncode(keks.FloatEncode(&buf, &keks.Float{M: big.NewInt(0).SetBytes([]byte{0x27, 0xE4, 0x1B, 0x32, 0x46, 0xBE, 0xC9, 0xB1, 0x6E, 0x39, 0x81, 0x15}), E: big.NewInt(-12345)}))
                        mustEncode(keks.ByteEncode(&buf, byte(keks.AtomEOC)))
                }
                {
index 7bc1811ca9a6f693a7a67c1f714f2219601c633f1b79ffcfe2a8e128bdea7990..a452bbc179a1f5351f3f40a7d173d665dfded7e7138ef0209b801c889066c373 100644 (file)
--- a/go/ctx.go
+++ b/go/ctx.go
@@ -66,6 +66,7 @@ type Decoder struct {
        tai64s   []tai64n.TAI64
        uints    []uint64
        hexlets  []*Hexlet
+       floats   []*Float
 
        blobChunkLens []int64
        blobChunkses  [][]string
index eb8edf8400931f0554c04e8b7d6e8b5fc752a5a7c6f733edb14ae73a947f5766..167ed28af110838cd5aefe5b8da3deadf82b302f98b051b6e0fec0ace9fabede 100644 (file)
@@ -69,6 +69,10 @@ func Encode(w io.Writer, v any, opts *EncodeOpts) (written int64, err error) {
                return io.Copy(w, bytes.NewReader(v))
        case *big.Int:
                return BigIntEncode(w, v)
+       case Float:
+               return FloatEncode(w, &v)
+       case *Float:
+               return FloatEncode(w, v)
        case bool:
                return BoolEncode(w, v)
        case uuid.UUID:
diff --git a/go/float.go b/go/float.go
new file mode 100644 (file)
index 0000000..be6b963
--- /dev/null
@@ -0,0 +1,62 @@
+// KEKS -- Go KEKS codec implementation
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, version 3 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package keks
+
+import (
+       "fmt"
+       "math/big"
+)
+
+type Float struct {
+       M    *big.Int
+       E    *big.Int
+       NaN  bool
+       Pinf bool
+       Ninf bool
+}
+
+func (f *Float) MarshalJSON() ([]byte, error) {
+       if f.NaN {
+               return fmt.Appendf(nil, "FLOAT[NaN]"), nil
+       }
+       if f.Pinf {
+               return fmt.Appendf(nil, "FLOAT[+inf]"), nil
+       }
+       if f.Ninf {
+               return fmt.Appendf(nil, "FLOAT[-inf]"), nil
+       }
+       return fmt.Appendf(nil, "FLOAT[%d, %d]", f.M, f.E), nil
+}
+
+func (f *Float) BigFloat() *big.Float {
+       exp := big.NewFloat(0).SetInt(big.NewInt(0).Exp(
+               big.NewInt(2),
+               big.NewInt(0).Abs(f.E),
+               nil,
+       ))
+       if f.E.Sign() == -1 {
+               exp = exp.Quo(big.NewFloat(1.0), exp)
+       }
+       return exp.Mul(exp, big.NewFloat(0).SetInt(f.M))
+}
+
+func (f *Float) IsZero() bool {
+       if f.NaN || f.Pinf || f.Ninf {
+               return false
+       }
+       z := big.NewInt(0)
+       return (f.M.Cmp(z) == 0) && (f.E.Cmp(z) == 0)
+}
index 1846ad250147588286655c80340a253520fc136fd8ea622ddf13e3af39e91de7..86d82a9fe3d71f0928aa1e2814f5ad2d6fc1007129ece2f97102636f61848ffc 100644 (file)
@@ -1,6 +1,5 @@
 // KEKS -- Go KEKS codec implementation
-// Copyright (C) 2024-2025 Anton Rudenko <rudenko.ad@phystech.edu>
-//                         Sergey Matveev <stargrave@stargrave.org>
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
 //
 // This program is free software: you can redistribute it and/or modify
 // it under the terms of the GNU Lesser General Public License as
@@ -19,7 +18,9 @@ package keks
 import (
        "bytes"
        "io"
+       "math/big"
        "testing"
+       "testing/quick"
 )
 
 func TestEncodingFloat(t *testing.T) {
@@ -29,137 +30,223 @@ func TestEncodingFloat(t *testing.T) {
        }
 }
 
-func TestFloat16Loads(t *testing.T) {
-       bin := append([]byte{0x10}, bytes.Repeat([]byte{0x11}, 2)...)
+func TestFloatNaN(t *testing.T) {
+       bin := []byte{0x10}
        decoder := NewDecoderFromBytes(append(bin, Junk...), nil)
        decoded, err := decoder.Decode()
        if err != nil {
                t.Fatal(err)
        }
-       casted, ok := decoded.(Raw)
+       casted, ok := decoded.(*Float)
        if !ok {
                t.Fatal("failed to cast")
        }
-       if !bytes.Equal(casted, bin) {
-               t.Fatal("casted differs")
+       if !casted.NaN {
+               t.Fatal("got non NaN")
+       }
+       if casted.Pinf || casted.Ninf || casted.M != nil || casted.E != nil {
+               t.Fatal("unexpectedly filled fields")
        }
        if !bytes.Equal(decoder.B, Junk) {
                t.Fatal("tail differs")
        }
 }
 
-func TestFloat32Loads(t *testing.T) {
-       bin := append([]byte{0x11}, bytes.Repeat([]byte{0x11}, 4)...)
+func TestFloatPinf(t *testing.T) {
+       bin := []byte{0x11}
        decoder := NewDecoderFromBytes(append(bin, Junk...), nil)
        decoded, err := decoder.Decode()
        if err != nil {
                t.Fatal(err)
        }
-       casted, ok := decoded.(Raw)
+       casted, ok := decoded.(*Float)
        if !ok {
                t.Fatal("failed to cast")
        }
-       if !bytes.Equal(casted, bin) {
-               t.Fatal("casted differs")
+       if !casted.Pinf {
+               t.Fatal("got non +inf")
+       }
+       if casted.NaN || casted.Ninf || casted.M != nil || casted.E != nil {
+               t.Fatal("unexpectedly filled fields")
        }
        if !bytes.Equal(decoder.B, Junk) {
                t.Fatal("tail differs")
        }
 }
 
-func TestFloat64Loads(t *testing.T) {
-       bin := append([]byte{0x12}, bytes.Repeat([]byte{0x12}, 8)...)
+func TestFloatNinf(t *testing.T) {
+       bin := []byte{0x12}
        decoder := NewDecoderFromBytes(append(bin, Junk...), nil)
        decoded, err := decoder.Decode()
        if err != nil {
                t.Fatal(err)
        }
-       casted, ok := decoded.(Raw)
+       casted, ok := decoded.(*Float)
        if !ok {
                t.Fatal("failed to cast")
        }
-       if !bytes.Equal(casted, bin) {
-               t.Fatal("casted differs")
+       if !casted.Ninf {
+               t.Fatal("got non -inf")
+       }
+       if casted.NaN || casted.Pinf || casted.M != nil || casted.E != nil {
+               t.Fatal("unexpectedly filled fields")
        }
        if !bytes.Equal(decoder.B, Junk) {
                t.Fatal("tail differs")
        }
 }
 
-func TestFloat128Loads(t *testing.T) {
-       bin := append([]byte{0x13}, bytes.Repeat([]byte{0x13}, 16)...)
-       decoder := NewDecoderFromBytes(append(bin, Junk...), nil)
+func TestFloat0(t *testing.T) {
+       var b bytes.Buffer
+       b.Write([]byte{0x13})
+       UIntEncode(&b, 0)
+       UIntEncode(&b, 0)
+       b.Write(Junk)
+       decoder := NewDecoderFromBytes(b.Bytes(), nil)
        decoded, err := decoder.Decode()
        if err != nil {
                t.Fatal(err)
        }
-       casted, ok := decoded.(Raw)
+       casted, ok := decoded.(*Float)
        if !ok {
                t.Fatal("failed to cast")
        }
-       if !bytes.Equal(casted, bin) {
-               t.Fatal("casted differs")
+       if casted.NaN || casted.Pinf || casted.Ninf {
+               t.Fatal("unexpectedly filled fields")
        }
        if !bytes.Equal(decoder.B, Junk) {
                t.Fatal("tail differs")
        }
-}
-
-func TestFloat256Loads(t *testing.T) {
-       bin := append([]byte{0x14}, bytes.Repeat([]byte{0x14}, 32)...)
-       decoder := NewDecoderFromBytes(append(bin, Junk...), nil)
-       decoded, err := decoder.Decode()
-       if err != nil {
-               t.Fatal(err)
-       }
-       casted, ok := decoded.(Raw)
-       if !ok {
-               t.Fatal("failed to cast")
+       if casted.M.Cmp(big.NewInt(0)) != 0 {
+               t.Fatal("m differs")
        }
-       if !bytes.Equal(casted, bin) {
-               t.Fatal("casted differs")
+       if casted.E.Cmp(big.NewInt(0)) != 0 {
+               t.Fatal("m differs")
        }
-       if !bytes.Equal(decoder.B, Junk) {
-               t.Fatal("tail differs")
+       f, _ := casted.BigFloat().Float64()
+       if f != 0.0 {
+               t.Fatal("non zero")
        }
 }
 
-func TestFloat16NotEnoughData(t *testing.T) {
-       bin := append([]byte{0x10}, bytes.Repeat([]byte{0x11}, 2-1)...)
-       _, err := NewDecoderFromBytes(bin, nil).Decode()
+func TestFloatNotEnough(t *testing.T) {
+       decoder := NewDecoderFromBytes([]byte{0x13}, nil)
+       _, err := decoder.Decode()
        if err != io.ErrUnexpectedEOF {
                t.Fatal(err)
        }
 }
 
-func TestFloat32NotEnoughData(t *testing.T) {
-       bin := append([]byte{0x11}, bytes.Repeat([]byte{0x11}, 4-1)...)
-       _, err := NewDecoderFromBytes(bin, nil).Decode()
+func TestFloatSingleInt(t *testing.T) {
+       var b bytes.Buffer
+       b.Write([]byte{0x13})
+       UIntEncode(&b, 0)
+       decoder := NewDecoderFromBytes(b.Bytes(), nil)
+       _, err := decoder.Decode()
        if err != io.ErrUnexpectedEOF {
                t.Fatal(err)
        }
 }
 
-func TestFloat64NotEnoughData(t *testing.T) {
-       bin := append([]byte{0x12}, bytes.Repeat([]byte{0x11}, 8-1)...)
-       _, err := NewDecoderFromBytes(bin, nil).Decode()
-       if err != io.ErrUnexpectedEOF {
+func TestFloatNonInt0(t *testing.T) {
+       var b bytes.Buffer
+       b.Write([]byte{0x13})
+       BoolEncode(&b, true)
+       decoder := NewDecoderFromBytes(b.Bytes(), nil)
+       _, err := decoder.Decode()
+       if err != ErrFloatBadInt {
                t.Fatal(err)
        }
 }
 
-func TestFloat128NotEnoughData(t *testing.T) {
-       bin := append([]byte{0x13}, bytes.Repeat([]byte{0x11}, 16-1)...)
-       _, err := NewDecoderFromBytes(bin, nil).Decode()
-       if err != io.ErrUnexpectedEOF {
+func TestFloatNonInt1(t *testing.T) {
+       var b bytes.Buffer
+       b.Write([]byte{0x13})
+       UIntEncode(&b, 0)
+       BoolEncode(&b, true)
+       decoder := NewDecoderFromBytes(b.Bytes(), nil)
+       _, err := decoder.Decode()
+       if err != ErrFloatBadInt {
                t.Fatal(err)
        }
 }
 
-func TestFloat256NotEnoughData(t *testing.T) {
-       bin := append([]byte{0x14}, bytes.Repeat([]byte{0x11}, 32-1)...)
-       _, err := NewDecoderFromBytes(bin, nil).Decode()
-       if err != io.ErrUnexpectedEOF {
+func TestFloatSymmetric(t *testing.T) {
+       f := func(m, e int64) bool {
+               fp := Float{M: big.NewInt(-181), E: big.NewInt(-2)}
+               var b bytes.Buffer
+               if _, err := FloatEncode(&b, &fp); err != nil {
+                       t.Fatal(err)
+               }
+               b.Write(Junk)
+               decoder := NewDecoderFromBytes(b.Bytes(), nil)
+               decoded, err := decoder.Decode()
+               if err != nil {
+                       t.Fatal(err)
+               }
+               casted, ok := decoded.(*Float)
+               if !ok {
+                       t.Fatal("failed to cast")
+               }
+               if casted.NaN || casted.Pinf || casted.Ninf {
+                       t.Fatal("unexpectedly filled fields")
+               }
+               if !bytes.Equal(decoder.B, Junk) {
+                       t.Fatal("tail differs")
+               }
+               return (casted.M.Cmp(fp.M) == 0) && (casted.E.Cmp(fp.E) == 0)
+       }
+       if err := quick.Check(f, nil); err != nil {
+               t.Fatal(err)
+       }
+}
+
+func TestFloatExamples(t *testing.T) {
+       var b bytes.Buffer
+       if _, err := FloatEncode(&b, &Float{
+               M: big.NewInt(-181),
+               E: big.NewInt(-2),
+       }); err != nil {
+               t.Fatal(err)
+       }
+       decoded, err := NewDecoderFromBytes(b.Bytes(), nil).Decode()
+       if err != nil {
+               t.Fatal(err)
+       }
+       fp, _ := decoded.(*Float).BigFloat().Float64()
+       if fp != -45.25 {
+               t.Fatal("-45.25")
+       }
+
+       b.Reset()
+       if _, err = FloatEncode(&b, &Float{
+               M: big.NewInt(5),
+               E: big.NewInt(-5),
+       }); err != nil {
+               t.Fatal(err)
+       }
+       decoded, err = NewDecoderFromBytes(b.Bytes(), nil).Decode()
+       if err != nil {
+               t.Fatal(err)
+       }
+       fp, _ = decoded.(*Float).BigFloat().Float64()
+       if fp != 0.15625 {
+               t.Fatal("0.15625")
+       }
+
+       b.Reset()
+       if _, err = FloatEncode(&b, &Float{
+               M: big.NewInt(-8687443681197687),
+               E: big.NewInt(-46),
+       }); err != nil {
                t.Fatal(err)
        }
+       decoded, err = NewDecoderFromBytes(b.Bytes(), nil).Decode()
+       if err != nil {
+               t.Fatal(err)
+       }
+       fp, _ = decoded.(*Float).BigFloat().Float64()
+       if fp != -123.456 {
+               t.Fatal("-123.456")
+       }
 }
index 59a9f259fe9941f67226b8c4272deada0ade355a3bcd9f0b143ce5739fc9c73a..700f0ef3b6a36ed2f6a07e50565a1ad8c69de45edb69eeaf294ffb437e0b8c69 100644 (file)
@@ -47,6 +47,7 @@ type Iterator struct {
        tai64s   int
        uints    int
        hexlets  int
+       floats   int
 }
 
 func (ctx *Decoder) Iter() *Iterator {
@@ -76,6 +77,8 @@ func (iter *Iterator) Next() bool {
                iter.tai64ns++
        case types.TAI64NA:
                iter.tai64nas++
+       case types.Float:
+               iter.floats++
        case types.Bin, types.Str, types.Magic:
                iter.strs++
        case types.Raw:
@@ -118,6 +121,10 @@ func (iter *Iterator) BigInt() *big.Int {
        return iter.ctx.bigints[iter.bigints]
 }
 
+func (iter *Iterator) Float() *Float {
+       return iter.ctx.floats[iter.floats]
+}
+
 func (iter *Iterator) Blob() BlobChunked {
        return BlobChunked{
                ChunkLen: iter.ctx.blobChunkLens[iter.blobs],
index 9d433ac5e8c8652cc669971828df936161d0ed86d30397a5a49e28c07a857972..7f966e8fbd68861b60f2c7cecd819c50c10c6b4eebd1b6863bc74ee91c5d48f5 100644 (file)
@@ -17,6 +17,7 @@ package keks
 
 import (
        "errors"
+       "math/big"
 
        "go.cypherpunks.su/keks/types"
 )
@@ -28,6 +29,7 @@ var (
        ErrBlobBadChunkLen = errors.New("blob bad chunk len")
        ErrEOCUnexpected   = errors.New("unexpected EOC")
        ErrTooDeep         = errors.New("too deep structure")
+       ErrFloatBadInt     = errors.New("non-int in float")
 )
 
 func (ctx *Decoder) deTail() {
@@ -38,12 +40,49 @@ func (ctx *Decoder) deTail() {
        }
 }
 
+func (ctx *Decoder) popIntForFloat() (b *big.Int, err error) {
+       var sub types.Type
+       sub, err = ctx.DecodeAtom()
+       if err != nil {
+               return
+       }
+       switch sub {
+       case types.Int:
+               b = big.NewInt(ctx.ints[len(ctx.ints)-1])
+               ctx.ints = ctx.ints[:len(ctx.ints)-1]
+       case types.UInt:
+               b = big.NewInt(0).SetUint64(ctx.uints[len(ctx.uints)-1])
+               ctx.uints = ctx.uints[:len(ctx.uints)-1]
+       case types.BigInt:
+               b = ctx.bigints[len(ctx.bigints)-1]
+               ctx.bigints = ctx.bigints[:len(ctx.bigints)-1]
+       default:
+               err = ErrFloatBadInt
+               return
+       }
+       ctx.deTail()
+       return
+}
+
 func (ctx *Decoder) parse() (t types.Type, err error) {
        t, err = ctx.DecodeAtom()
        if err != nil {
                return
        }
        switch t {
+       case types.Float:
+               f := ctx.floats[len(ctx.floats)-1]
+               if f.NaN || f.Pinf || f.Ninf {
+                       return
+               }
+               f.M, err = ctx.popIntForFloat()
+               if err != nil {
+                       return
+               }
+               f.E, err = ctx.popIntForFloat()
+               if err != nil {
+                       return
+               }
        case types.List:
                ctx.depth++
                if ctx.depth < 0 {
index ecc8d3a3193b4ed78685c1b50b90b577f7291c696e4a813461a4ab554f20baff..9467f0d462967eaf094566ab1a9419e9297817aace5e25de3ba2dbaa7edfcfeb 100644 (file)
@@ -4,25 +4,24 @@ type AtomType byte
 
 //go:generate stringer -type=AtomType
 const (
-       AtomEOC      AtomType = 0x00
-       AtomNIL      AtomType = 0x01
-       AtomFalse    AtomType = 0x02
-       AtomTrue     AtomType = 0x03
-       AtomHexlet   AtomType = 0x04
-       AtomList     AtomType = 0x08
-       AtomMap      AtomType = 0x09
-       AtomBLOB     AtomType = 0x0B
-       AtomPInt     AtomType = 0x0C
-       AtomNInt     AtomType = 0x0D
-       AtomFloat16  AtomType = 0x10
-       AtomFloat32  AtomType = 0x11
-       AtomFloat64  AtomType = 0x12
-       AtomFloat128 AtomType = 0x13
-       AtomFloat256 AtomType = 0x14
-       AtomTAI64    AtomType = 0x18
-       AtomTAI64N   AtomType = 0x19
-       AtomTAI64NA  AtomType = 0x1A
-       AtomMagic    AtomType = 0x4B
+       AtomEOC       AtomType = 0x00
+       AtomNIL       AtomType = 0x01
+       AtomFalse     AtomType = 0x02
+       AtomTrue      AtomType = 0x03
+       AtomHexlet    AtomType = 0x04
+       AtomList      AtomType = 0x08
+       AtomMap       AtomType = 0x09
+       AtomBLOB      AtomType = 0x0B
+       AtomPInt      AtomType = 0x0C
+       AtomNInt      AtomType = 0x0D
+       AtomFloatNaN  AtomType = 0x10
+       AtomFloatPinf AtomType = 0x11
+       AtomFloatNinf AtomType = 0x12
+       AtomFloat     AtomType = 0x13
+       AtomTAI64     AtomType = 0x18
+       AtomTAI64N    AtomType = 0x19
+       AtomTAI64NA   AtomType = 0x1A
+       AtomMagic     AtomType = 0x4B
 
        AtomStrings = 0x80
        AtomIsUTF8  = 0x40
index 3d4eabec776a217ea66dcbbde6ee3aca84d9dddee957cf25e8178e105fdc8f8e..f4acd743d10d567d3d9c62e235d8872419d1ee462277b3b66892ccc6d0d55613 100644 (file)
@@ -94,7 +94,7 @@ func (ctx *Decoder) unmarshal(iter *Iterator) (v any, err error) {
        case types.BigInt:
                return iter.BigInt(), nil
        case types.Float:
-               panic("float is unsupported")
+               return iter.Float(), nil
        case types.TAI64:
                t := iter.TAI64()
                if ctx.opts != nil {
index 60d9047764917fb83ad079407f8aae231f6fd005284c534d7178e21d2a6e07ab..f6f689b7bdf97da8dfcadaa33f2c2e593448008c537d9f5c9bc9c3d66f939b6f 100644 (file)
@@ -1,6 +1,7 @@
 Python3 implementation of KEKS codec.
 
-* No FLOAT*, TAI64NA, or nanoseconds support.
+* Partial FLOAT support.
+* No TAI64NA, or nanoseconds support.
   They are stored/decoded just as a raw value
 
 It is free software: see the file COPYING.LESSER for copying conditions.
index a62ae4c0fa3ccbb83fa6e54364ea73d10ddaf75ecc1a8d1123a404f0d2de1729..d0e2ee60b82e992a8894532a65948992e00aa160be56ba7b1f8dc3cbf2e4d6a7 100755 (executable)
@@ -23,7 +23,7 @@ transparently replace JSON.
 
 It has :py:func:`loads` and :py:func:`dumps` functions, similar to
 native :py:module:`json` library's. KEKS supports dictionaries, lists,
-None, booleans, UUID, IPv6 addresses, floats (currently not
+None, booleans, UUID, IPv6 addresses, floats (currently not fully
 implemented!), integers (including big ones), datetime, UTF-8 and
 binary strings.
 
@@ -39,6 +39,7 @@ from datetime import timedelta
 from datetime import timezone
 from ipaddress import IPv6Address
 from math import ceil as _ceil
+from math import isnan as _isnan
 from uuid import UUID
 
 
@@ -52,17 +53,17 @@ TagMap = 0x09
 TagBlob = 0x0B
 TagPInt = 0x0C
 TagNInt = 0x0D
-TagFloat16 = 0x10
-TagFloat32 = 0x11
-TagFloat64 = 0x12
-TagFloat128 = 0x13
-TagFloat256 = 0x14
+TagFloatNaN = 0x10
+TagFloatPinf = 0x11
+TagFloatNinf = 0x12
+TagFloat = 0x13
 TagTAI64 = 0x18
 TagTAI64N = 0x19
 TagTAI64NA = 0x1A
 TagMagic = 0x4B
 TagStr = 0x80
 TagUTF8 = 0x40
+NaN = float("nan")
 
 
 def _byte(v):
@@ -82,6 +83,10 @@ TagNIntb = _byte(TagNInt)
 TagTAI64b = _byte(TagTAI64)
 TagTAI64Nb = _byte(TagTAI64N)
 TagMagicb = _byte(TagMagic)
+TagFloatNaNb = _byte(TagFloatNaN)
+TagFloatPinfb = _byte(TagFloatPinf)
+TagFloatNinfb = _byte(TagFloatNinf)
+TagFloatb = _byte(TagFloat)
 
 
 class DecodeError(ValueError):
@@ -169,6 +174,25 @@ class Hexlet:
         return IPv6Address(self.v)
 
 
+class Float:
+    __slots__ = ("m", "e")
+
+    def __init__(self, m: int, e: int):
+        self.m = m
+        self.e = e
+
+    def __eq__(self, other) -> bool:
+        if not isinstance(other, self.__class__):
+            return False
+        return (self.m == other.m) and (self.e == other.e)
+
+    def __float__(self) -> float:
+        return self.m * pow(2, self.e)
+
+    def __repr__(self) -> str:
+        return "Float(m=%d, e=%d)" % (self.m, self.e)
+
+
 Blob = namedtuple("Blob", ("l", "v"))
 
 
@@ -260,7 +284,17 @@ def dumps(v):
     if isinstance(v, Hexlet):
         return TagHexletb + v.v
     if isinstance(v, float):
-        raise NotImplementedError("no FLOAT* support")
+        if v == 0.0:
+            return dumps(Float(0, 0))
+        if _isnan(v):
+            return TagFloatNaNb
+        if v == float("+inf"):
+            return TagFloatPinfb
+        if v == float("-inf"):
+            return TagFloatNinfb
+        raise NotImplementedError("no full FLOAT support")
+    if isinstance(v, Float):
+        return TagFloatb + dumps(v.m) + dumps(v.e)
     if isinstance(v, datetime):
         ms = v.microsecond
         v = v.replace(microsecond=0)
@@ -335,7 +369,6 @@ def _int(v):
 
 
 _EOC = object()
-_floats = {TagFloat16: 2, TagFloat32: 4, TagFloat64: 8, TagFloat128: 16, TagFloat256: 32}
 _tais = {TagTAI64: 8, TagTAI64N: 12, TagTAI64NA: 16}
 
 
@@ -372,11 +405,22 @@ def _loads(v, sets=False, leapsecUTCAllow=False, _allowContainers=True):
         if len(v) < 1+16:
             raise NotEnoughData(1+16-len(v))
         return Hexlet(v[1:1+16]), v[1+16:]
-    l = _floats.get(b)
-    if l is not None:
-        if len(v) < 1+l:
-            raise NotEnoughData(1+l-len(v))
-        return Raw(v[:1+l]), v[1+l:]
+    if b == TagFloatNaN:
+        return NaN, v[1:]
+    if b == TagFloatPinf:
+        return float("+inf"), v[1:]
+    if b == TagFloatNinf:
+        return float("-inf"), v[1:]
+    if b == TagFloat:
+        m, v = _loads(v[1:])
+        if type(m) != int:
+            raise DecodeError("non-int m float")
+        e, v = _loads(v)
+        if type(e) != int:
+            raise DecodeError("non-int e float")
+        if (m == 0) and (e == 0):
+            return 0.0, v
+        return Float(m, e), v
     l = _tais.get(b)
     if l is not None:
         if len(v) < 1+l:
index c5e4b78f34619f98a37461c41447058f41d5a9b4f2703f35e32962ab2b59aa51..687997be14a89143f4b297f6ce8d061687666762bb7b43fcc39365218a588e2e 100644 (file)
@@ -17,7 +17,16 @@ data = {
             -123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789,
         ],
     },
-    "floats": [keks.Raw(keks._byte(keks.TagFloat32) + bytes.fromhex("01020304"))],
+    "floats": [
+        keks.NaN,
+        float("+inf"),
+        float("-inf"),
+        0.0,
+        keks.Float(-181, -2),  # -45.25
+        keks.Float(5, -5),  # 0.15625
+        keks.Float(-8687443681197687, -46),  # -123.456
+        keks.Float(12345678901234567890123456789, -12345),
+    ],
     "nil": None,
     "bool": [True, False],
     "str": {
index 0df252f4c5a839b02bfa5760b437530ca2e74afa3e5282241ef40eef92970f93..60de06e5a145d4d92276b19b5b541ed7b187892387b5170dacb500841a095655 100644 (file)
 from unittest import TestCase
 
 from hypothesis import given
+from hypothesis.strategies import integers
 
-from keks import _byte
+from keks import DecodeError
 from keks import dumps
+from keks import Float
 from keks import loads
+from keks import NaN
 from keks import NotEnoughData
-from keks import Raw
 
 from tests.strategies import junk_st
 
@@ -31,59 +33,66 @@ class TestFloat(TestCase):
     def test_throws_when_dumps_float(self) -> None:
         with self.assertRaises(NotImplementedError) as err:
             dumps(1.5)
-        self.assertEqual(str(err.exception), "no FLOAT* support")
+        self.assertEqual(str(err.exception), "no full FLOAT support")
 
     @given(junk_st)
-    def test_loads_16(self, junk: bytes) -> None:
-        decoded, tail = loads((b"\x10" + b"\x11" * 2) + junk)
-        self.assertEqual(decoded, Raw(_byte(0x10) + b"\x11" * 2))
+    def test_nan(self, junk: bytes) -> None:
+        b = dumps(float("nan"))
+        self.assertSequenceEqual(b, b"\x10")
+        f, tail = loads(b + junk)
         self.assertSequenceEqual(tail, junk)
+        self.assertIs(f, NaN)
 
     @given(junk_st)
-    def test_loads_32(self, junk: bytes) -> None:
-        decoded, tail = loads((b"\x11" + b"\x11" * 4) + junk)
-        self.assertEqual(decoded, Raw(_byte(0x11) + b"\x11" * 4))
+    def test_pinf(self, junk: bytes) -> None:
+        b = dumps(float("+inf"))
+        self.assertSequenceEqual(b, b"\x11")
+        f, tail = loads(b + junk)
         self.assertSequenceEqual(tail, junk)
+        self.assertEqual(f, float("+inf"))
 
     @given(junk_st)
-    def test_loads_64(self, junk: bytes) -> None:
-        decoded, tail = loads((b"\x12" + b"\x11" * 8) + junk)
-        self.assertEqual(decoded, Raw(_byte(0x12) + b"\x11" * 8))
+    def test_ninf(self, junk: bytes) -> None:
+        b = dumps(float("-inf"))
+        self.assertSequenceEqual(b, b"\x12")
+        f, tail = loads(b + junk)
         self.assertSequenceEqual(tail, junk)
+        self.assertEqual(f, float("-inf"))
 
     @given(junk_st)
-    def test_loads_128(self, junk: bytes) -> None:
-        decoded, tail = loads((b"\x13" + b"\x11" * 16) + junk)
-        self.assertEqual(decoded, Raw(_byte(0x13) + b"\x11" * 16))
+    def test_0(self, junk: bytes) -> None:
+        b = dumps(0.0)
+        self.assertSequenceEqual(b, b"\x13" + dumps(0) + dumps(0))
+        f, tail = loads(b + junk)
         self.assertSequenceEqual(tail, junk)
+        self.assertEqual(f, 0.0)
 
-    @given(junk_st)
-    def test_loads_256(self, junk: bytes) -> None:
-        decoded, tail = loads((b"\x14" + b"\x11" * 32) + junk)
-        self.assertEqual(decoded, Raw(_byte(0x14) + b"\x11" * 32))
-        self.assertSequenceEqual(tail, junk)
-
-    def test_not_enough_data_16(self) -> None:
+    def test_not_enough(self) -> None:
         with self.assertRaises(NotEnoughData) as err:
-            loads(b"\x10" + b"\x11" * (2-1))
+            loads(b"\x13")
         self.assertEqual(err.exception.n, 1)
 
-    def test_not_enough_data_32(self) -> None:
+    def test_not_enough_int(self) -> None:
         with self.assertRaises(NotEnoughData) as err:
-            loads(b"\x11" + b"\x11" * (4-1))
+            loads(b"\x13" + dumps(123))
         self.assertEqual(err.exception.n, 1)
 
-    def test_not_enough_data_64(self) -> None:
-        with self.assertRaises(NotEnoughData) as err:
-            loads(b"\x12" + b"\x11" * (8-1))
-        self.assertEqual(err.exception.n, 1)
+    def test_non_int0(self) -> None:
+        with self.assertRaises(DecodeError) as err:
+            loads(b"\x13" + dumps(False) + dumps(123))
+        self.assertEqual(str(err.exception), "non-int m float")
 
-    def test_not_enough_data_128(self) -> None:
-        with self.assertRaises(NotEnoughData) as err:
-            loads(b"\x13" + b"\x11" * (16-1))
-        self.assertEqual(err.exception.n, 1)
+    def test_non_int1(self) -> None:
+        with self.assertRaises(DecodeError) as err:
+            loads(b"\x13" + dumps(123) + dumps(False))
+        self.assertEqual(str(err.exception), "non-int e float")
 
-    def test_not_enough_data_256(self) -> None:
-        with self.assertRaises(NotEnoughData) as err:
-            loads(b"\x14" + b"\x11" * (32-1))
-        self.assertEqual(err.exception.n, 1)
+    @given(integers(), integers(), junk_st)
+    def test_symmetric(self, m: int, e: int, junk: bytes) -> None:
+        f, tail = loads(dumps(Float(m, e)) + junk)
+        self.assertSequenceEqual(tail, junk)
+        if (m == 0) and (e == 0):
+            self.assertEqual(f, 0.0)
+        else:
+            self.assertEqual(f.m, m)
+            self.assertEqual(f.e, e)
index 2e52413e010be675311af91039dfab1df9bd4a6a9e96f99e20505715cadca2b6..d978f9f01159349f5c92b0a950fbb70748d6478df54cde45ec8f188fcb5aa2d7 100644 (file)
@@ -1,14 +1,27 @@
-          ====================================================
-                                WARNING
-          ====================================================
-           Currently not implemented and format is not fixed.
-          ====================================================
+Floats consists of [encoding/INT]-encoded mantissa "m" and
+base-2 exponent "e": m * pow(2,e).
 
-Floats are encoded in IEEE 754 binary formats: half, single, double,
-quadruple, octuple precision ones.
+Normalised values *must* be used.
+Separate tag's values are used for representing NaN and infinite
+numbers. Zero is represented as zero mantissa and exponent.
 
-Negative zero *must not* be used. Shortest possible form *must* be used.
+            NaN = 0x10
+           +inf = 0x11
+           -inf = 0x12
+    float(m, e) = 0x13 || INT(m) || INT(e)
+            0.0 = float(0, 0)
 
-Hint: look at CBOR's RFC for example code of float16 conversion.
+That representation is far from being compact. Sending binary IEEE754
+binaries may be more preferable.
 
-Maybe there appear additional restrictions and rules.
+Example representations:
+
+NaN                              | 10
++inf                             | 11
+-inf                             | 12
+0.0                              | 13 0C80   0C80
+45.25   m=181 e=-2               | 13 0C81B5 0D8101
+-45.25  m=-181 e=-2              | 13 0D81B4 0D8101
+0.125   m=1 e=-3                 | 13 0C8101 0D8102
+0.15625 m=5 e=-5                 | 13 0C8105 0D8104
+123.456 m=8687443681197687 e=-46 | 13 0C871EDD2F1A9FBE77 0D812D
index 6bd0d64a063d94233bab2cd9c6220f5a2e6f6f233ac18323e3d782784ebaa512..2999c36448101f4516f31e0087bd8245888c8a392a139e81d9a8167f4e9de24e 100644 (file)
@@ -14,11 +14,11 @@ dec | hex | bin      | vlen |
 012 | 0C  | 00001100 | 1+~  | + [encoding/INT]
 013 | 0D  | 00001101 | 1+~  | - [encoding/INT]
 014 | 0E  | 00001110 | 0    |
-015 | 0F  | 00001111 | ?    | [encoding/FLOAT]
-016 | 10  | 00010000 | 0    |
-017 | 11  | 00010001 | 0    |
-018 | 12  | 00010010 | 0    |
-019 | 13  | 00010011 | 0    |
+015 | 0F  | 00001111 | 0    |
+016 | 10  | 00010000 | 0    | [encoding/FLOAT] NaN
+017 | 11  | 00010001 | 0    | [encoding/FLOAT] +inf
+018 | 12  | 00010010 | 0    | [encoding/FLOAT] -inf
+019 | 13  | 00010011 | 4+~  | [encoding/FLOAT]
 020 | 14  | 00010100 | 0    |
 021 | 15  | 00010101 | 0    |
 022 | 16  | 00010110 | 0    |
index d61de13b4dd64261c2572200286555af3a80e38ba97b77e86ce09f6cd10c98d8..b7f37ba8b45bf650e81005efc4c2d67b4f5960000425215ba20f99188de54a98 100644 (file)
@@ -17,7 +17,10 @@ dec | hex | bin      | vlen |
 012 | 0C  | 00001100 | 1+~  | + [encoding/INT]
 013 | 0D  | 00001101 | 1+~  | - [encoding/INT]
 ... | ... | ...      | ...  | ...
-015 | 0F  | 00001111 | ?    | [encoding/FLOAT]
+016 | 10  | 00010000 | 0    | [encoding/FLOAT] NaN
+017 | 11  | 00010001 | 0    | [encoding/FLOAT] +inf
+018 | 12  | 00010010 | 0    | [encoding/FLOAT] -inf
+019 | 13  | 00010011 | 4+~  | [encoding/FLOAT]
 ... | ... | ...      | ...  | ...
 024 | 18  | 00011000 | 8    | [encoding/TAI]64
 025 | 19  | 00011001 | 12   | [encoding/TAI]64N
index 109834577ad66ef68a432d7a2288f826cf9fb7349b816a62a2c43e712396bf18..ba005603269123dc5a6fe44c58fb11439519cabed6c4e19864482a7e84d85243 100644 (file)
@@ -1,5 +1,3 @@
 Tcl implementation of the KEKS encoder.
 
-* No FLOAT* support. They can be stored just as a raw value.
-
 It is free software: see the file COPYING.LESSER for copying conditions.
index 5089f06cf3d17125ac6e93e78bb645c2fc31f112d72abfae01fb03816beae64e..8da73481a23408b5d6304b52800f0729948ad18564fda1762c0f7049a6f1d0c7 100755 (executable)
@@ -28,6 +28,8 @@ proc char {v} {
     add [binary format c $v]
 }
 
+########################################################################
+
 # v is a complete raw value of the atom.
 proc RAW {v} {
     upvar buf buf
@@ -54,6 +56,8 @@ proc TRUE {} {
     char [expr 0x03]
 }
 
+########################################################################
+
 # v is either 16-bytes string, or UUID or uncompressed IP address.
 proc HEXLET {v} {
     set v [binary decode hex [string map {- "" : ""} $v]]
@@ -76,6 +80,8 @@ proc MAGIC {v} {
     add [string repeat [binary format c 0] [expr {12 - $l}]]
 }
 
+########################################################################
+
 proc toBEbin {l v} {
     set a {}
     for {set i 0} {$i < $l} {incr i} {
@@ -94,7 +100,7 @@ proc INT {v} {
     upvar buf buf
     if {$v >= 0} {
         char [expr 0x0C]
-    } {
+    } else {
         char [expr 0x0D]
         set v [expr {- ($v + 1)}]
     }
@@ -112,6 +118,29 @@ proc INT {v} {
     BIN [toBEbin [expr {$l + 1}] $v]
 }
 
+# Either "nan", "+inf", "-inf", or integers {mantissa exponent}.
+proc FLOAT {v} {
+    upvar buf buf
+    switch $v {
+        nan {
+            char [expr 0x10]
+        }
+        +inf {
+            char [expr 0x11]
+        }
+        -inf {
+            char [expr 0x12]
+        }
+        default {
+            char [expr 0x13]
+            INT [lindex $v 0]
+            INT [lindex $v 1]
+        }
+    }
+}
+
+########################################################################
+
 proc _str {atom v} {
     set ll 0
     set vl [string length $v]
@@ -147,6 +176,27 @@ proc STR {v} {
     _str [expr {0x80 | 0x40}] [encoding convertto utf-8 $v]
 }
 
+proc BLOB {chunkLen v} {
+    upvar buf buf
+    char [expr 0x0B]
+    toBE 8 [expr {$chunkLen - 1}]
+    set vl [string length $v]
+    set chunks [expr {$vl / $chunkLen}]
+    for {set i 0} {$i < $chunks} {incr i} {
+        BIN [string range $v \
+            [expr {$i * $chunkLen}] \
+            [expr {(($i + 1) * $chunkLen) - 1}]]
+    }
+    set left [expr {$vl - ($chunks * $chunkLen)}]
+    if {$left == 0} {
+        BIN ""
+    } else {
+        BIN [string range $v [expr {$vl - $left}] end]
+    }
+}
+
+########################################################################
+
 # v is a list of values that will be eval-ed.
 proc LIST {v} {
     upvar buf buf
@@ -211,24 +261,7 @@ proc SET {v} {
     MAP $args
 }
 
-proc BLOB {chunkLen v} {
-    upvar buf buf
-    char [expr 0x0B]
-    toBE 8 [expr {$chunkLen - 1}]
-    set vl [string length $v]
-    set chunks [expr {$vl / $chunkLen}]
-    for {set i 0} {$i < $chunks} {incr i} {
-        BIN [string range $v \
-            [expr {$i * $chunkLen}] \
-            [expr {(($i + 1) * $chunkLen) - 1}]]
-    }
-    set left [expr {$vl - ($chunks * $chunkLen)}]
-    if {$left == 0} {
-        BIN ""
-    } else {
-        BIN [string range $v [expr {$vl - $left}] end]
-    }
-}
+########################################################################
 
 # v is a "2006-01-02 15:04:05"-formatted string that is converted to seconds.
 proc ISOToSec {v} {
@@ -308,7 +341,9 @@ proc TAI64 {v {n 0} {a 0}} {
     }
 }
 
-namespace export EOC NIL FALSE TRUE HEXLET MAGIC INT STR BIN RAW
+########################################################################
+
+namespace export EOC NIL FALSE TRUE HEXLET MAGIC INT STR BIN FLOAT RAW
 namespace export TAI64 ToTAI ISOToSec
 namespace export LIST MAP SET LenFirstSort BLOB
 
index 3771b884d3fc25fe01d5bf564cc275db02c228cd70f81edd9e046e1315a79684..614e29b6e882596219dd1281f5e1b624c833a36168d3f265b9040403ad3f0339 100755 (executable)
@@ -32,3 +32,9 @@ dump 'RAW [binary decode hex "1840000000586846A4"]' >tai-leap
 dump 'TAI64 1234 1234' >tai-ns
 dump 'TAI64 1234 1234 1234' >tai-as
 dump "MAGIC fuzz" >magic
+dump "FLOAT nan" >float=nan
+dump "FLOAT +inf" >float=+inf
+dump "FLOAT -inf" >float=-inf
+dump "FLOAT {0 0}" >float=0
+dump "FLOAT {123 45}" >float=123,45
+dump "FLOAT {-123 -45}" >float=-123,-45
index 8ee7fe0cbe0c6332be118ad4483a61e061281142e2723e1fb58ef9caffa5f9ef..680f46d505c6d370dafe57349dedfe300af66bf09e5a5a14130f8aad61bb1183 100644 (file)
@@ -30,7 +30,14 @@ MAP {
         }}
     }}
     floats {LIST {
-        {RAW [binary decode hex "1101020304"]}
+        {FLOAT nan}
+        {FLOAT +inf}
+        {FLOAT -inf}
+        {FLOAT {0 0}}
+        {FLOAT {-181 -2}}
+        {FLOAT {5 -5}}
+        {FLOAT {-8687443681197687 -46}}
+        {FLOAT {12345678901234567890123456789 -12345}}
     }}
     nil NIL
     bool {LIST {TRUE FALSE}}