From 37db38ea0924c13a579a694f19f3491ab76c8e69 Mon Sep 17 00:00:00 2001 From: Karl Lehenbauer Date: Fri, 19 Nov 2021 17:05:21 +0000 Subject: [PATCH 1/6] switch from longs to wide ints for 64-bits ...on 32-bit machines like current raspberry pi --- generic/tohil.c | 150 ++++++++++++++++++++++++------------------------ 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/generic/tohil.c b/generic/tohil.c index c96bee4..495de41 100644 --- a/generic/tohil.c +++ b/generic/tohil.c @@ -434,7 +434,7 @@ static PyObject * tclObjToPy(Tcl_Interp *interp, Tcl_Obj *tObj) { int intValue; - long longValue; + Tcl_WideInt wideValue; double doubleValue; if (Tcl_GetBooleanFromObj(NULL, tObj, &intValue) == TCL_OK) { @@ -443,8 +443,8 @@ tclObjToPy(Tcl_Interp *interp, Tcl_Obj *tObj) return p; } - if (Tcl_GetLongFromObj(NULL, tObj, &longValue) == TCL_OK) { - return PyLong_FromLong(longValue); + if (Tcl_GetWideIntFromObj(NULL, tObj, &wideValue) == TCL_OK) { + return PyLong_FromLongLong(wideValue); } if (Tcl_GetDoubleFromObj(NULL, tObj, &doubleValue) == TCL_OK) { @@ -1694,7 +1694,7 @@ static PyObject * TohilTclObj_incr(TohilTclObj *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"incr", NULL}; - long longValue = 0; + Tcl_WideInt wideValue = 0; long increment = 1; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|l", kwlist, &increment)) { @@ -1705,23 +1705,23 @@ TohilTclObj_incr(TohilTclObj *self, PyObject *args, PyObject *kwargs) if (selfobj == NULL) return NULL; - if (Tcl_GetLongFromObj(self->interp, selfobj, &longValue) == TCL_ERROR) { + if (Tcl_GetWideIntFromObj(self->interp, selfobj, &wideValue) == TCL_ERROR) { PyErr_SetString(PyExc_TypeError, Tcl_GetString(Tcl_GetObjResult(self->interp))); return NULL; } - longValue += increment; + wideValue += increment; Tcl_Obj *writeObj = TohilTclObj_writable_objptr(self); if (writeObj == NULL) return NULL; - Tcl_SetLongObj(writeObj, longValue); + Tcl_SetWideIntObj(writeObj, wideValue); if (TohilTclObj_possibly_stuff_var(self, writeObj) < 0) return NULL; - return PyLong_FromLong(longValue); + return PyLong_FromLongLong(wideValue); } // @@ -2753,14 +2753,14 @@ tclobj_nb_bool(TohilTclObj *self) static PyObject * tclobj_nb_long(PyObject *p) { - long longValue = 0; + Tcl_WideInt wideValue = 0; TohilTclObj *self = (TohilTclObj *)p; Tcl_Obj *selfobj = TohilTclObj_objptr(self); if (selfobj == NULL) return NULL; - if (Tcl_GetLongFromObj(self->interp, selfobj, &longValue) == TCL_ERROR) { + if (Tcl_GetWideIntFromObj(self->interp, selfobj, &wideValue) == TCL_ERROR) { double doubleValue = 0; if (Tcl_GetDoubleFromObj(NULL, selfobj, &doubleValue) == TCL_OK) { return PyLong_FromDouble(doubleValue); @@ -2768,7 +2768,7 @@ tclobj_nb_long(PyObject *p) PyErr_SetString(PyExc_TypeError, Tcl_GetString(Tcl_GetObjResult(self->interp))); return NULL; } - return PyLong_FromLong(longValue); + return PyLong_FromLongLong(wideValue); } static PyObject * @@ -2788,10 +2788,10 @@ tclobj_nb_float(PyObject *p) } static int -tohil_pyobj_to_number(PyObject *v, long *longPtr, double *doublePtr, Tcl_Interp **interpPtr) +tohil_pyobj_to_number(PyObject *v, Tcl_WideInt *widePtr, double *doublePtr, Tcl_Interp **interpPtr) { if (PyLong_Check(v)) { - *longPtr = PyLong_AsLong(v); + *widePtr = PyLong_AsLongLong(v); return 0; } @@ -2813,7 +2813,7 @@ tohil_pyobj_to_number(PyObject *v, long *longPtr, double *doublePtr, Tcl_Interp if (selfobj == NULL) return -1; - if (Tcl_GetLongFromObj(self->interp, selfobj, longPtr) == TCL_OK) { + if (Tcl_GetWideIntFromObj(self->interp, selfobj, widePtr) == TCL_OK) { return 0; } @@ -2833,8 +2833,8 @@ tclobj_nb_unaryop(PyObject *v, enum tclobj_unary_op operator) { Tcl_Interp *interp = NULL; double doubleV = 0.0; - long longV = 0; - int vFloat = tohil_pyobj_to_number(v, &longV, &doubleV, &interp); + Tcl_WideInt wideV = 0; + int vFloat = tohil_pyobj_to_number(v, &wideV, &doubleV, &interp); if (vFloat < 0) { assert(interp != NULL); PyErr_SetString(PyExc_TypeError, Tcl_GetString(Tcl_GetObjResult(interp))); @@ -2858,16 +2858,16 @@ tclobj_nb_unaryop(PyObject *v, enum tclobj_unary_op operator) } else { switch (operator) { case Abs: - return PyLong_FromLong(labs(longV)); + return PyLong_FromLongLong(labs(wideV)); case Negative: - return PyLong_FromLong(-longV); + return PyLong_FromLongLong(-wideV); case Positive: - return PyLong_FromLong(longV); + return PyLong_FromLongLong(wideV); case Invert: - return PyLong_FromLong(~longV); + return PyLong_FromLongLong(~wideV); default: Py_RETURN_NOTIMPLEMENTED; @@ -2881,14 +2881,14 @@ static PyObject * tclobj_nb_binop(PyObject *v, PyObject *w, enum tclobj_op operator) { double doubleV = 0.0; - long longV = 0; + Tcl_WideInt wideV = 0; Tcl_Interp *vInterp = NULL; - int vFloat = tohil_pyobj_to_number(v, &longV, &doubleV, &vInterp); + int vFloat = tohil_pyobj_to_number(v, &wideV, &doubleV, &vInterp); double doubleW = 0.0; - long longW = 0; + Tcl_WideInt wideW = 0; Tcl_Interp *wInterp = NULL; - int wFloat = tohil_pyobj_to_number(w, &longW, &doubleW, &wInterp); + int wFloat = tohil_pyobj_to_number(w, &wideW, &doubleW, &wInterp); ldiv_t ldiv_res; double quotient; @@ -2907,9 +2907,9 @@ tclobj_nb_binop(PyObject *v, PyObject *w, enum tclobj_op operator) if (vFloat || wFloat) { if (!wFloat) { - doubleW = longW; + doubleW = wideW; } else if (!vFloat) { - doubleV = longV; + doubleV = wideV; } switch (operator) { @@ -2958,60 +2958,60 @@ tclobj_nb_binop(PyObject *v, PyObject *w, enum tclobj_op operator) } else { switch (operator) { case Add: - return PyLong_FromLong(longV + longW); + return PyLong_FromLongLong(wideV + wideW); case Sub: - return PyLong_FromLong(longV - longW); + return PyLong_FromLongLong(wideV - wideW); case Mul: - return PyLong_FromLong(longV * longW); + return PyLong_FromLongLong(wideV * wideW); case And: - return PyLong_FromLong(longV & longW); + return PyLong_FromLongLong(wideV & wideW); case Or: - return PyLong_FromLong(longV | longW); + return PyLong_FromLongLong(wideV | wideW); case Xor: - return PyLong_FromLong(longV ^ longW); + return PyLong_FromLongLong(wideV ^ wideW); case Lshift: - return PyLong_FromLong(longV << longW); + return PyLong_FromLongLong(wideV << wideW); case Rshift: - return PyLong_FromLong(longV >> longW); + return PyLong_FromLongLong(wideV >> wideW); case Truediv: - if (longW == 0) { + if (wideW == 0) { PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); return NULL; } - return PyFloat_FromDouble((double)longV / (double)longW); + return PyFloat_FromDouble((double)wideV / (double)wideW); case Floordiv: - if (longW == 0) { + if (wideW == 0) { PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); return NULL; } - ldiv_res = ldiv(longV, longW); - if (ldiv_res.rem != 0 && ((longV < 0) ^ (longW < 0))) { + ldiv_res = ldiv(wideV, wideW); + if (ldiv_res.rem != 0 && ((wideV < 0) ^ (wideW < 0))) { ldiv_res.quot--; } return PyLong_FromLong(ldiv_res.quot); case Remainder: - if (longW == 0) { + if (wideW == 0) { PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); return NULL; } - return PyLong_FromLong(((longV % longW) + longW) % longW); + return PyLong_FromLongLong(((wideV % wideW) + wideW) % wideW); case Divmod: - if (longW == 0) { + if (wideW == 0) { PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); return NULL; } - ldiv_res = ldiv(longV, longW); + ldiv_res = ldiv(wideV, wideW); return Py_BuildValue("ll", ldiv_res.quot, ldiv_res.rem); default: @@ -3121,14 +3121,14 @@ tclobj_nb_inplace_binop(PyObject *v, PyObject *w, enum tclobj_op operator) { // NB same chunk of code in tclobj_binop double doubleV = 0.0; - long longV = 0; + Tcl_WideInt wideV = 0; Tcl_Interp *vInterp = NULL; - int vFloat = tohil_pyobj_to_number(v, &longV, &doubleV, &vInterp); + int vFloat = tohil_pyobj_to_number(v, &wideV, &doubleV, &vInterp); double doubleW = 0.0; - long longW = 0; + Tcl_WideInt wideW = 0; Tcl_Interp *wInterp = NULL; - int wFloat = tohil_pyobj_to_number(w, &longW, &doubleW, &wInterp); + int wFloat = tohil_pyobj_to_number(w, &wideW, &doubleW, &wInterp); ldiv_t ldiv_res; @@ -3150,9 +3150,9 @@ tclobj_nb_inplace_binop(PyObject *v, PyObject *w, enum tclobj_op operator) if (vFloat || wFloat) { if (!wFloat) { - doubleW = longW; + doubleW = wideW; } else if (!vFloat) { - doubleV = longV; + doubleV = wideV; } switch (operator) { @@ -3198,63 +3198,63 @@ tclobj_nb_inplace_binop(PyObject *v, PyObject *w, enum tclobj_op operator) } else { switch (operator) { case Add: - Tcl_SetLongObj(writeObj, longV + longW); + Tcl_SetWideIntObj(writeObj, wideV + wideW); break; case Sub: - Tcl_SetLongObj(writeObj, longV - longW); + Tcl_SetWideIntObj(writeObj, wideV - wideW); break; case Mul: - Tcl_SetLongObj(writeObj, longV * longW); + Tcl_SetWideIntObj(writeObj, wideV * wideW); break; case And: - Tcl_SetLongObj(writeObj, longV & longW); + Tcl_SetWideIntObj(writeObj, wideV & wideW); break; case Or: - Tcl_SetLongObj(writeObj, longV | longW); + Tcl_SetWideIntObj(writeObj, wideV | wideW); break; case Xor: - Tcl_SetLongObj(writeObj, longV ^ longW); + Tcl_SetWideIntObj(writeObj, wideV ^ wideW); break; case Lshift: - Tcl_SetLongObj(writeObj, longV << longW); + Tcl_SetWideIntObj(writeObj, wideV << wideW); break; case Rshift: - Tcl_SetLongObj(writeObj, longV >> longW); + Tcl_SetWideIntObj(writeObj, wideV >> wideW); break; case Truediv: - if (longW == 0) { + if (wideW == 0) { PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); return NULL; } - Tcl_SetDoubleObj(writeObj, (double)longV / (double)longW); + Tcl_SetDoubleObj(writeObj, (double)wideV / (double)wideW); break; case Floordiv: - if (longW == 0) { + if (wideW == 0) { PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); return NULL; } - ldiv_res = ldiv(longV, longW); - if (ldiv_res.rem != 0 && ((longV < 0) ^ (longW < 0))) { + ldiv_res = ldiv(wideV, wideW); + if (ldiv_res.rem != 0 && ((wideV < 0) ^ (wideW < 0))) { ldiv_res.quot--; } Tcl_SetLongObj(writeObj, ldiv_res.quot); break; case Remainder: - if (longW == 0) { + if (wideW == 0) { PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); return NULL; } - Tcl_SetLongObj(writeObj, ((longV % longW) + longW) % longW); + Tcl_SetWideIntObj(writeObj, ((wideV % wideW) + wideW) % wideW); break; default: @@ -4037,10 +4037,10 @@ tohil_python_return(Tcl_Interp *interp, int tcl_result, PyObject *toType, Tcl_Ob } if (STREQU(toString, "int")) { - long longValue; + Tcl_WideInt wideValue; - if (Tcl_GetLongFromObj(interp, resultObj, &longValue) == TCL_OK) { - return PyLong_FromLong(longValue); + if (Tcl_GetWideIntFromObj(interp, resultObj, &wideValue) == TCL_OK) { + return PyLong_FromLongLong(wideValue); } PyErr_SetString(PyExc_ValueError, Tcl_GetString(Tcl_GetObjResult(interp))); return NULL; @@ -4281,7 +4281,7 @@ tohil_incr(PyObject *m, PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"var", "incr", NULL}; char *var = NULL; - long longValue = 0; + Tcl_WideInt wideValue = 0; long increment = 1; Tcl_Interp *interp = tohilstate(m)->interp; @@ -4290,32 +4290,32 @@ tohil_incr(PyObject *m, PyObject *args, PyObject *kwargs) Tcl_Obj *obj = Tcl_GetVar2Ex(interp, var, NULL, 0); if (obj == NULL) { - longValue = increment; - obj = Tcl_NewLongObj(longValue); + wideValue = increment; + obj = Tcl_NewWideIntObj(wideValue); if (Tcl_SetVar2Ex(interp, var, NULL, obj, (TCL_LEAVE_ERR_MSG)) == NULL) { goto type_error; } } else { - if (Tcl_GetLongFromObj(interp, obj, &longValue) == TCL_ERROR) { + if (Tcl_GetWideIntFromObj(interp, obj, &wideValue) == TCL_ERROR) { type_error: PyErr_SetString(PyExc_TypeError, Tcl_GetString(Tcl_GetObjResult(interp))); return NULL; } - longValue += increment; + wideValue += increment; if (Tcl_IsShared(obj)) { Tcl_DecrRefCount(obj); obj = Tcl_DuplicateObj(obj); - Tcl_SetLongObj(obj, longValue); + Tcl_SetWideIntObj(obj, wideValue); if (Tcl_SetVar2Ex(interp, var, NULL, obj, (TCL_LEAVE_ERR_MSG)) == NULL) { goto type_error; } } else { - Tcl_SetLongObj(obj, longValue); + Tcl_SetWideIntObj(obj, wideValue); } } - return PyLong_FromLong(longValue); + return PyLong_FromLongLong(wideValue); } // tohil.unset - from python unset a variable or array element in the tcl From 5904070b4c328b67bc7278df928c28c840c80760 Mon Sep 17 00:00:00 2001 From: Karl Lehenbauer Date: Fri, 19 Nov 2021 17:53:26 +0000 Subject: [PATCH 2/6] major improvements to numeric python object conversion to tcl When creating a new Tcl object from a Python object, and the Python object was numeric, tohil always converted it to a string, which thereby supported both Tcl and Python's arbitrary precision arithmetic, but would be slower than using access functions that worked on machine-native numeric types. Tohil, now, if the python object is a number, tries PyLong_AsLongAndOverflow to convert to long and if it works without overflow, returns a Tcl_NewLongObj. If it overflowed, tohil tries PyLong_AsLongLongAndOverflow, and if that doesn't overflow, tohil returns a new wide integer through Tcl_NewWideIntObj. Likewise if the number is floating point, it is converted through PyFLoat_AsDouble and Tcl_NewDoubleObj. Now, only if the number is wider than a long long, or complex, is it converted to a string for Tcl. --- generic/tohil.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/generic/tohil.c b/generic/tohil.c index 495de41..aecbc6b 100644 --- a/generic/tohil.c +++ b/generic/tohil.c @@ -529,13 +529,41 @@ _pyObjToTcl(Tcl_Interp *interp, PyObject *pObj) ckfree(utf8string); Py_DECREF(pBytesObj); } else if (PyNumber_Check(pObj)) { - /* We go via string to support arbitrary length numbers */ + // we go via string to support arbitrary length numbers if (PyLong_Check(pObj)) { + int overflow = 0; + // try long conversion + long longValue = PyLong_AsLongAndOverflow(pObj, &overflow); + if (!overflow) + return Tcl_NewLongObj(longValue); + + // not big enough try long long conversion + overflow = 0; + Tcl_WideInt wideValue = PyLong_AsLongLongAndOverflow(pObj, &overflow); + if (!overflow) { + return Tcl_NewWideIntObj(wideValue); + } + // still not big enough just use strings + // (both python and tcl have bignum support) pStrObj = PyNumber_ToBase(pObj, 10); } else { - assert(PyComplex_Check(pObj) || PyFloat_Check(pObj)); + // it's not int, it better be float or complex + if (PyFloat_Check(pObj)) { + // it's float, effeciently get it from python + // to tcl + double doubleValue = PyFloat_AsDouble(pObj); + if (doubleValue == 1.0 && PyErr_Occurred()) { + return NULL; + } + return Tcl_NewDoubleObj(doubleValue); + } + // it has to be complex type at this point, punt to string + assert(PyComplex_Check(pObj)); pStrObj = PyObject_Str(pObj); } + + // still here? it's either wider than a long long, or + // complex, send it to tcl as a string if (pStrObj == NULL) return NULL; pBytesObj = PyUnicode_AsUTF8String(pStrObj); From 2df75cdb691fbbad9c64c556ab9a769034d6b69e Mon Sep 17 00:00:00 2001 From: Karl Lehenbauer Date: Fri, 19 Nov 2021 23:36:09 +0000 Subject: [PATCH 3/6] extend tclobj math to wide int aka long long also expand hypothesis number ranges on tclobj math tests --- generic/tohil.c | 24 +++---- tests/test_tclobj_math.py | 140 ++++++++++++++++++++++---------------- 2 files changed, 92 insertions(+), 72 deletions(-) diff --git a/generic/tohil.c b/generic/tohil.c index aecbc6b..277ae7d 100644 --- a/generic/tohil.c +++ b/generic/tohil.c @@ -2918,7 +2918,7 @@ tclobj_nb_binop(PyObject *v, PyObject *w, enum tclobj_op operator) Tcl_Interp *wInterp = NULL; int wFloat = tohil_pyobj_to_number(w, &wideW, &doubleW, &wInterp); - ldiv_t ldiv_res; + lldiv_t lldiv_res; double quotient; double remainder; @@ -3021,11 +3021,11 @@ tclobj_nb_binop(PyObject *v, PyObject *w, enum tclobj_op operator) PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); return NULL; } - ldiv_res = ldiv(wideV, wideW); - if (ldiv_res.rem != 0 && ((wideV < 0) ^ (wideW < 0))) { - ldiv_res.quot--; + lldiv_res = lldiv(wideV, wideW); + if (lldiv_res.rem != 0 && ((wideV < 0) ^ (wideW < 0))) { + lldiv_res.quot--; } - return PyLong_FromLong(ldiv_res.quot); + return PyLong_FromLongLong(lldiv_res.quot); case Remainder: if (wideW == 0) { @@ -3039,8 +3039,8 @@ tclobj_nb_binop(PyObject *v, PyObject *w, enum tclobj_op operator) PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); return NULL; } - ldiv_res = ldiv(wideV, wideW); - return Py_BuildValue("ll", ldiv_res.quot, ldiv_res.rem); + lldiv_res = lldiv(wideV, wideW); + return Py_BuildValue("LL", lldiv_res.quot, lldiv_res.rem); default: Py_RETURN_NOTIMPLEMENTED; @@ -3158,7 +3158,7 @@ tclobj_nb_inplace_binop(PyObject *v, PyObject *w, enum tclobj_op operator) Tcl_Interp *wInterp = NULL; int wFloat = tohil_pyobj_to_number(w, &wideW, &doubleW, &wInterp); - ldiv_t ldiv_res; + lldiv_t lldiv_res; if (vFloat < 0 || wFloat < 0) { if (operator== Add) { @@ -3270,11 +3270,11 @@ tclobj_nb_inplace_binop(PyObject *v, PyObject *w, enum tclobj_op operator) PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); return NULL; } - ldiv_res = ldiv(wideV, wideW); - if (ldiv_res.rem != 0 && ((wideV < 0) ^ (wideW < 0))) { - ldiv_res.quot--; + lldiv_res = lldiv(wideV, wideW); + if (lldiv_res.rem != 0 && ((wideV < 0) ^ (wideW < 0))) { + lldiv_res.quot--; } - Tcl_SetLongObj(writeObj, ldiv_res.quot); + Tcl_SetWideIntObj(writeObj, lldiv_res.quot); break; case Remainder: diff --git a/tests/test_tclobj_math.py b/tests/test_tclobj_math.py index 57b42b7..eddf2bc 100644 --- a/tests/test_tclobj_math.py +++ b/tests/test_tclobj_math.py @@ -5,8 +5,10 @@ import tohil +tohil.eval("set tcl_precision 17") + class TestTclObj(unittest.TestCase): - @given(st.integers(-40000, 40000), st.integers(-40000, 40000)) + @given(st.integers(-2**62, 2**62-1), st.integers(-2**62, 2**62-1)) def test_tclobj_math1(self, i, j): """exercise tohil.tclobj 'plus' math ops""" t = tohil.tclobj(i) @@ -20,7 +22,7 @@ def test_tclobj_math1(self, i, j): assert(t + float(t) == i + float(i)) assert(6. + t == 6. + i) - @given(st.integers(-40000, 40000), st.integers(-40000, 40000)) + @given(st.integers(-2**62, 2**62), st.integers(-2**62, 2**62-1)) def test_tclobj_math2(self, i, j): """exercise tohil.tclobj 'minus' math ops""" ti = tohil.tclobj(i) @@ -32,11 +34,11 @@ def test_tclobj_math2(self, i, j): assert(tj - 4. == j - 4.) assert(tj - float(tj) == 0.) - assert(6. - ti == 6 - i) + assert(6. - ti == 6. - i) assert(tj - ti == j - i) - @given(st.floats(-40000.0, 40000.0), st.integers(-40000, 40000)) + @given(st.floats(-2**62, 2**62.0), st.integers(-2**62, 2**62-1)) def test_tclobj_math3(self, f, i): """exercise tohil.tclobj plus float math ops""" t = tohil.tclobj(f) @@ -50,7 +52,7 @@ def test_tclobj_math3(self, f, i): assert(int(t) - i == int(t) - i) assert(6. - t == 6. - f) - @given(st.integers(-40000.0, 40000.0), st.integers(-40000, 40000)) + @given(st.integers(-2**31, 2**31-1), st.integers(-2**31, 2*31-1)) def test_tclobj_math4(self, i, j): """exercise tohil.tclobj multiply math ops""" ti = tohil.tclobj(i) @@ -68,7 +70,7 @@ def test_tclobj_math4(self, i, j): assert(ti * float(ti) == i * float(i)) assert(8. * ti == 8. * i) - @given(st.floats(-40000.0, 40000.0), st.floats(-40000, 40000)) + @given(st.floats(-2**31, 2**31-1), st.floats(-2**31, 2**31-1)) def test_tclobj_math5(self, u, v): """exercise tohil.tclobj multiply float math ops""" t6 = tohil.tclobj('6.') @@ -82,9 +84,9 @@ def test_tclobj_math5(self, u, v): assert(tu * tv == u * v) - @given(st.integers(-40000, 40000), st.integers(-40000, 40000)) + @given(st.integers(-2**31, 2**31-1), st.integers(-2**31, 2**31-1)) def test_tclobj_math6(self, i, j): - """exercise tohil.tclobj remainder ops""" + """exercise tohil.tclobj integer remainder ops""" assume(i != 0 and j != 0) ti = tohil.tclobj(i) @@ -100,8 +102,26 @@ def test_tclobj_math6(self, i, j): assert(ti % j == i % j) assert(i % tj == i % j) - @given(st.integers(0, 512), st.integers(0, 22)) + @given(st.floats(-2**31, 2**31-1), st.floats(-2**31, 2**31-1)) def test_tclobj_math7(self, i, j): + """exercise tohil.tclobj float remainder ops""" + assume(i != 0 and j != 0) + + ti = tohil.tclobj(i) + tj = tohil.tclobj(j) + + assert(abs(ti % 7 - i % 7) < 1e-6) + assert(abs(ti % j - i % j) < 1e-6) + assert(abs(11 % ti - 11 % i) < 1e-6) + assert(abs(-7 % ti - -7 % i) < 1e-6) + assert(abs(ti % -7 - i % -7) < 1e-6) + + assert(abs(ti % tj - i % j) < 1e-6) + assert(abs(ti % j - i % j) < 1e-6) + assert(abs(i % tj - i % j) < 1e-6) + + @given(st.integers(0, 512), st.integers(0, 22)) + def test_tclobj_math8(self, i, j): """exercise tohil.tclobj left shift math ops""" ti = tohil.tclobj(i) tj = tohil.tclobj(j) @@ -113,8 +133,8 @@ def test_tclobj_math7(self, i, j): assert(ti << 4 == i << 4) assert(4 << tj == 4 << j) - @given(st.integers(0, 2000000000), st.integers(0, 23)) - def test_tclobj_math8(self, i, j): + @given(st.integers(0, 2**63-1), st.integers(0, 64)) + def test_tclobj_math9(self, i, j): """exercise tohil.tclobj right shift math ops""" ti = tohil.tclobj(i) tj = tohil.tclobj(j) @@ -123,8 +143,8 @@ def test_tclobj_math8(self, i, j): assert(ti >> tj == i >> j) assert(i >> tj == i >> j) - @given(st.integers(0, 2000000000), st.integers(0, 2000000000)) - def test_tclobj_math9(self, i, j): + @given(st.integers(0, 2**63-1), st.integers(0, 2**63-1)) + def test_tclobj_math10(self, i, j): """exercise tohil.tclobj "and" math ops""" ti = tohil.tclobj(i) tj = tohil.tclobj(j) @@ -133,8 +153,8 @@ def test_tclobj_math9(self, i, j): assert(ti & tj == i & j) assert(i & tj == i & j) - @given(st.integers(0, 2000000000), st.integers(0, 2000000000)) - def test_tclobj_math10(self, i, j): + @given(st.integers(0, 2**63-1), st.integers(0, 2**63-1)) + def test_tclobj_math11(self, i, j): """exercise tohil.tclobj "or" math ops""" ti = tohil.tclobj(i) tj = tohil.tclobj(j) @@ -143,8 +163,8 @@ def test_tclobj_math10(self, i, j): assert(ti | tj == i | j) assert(i | tj == i | j) - @given(st.integers(0, 2000000000), st.integers(0, 2000000000)) - def test_tclobj_math11(self, i, j): + @given(st.integers(0, 2**63-1), st.integers(0, 2**63-1)) + def test_tclobj_math12(self, i, j): """exercise tohil.tclobj "xor" math ops""" ti = tohil.tclobj(i) tj = tohil.tclobj(j) @@ -153,8 +173,8 @@ def test_tclobj_math11(self, i, j): assert(ti ^ tj == i ^ j) assert(i ^ tj == i ^ j) - @given(st.integers(-1000000000, 1000000000), st.integers(-1000000000, 1000000000)) - def test_tclobj_math12(self, i, j): + @given(st.integers(-2**62, 2**62-1), st.integers(-2**62, 2**62-1)) + def test_tclobj_math13(self, i, j): """exercise tohil.tclobj "inplace add" math ops""" ti = tohil.tclobj(i) tj = tohil.tclobj(j) @@ -170,8 +190,8 @@ def test_tclobj_math12(self, i, j): ti += tj assert(ti == i + j) - @given(st.integers(-1000000000, 1000000000), st.integers(-1000000000, 1000000000)) - def test_tclobj_math13(self, i, j): + @given(st.integers(-2**62, 2**62-1), st.integers(-2**62, 2**62-1)) + def test_tclobj_math14(self, i, j): """exercise tohil.tclobj "inplace subtract" math ops""" ti = tohil.tclobj(i) tj = tohil.tclobj(j) @@ -183,8 +203,8 @@ def test_tclobj_math13(self, i, j): ti -= tj assert(ti == i - j) - @given(st.integers(-40000, 40000), st.integers(-40000, 40000)) - def test_tclobj_math14(self, i, j): + @given(st.integers(-2**31, 2**31-1), st.integers(-2**31, 2**31-1)) + def test_tclobj_math15(self, i, j): """exercise tohil.tclobj "inplace multiply" math ops""" ti = tohil.tclobj(i) tj = tohil.tclobj(j) @@ -197,8 +217,8 @@ def test_tclobj_math14(self, i, j): assert(ti == i * j) - @given(st.integers(0, 512), st.integers(0, 22)) - def test_tclobj_math15(self, i, j): + @given(st.integers(0, 512), st.integers(0, 54)) + def test_tclobj_math16(self, i, j): """exercise tohil.tclobj "inplace left shift" math ops""" ti = tohil.tclobj(i) @@ -217,8 +237,8 @@ def test_tclobj_math15(self, i, j): ti <<= tj assert(ti == i << j) - @given(st.integers(0, 2000000000), st.integers(0, 23)) - def test_tclobj_math16(self, i, j): + @given(st.integers(0, 2**63-1), st.integers(0, 64)) + def test_tclobj_math17(self, i, j): """exercise tohil.tclobj right shift math ops""" ti = tohil.tclobj(i) tj = tohil.tclobj(j) @@ -230,8 +250,8 @@ def test_tclobj_math16(self, i, j): ti >>= tj assert(ti == i >> j) - @given(st.integers(0, 2000000000), st.integers(0, 2000000000)) - def test_tclobj_math16(self, i, j): + @given(st.integers(0, 2**63-1), st.integers(0, 2**63-1)) + def test_tclobj_math18(self, i, j): """exercise tohil.tclobj "inplace bitwise or" math ops""" ti = tohil.tclobj(i) tj = tohil.tclobj(j) @@ -243,8 +263,8 @@ def test_tclobj_math16(self, i, j): ti |= tj assert(ti == i | j) - @given(st.integers(0, 2000000000), st.integers(0, 2000000000)) - def test_tclobj_math17(self, i, j): + @given(st.integers(0, 2**63-1), st.integers(0, 2**63-1)) + def test_tclobj_math19(self, i, j): """exercise tohil.tclobj "inplace bitwise and" math ops""" ti = tohil.tclobj(i) tj = tohil.tclobj(j) @@ -256,8 +276,8 @@ def test_tclobj_math17(self, i, j): ti &= tj assert(ti == i & j) - @given(st.integers(0, 2000000000), st.integers(0, 2000000000)) - def test_tclobj_math17(self, i, j): + @given(st.integers(0, 2**63-1), st.integers(0, 2**63-1)) + def test_tclobj_math20(self, i, j): """exercise tohil.tclobj "inplace bitwise xor" math ops""" ti = tohil.tclobj(i) tj = tohil.tclobj(j) @@ -269,18 +289,18 @@ def test_tclobj_math17(self, i, j): ti ^= tj assert(ti == i ^ j) - @given(st.integers(-2000000000, 2000000000), st.integers(-2000000000, 2000000000)) - def test_tclobj_math18(self, i, j): + @given(st.integers(-2**32, 2**32-1), st.integers(-2**32, 2**32-1)) + def test_tclobj_math21(self, i, j): """exercise tohil.tclobj "true divide" math ops""" assume(j != 0) ti = tohil.tclobj(i) tj = tohil.tclobj(j) - assert(ti / j == i / j) - assert(ti / tj == i / j) - assert(i / tj == i / j) + assert(abs(ti / j - i / j) < 1e-6) + assert(abs(ti / tj - i / j) < 1e-6) + assert(abs(i / tj - i / j) < 1e-6) - @given(st.integers(-2000000000, 2000000000), st.integers(-2000000000, 2000000000)) + @given(st.integers(-2**32, 2**32-1), st.integers(-2**32, 2**32-1)) def test_tclobj_math19(self, i, j): """exercise tohil.tclobj "inplace true divide" math ops""" assume(j != 0) @@ -288,14 +308,14 @@ def test_tclobj_math19(self, i, j): tj = tohil.tclobj(j) ti /= j - assert(abs(ti - i / j) < 0.000000001) + assert(abs(ti - i / j) < 1e-6) ti = tohil.tclobj(i) ti /= tj - assert(abs(ti - i / j) < 0.000000001) + assert(abs(ti - i / j) < 1e-6) - @given(st.integers(-2000000000, 2000000000), st.integers(-2000000000, 2000000000)) - def test_tclobj_math20(self, i, j): + @given(st.integers(-2**63, 2**63-1), st.integers(-2**63, 2**63-1)) + def test_tclobj_math22(self, i, j): """exercise tohil.tclobj "floor divide" math ops""" assume(j != 0) ti = tohil.tclobj(i) @@ -305,8 +325,8 @@ def test_tclobj_math20(self, i, j): assert(ti // tj == i // j) assert(i // tj == i // j) - @given(st.integers(-2000000000, 2000000000), st.integers(-2000000000, 2000000000)) - def test_tclobj_math21(self, i, j): + @given(st.integers(-2**63, 2**63-1), st.integers(-2**63, 2**63-1)) + def test_tclobj_math23(self, i, j): """exercise tohil.tclobj "inplace floor divide" integer math ops""" assume(j != 0) ti = tohil.tclobj(i) @@ -319,8 +339,8 @@ def test_tclobj_math21(self, i, j): ti //= tj assert(ti == i // j) - @given(st.floats(-100000000, 100000000), st.floats(-100000000, 100000000)) - def test_tclobj_math22(self, u, v): + @given(st.floats(-2**31, 2**31-1), st.floats(-2**31, 2**31-1)) + def test_tclobj_math24(self, u, v): """exercise tohil.tclobj "inplace floor divide" float math ops""" assume(v < -0.1 or v > 0.1) tu = tohil.tclobj(u) @@ -333,8 +353,8 @@ def test_tclobj_math22(self, u, v): tu //= tv assert(tu == u // v) - @given(st.integers(-1000000000, 1000000000), st.integers(-1000000000, 1000000000)) - def test_tclobj_math23(self, i, j): + @given(st.integers(-2**62, 2**62-1), st.integers(-2**62, 2**62-1)) + def test_tclobj_math25(self, i, j): """exercise tohil.tclobj "inplace remainder" integer math ops""" assume(j != 0) ti = tohil.tclobj(i) @@ -347,8 +367,8 @@ def test_tclobj_math23(self, i, j): ti %= tj assert(ti == i % j) - @given(st.floats(-2000000000, 2000000000), st.floats(-2000000000, 2000000000)) - def test_tclobj_math24(self, u, v): + @given(st.floats(-2**31, 2**31-1), st.floats(-2**31, 2**31-1)) + def test_tclobj_math26(self, u, v): """exercise tohil.tclobj "inplace remainder" float math ops""" assume(v != 0) tu = tohil.tclobj(u) @@ -361,8 +381,8 @@ def test_tclobj_math24(self, u, v): tu %= tv assert(abs(tu - u % v) < 0.000001) - @given(st.integers(-2000000000, 2000000000), st.floats(-2000000000, 2000000000)) - def test_tclobj_math25(self, i, v): + @given(st.floats(-2**31, 2**31-1), st.floats(-2**31, 2**31-1)) + def test_tclobj_math27(self, i, v): """exercise tohil.tclobj "inplace remainder" mixed-type math ops""" assume(v != 0) ti = tohil.tclobj(i) @@ -380,13 +400,13 @@ def test_tclobj_math25(self, i, v): tv %= i assert(abs(tv - v % i) < 0.000001) - def test_tclobj_math26(self): - """exercise tohil.tclobj division by zero exceptions""" - t5 = tohil.tclobj(5) - t0 = tohil.tclobj(0) + def test_tclobj_math28(self): + """exercise tohil.tclobj float division by zero exceptions""" + t5 = tohil.tclobj(5.) + t0 = tohil.tclobj(0.) with self.assertRaises(ZeroDivisionError): - t5 / 0 + t5 / 0. with self.assertRaises(ZeroDivisionError): t5 / t0 @@ -394,7 +414,7 @@ def test_tclobj_math26(self): with self.assertRaises(ZeroDivisionError): 5 / t0 - def test_tclobj_math27(self): + def test_tclobj_math29(self): """exercise tohil.tclobj integer division by zero exceptions""" t5 = tohil.tclobj(5) t0 = tohil.tclobj(0) @@ -408,7 +428,7 @@ def test_tclobj_math27(self): with self.assertRaises(ZeroDivisionError): 5 // t0 - def test_tclobj_math28(self): + def test_tclobj_math30(self): """exercise tohil.tclobj integer remainder of by zero exceptions""" t5 = tohil.tclobj(5) t0 = tohil.tclobj(0) From 1724609ce5d245734955da1e40659a1307c40bec Mon Sep 17 00:00:00 2001 From: Karl Lehenbauer Date: Fri, 19 Nov 2021 23:46:40 +0000 Subject: [PATCH 4/6] Make TclProcs default to returning tohil.tclobj Fixes issue #72. --- pysrc/tohil/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pysrc/tohil/__init__.py b/pysrc/tohil/__init__.py index ff509be..feb92d1 100644 --- a/pysrc/tohil/__init__.py +++ b/pysrc/tohil/__init__.py @@ -103,7 +103,7 @@ class ShadowDict(_MutableMapping): def __init__(self, tcl_array, *, default=None, to=None): self.tcl_array = tcl_array if to is None: - self.to_type = str + self.to_type = tohil.tclobj else: self.to_type = to @@ -370,7 +370,7 @@ class TclProc: passthrough_trampoline function, below. """ - def __init__(self, proc, to=str): + def __init__(self, proc, to=tclobj): self.proc = proc self.function_name = self._proc_to_function(proc) From c5499a316601d62042905d58a7eb616aa455fe64 Mon Sep 17 00:00:00 2001 From: Karl Lehenbauer Date: Sat, 20 Nov 2021 00:12:12 +0000 Subject: [PATCH 5/6] tclobjs math is limited to 64 bits --- tests/test_tclobj_math.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_tclobj_math.py b/tests/test_tclobj_math.py index eddf2bc..344d2d5 100644 --- a/tests/test_tclobj_math.py +++ b/tests/test_tclobj_math.py @@ -133,7 +133,7 @@ def test_tclobj_math8(self, i, j): assert(ti << 4 == i << 4) assert(4 << tj == 4 << j) - @given(st.integers(0, 2**63-1), st.integers(0, 64)) + @given(st.integers(0, 2**63-1), st.integers(0, 63)) def test_tclobj_math9(self, i, j): """exercise tohil.tclobj right shift math ops""" ti = tohil.tclobj(i) @@ -237,7 +237,7 @@ def test_tclobj_math16(self, i, j): ti <<= tj assert(ti == i << j) - @given(st.integers(0, 2**63-1), st.integers(0, 64)) + @given(st.integers(0, 2**63-1), st.integers(0, 63)) def test_tclobj_math17(self, i, j): """exercise tohil.tclobj right shift math ops""" ti = tohil.tclobj(i) From 4ba485ed4a0d7e325bc1dd42e17fc67a5c057b3b Mon Sep 17 00:00:00 2001 From: Karl Lehenbauer Date: Sat, 20 Nov 2021 00:38:05 +0000 Subject: [PATCH 6/6] add note about bignums and tclobj 64-bit calculation limits --- Doc/reference/tohil_types.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Doc/reference/tohil_types.rst b/Doc/reference/tohil_types.rst index 9ed72fd..1a54934 100644 --- a/Doc/reference/tohil_types.rst +++ b/Doc/reference/tohil_types.rst @@ -115,6 +115,20 @@ float. Tohil will raise a TypeError exception if the Tcl object can't be converted to the Python type that's needed. +Both Python and Tcl support arbitrarily large numbers, and +you can freely assign tclobjs from arbitrarily large numbers produced +by Python, and vice versa. + +Note that Python calculations performed using Tohil's tclobjs are +limited to 64 bits (or whatever width a C language "long long" is on the +machine tohil was compiled for.) While this should be fine in +the overwhelming majority of cases, if you are manipulating numbers +that are wider than 64 bits (i.e. less than +-9,223,372,036,854,775,808 or greater than 9,223,372,036,854,775,807), +you will need to move them from tclobjs to native Python ints, first, +by invoking ``int()`` on the tclobjs of interest. + + .. _tohil_bitstring-ops: =================================