csignum-fast 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- csignum_fast-1.0.0/LICENSE +21 -0
- csignum_fast-1.0.0/MANIFEST.in +2 -0
- csignum_fast-1.0.0/PKG-INFO +58 -0
- csignum_fast-1.0.0/README.md +41 -0
- csignum_fast-1.0.0/csignum_fast.egg-info/PKG-INFO +58 -0
- csignum_fast-1.0.0/csignum_fast.egg-info/SOURCES.txt +10 -0
- csignum_fast-1.0.0/csignum_fast.egg-info/dependency_links.txt +1 -0
- csignum_fast-1.0.0/csignum_fast.egg-info/top_level.txt +1 -0
- csignum_fast-1.0.0/pyproject.toml +27 -0
- csignum_fast-1.0.0/setup.cfg +4 -0
- csignum_fast-1.0.0/signum.cpp +130 -0
- csignum_fast-1.0.0/tests/test_signum.py +307 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Alexandru Colesnicov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: csignum-fast
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: High-performance universal sign function
|
|
5
|
+
Author-email: Alexandru Colesnicov <acolesnicov@gmx.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: math,signum,sign,c-extension
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
11
|
+
Classifier: Programming Language :: C
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# csignum-fast
|
|
19
|
+

|
|
20
|
+

|
|
21
|
+

|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
**A robust, high-performance C++ implementation of the universal sign function for Python.**
|
|
25
|
+
*Released on December 25, 2025 — The Christmas Edition.*
|
|
26
|
+
|
|
27
|
+
## Key Features
|
|
28
|
+
|
|
29
|
+
1. **Uniform Results**: Always returns only `-1`, `0`, or `1` as an `int` for valid numeric comparisons.
|
|
30
|
+
2. **Correct Edge Case Handling**:
|
|
31
|
+
* `sign(+0.0)` and `sign(-0.0)` return `0`.
|
|
32
|
+
* `sign(inf)` returns `1`, `sign(-inf)` returns `-1`.
|
|
33
|
+
* For any **NaN** (float NaN, Decimal NaN, etc.), it returns `math.nan` (float).
|
|
34
|
+
3. **Comprehensive Duck Typing**: Delegates comparisons to the argument's class. Works seamlessly with:
|
|
35
|
+
* Built-in `int` (including arbitrary-precision), `bool`, and `float`.
|
|
36
|
+
* `fractions.Fraction` and `decimal.Decimal`.
|
|
37
|
+
* Any existing and future objects that support rich comparisons with numbers.
|
|
38
|
+
4. **Informative Error Handling for Easy Debugging**: Provides clear, descriptive `TypeError` messages when passed non-numeric, non-scalar, or incomparable arguments.
|
|
39
|
+
5. **High Performance**: Implemented in C++ using a branchless ternary logic approach (no `if-else` chains), ensuring maximum execution speed.
|
|
40
|
+
6. **Thoroughly Tested**: Tested on 51 cases including different types, edge cases, new custom class, and inappropriate arguments.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install csignum-fast
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from signum import sign
|
|
52
|
+
from decimal import Decimal
|
|
53
|
+
|
|
54
|
+
print(sign(-10**100)) # -1
|
|
55
|
+
print(sign(3.14)) # 1
|
|
56
|
+
print(sign(Decimal("0.0"))) # 0
|
|
57
|
+
print(sign(float('-nan'))) # nan
|
|
58
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# csignum-fast
|
|
2
|
+

|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
**A robust, high-performance C++ implementation of the universal sign function for Python.**
|
|
8
|
+
*Released on December 25, 2025 — The Christmas Edition.*
|
|
9
|
+
|
|
10
|
+
## Key Features
|
|
11
|
+
|
|
12
|
+
1. **Uniform Results**: Always returns only `-1`, `0`, or `1` as an `int` for valid numeric comparisons.
|
|
13
|
+
2. **Correct Edge Case Handling**:
|
|
14
|
+
* `sign(+0.0)` and `sign(-0.0)` return `0`.
|
|
15
|
+
* `sign(inf)` returns `1`, `sign(-inf)` returns `-1`.
|
|
16
|
+
* For any **NaN** (float NaN, Decimal NaN, etc.), it returns `math.nan` (float).
|
|
17
|
+
3. **Comprehensive Duck Typing**: Delegates comparisons to the argument's class. Works seamlessly with:
|
|
18
|
+
* Built-in `int` (including arbitrary-precision), `bool`, and `float`.
|
|
19
|
+
* `fractions.Fraction` and `decimal.Decimal`.
|
|
20
|
+
* Any existing and future objects that support rich comparisons with numbers.
|
|
21
|
+
4. **Informative Error Handling for Easy Debugging**: Provides clear, descriptive `TypeError` messages when passed non-numeric, non-scalar, or incomparable arguments.
|
|
22
|
+
5. **High Performance**: Implemented in C++ using a branchless ternary logic approach (no `if-else` chains), ensuring maximum execution speed.
|
|
23
|
+
6. **Thoroughly Tested**: Tested on 51 cases including different types, edge cases, new custom class, and inappropriate arguments.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install csignum-fast
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from signum import sign
|
|
35
|
+
from decimal import Decimal
|
|
36
|
+
|
|
37
|
+
print(sign(-10**100)) # -1
|
|
38
|
+
print(sign(3.14)) # 1
|
|
39
|
+
print(sign(Decimal("0.0"))) # 0
|
|
40
|
+
print(sign(float('-nan'))) # nan
|
|
41
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: csignum-fast
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: High-performance universal sign function
|
|
5
|
+
Author-email: Alexandru Colesnicov <acolesnicov@gmx.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: math,signum,sign,c-extension
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
11
|
+
Classifier: Programming Language :: C
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# csignum-fast
|
|
19
|
+

|
|
20
|
+

|
|
21
|
+

|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
**A robust, high-performance C++ implementation of the universal sign function for Python.**
|
|
25
|
+
*Released on December 25, 2025 — The Christmas Edition.*
|
|
26
|
+
|
|
27
|
+
## Key Features
|
|
28
|
+
|
|
29
|
+
1. **Uniform Results**: Always returns only `-1`, `0`, or `1` as an `int` for valid numeric comparisons.
|
|
30
|
+
2. **Correct Edge Case Handling**:
|
|
31
|
+
* `sign(+0.0)` and `sign(-0.0)` return `0`.
|
|
32
|
+
* `sign(inf)` returns `1`, `sign(-inf)` returns `-1`.
|
|
33
|
+
* For any **NaN** (float NaN, Decimal NaN, etc.), it returns `math.nan` (float).
|
|
34
|
+
3. **Comprehensive Duck Typing**: Delegates comparisons to the argument's class. Works seamlessly with:
|
|
35
|
+
* Built-in `int` (including arbitrary-precision), `bool`, and `float`.
|
|
36
|
+
* `fractions.Fraction` and `decimal.Decimal`.
|
|
37
|
+
* Any existing and future objects that support rich comparisons with numbers.
|
|
38
|
+
4. **Informative Error Handling for Easy Debugging**: Provides clear, descriptive `TypeError` messages when passed non-numeric, non-scalar, or incomparable arguments.
|
|
39
|
+
5. **High Performance**: Implemented in C++ using a branchless ternary logic approach (no `if-else` chains), ensuring maximum execution speed.
|
|
40
|
+
6. **Thoroughly Tested**: Tested on 51 cases including different types, edge cases, new custom class, and inappropriate arguments.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install csignum-fast
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from signum import sign
|
|
52
|
+
from decimal import Decimal
|
|
53
|
+
|
|
54
|
+
print(sign(-10**100)) # -1
|
|
55
|
+
print(sign(3.14)) # 1
|
|
56
|
+
print(sign(Decimal("0.0"))) # 0
|
|
57
|
+
print(sign(float('-nan'))) # nan
|
|
58
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
signum
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "csignum-fast"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "High-performance universal sign function"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Alexandru Colesnicov", email = "acolesnicov@gmx.com"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["math", "signum", "sign", "c-extension"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Topic :: Software Development :: Libraries",
|
|
20
|
+
"Programming Language :: C",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[tool.setuptools]
|
|
25
|
+
ext-modules = [
|
|
26
|
+
{name = "signum", sources = ["signum.cpp"]}
|
|
27
|
+
]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* signum.cpp
|
|
3
|
+
* A robust, branchless implementation of the universal sign function for Python.
|
|
4
|
+
* Version: 1.0.0
|
|
5
|
+
* Released: December 25, 2025 (Christmas Edition)
|
|
6
|
+
* Author: Alexandru Colesnicov
|
|
7
|
+
* License: MIT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include <Python.h>
|
|
11
|
+
|
|
12
|
+
static PyObject *
|
|
13
|
+
signum_sign(PyObject *module, PyObject *x)
|
|
14
|
+
{
|
|
15
|
+
/* Check for numeric NaN */
|
|
16
|
+
double d = PyFloat_AsDouble(x);
|
|
17
|
+
if (Py_IS_NAN(d)) return PyFloat_FromDouble(Py_NAN);
|
|
18
|
+
/* If it is something special, we will try comparisons */
|
|
19
|
+
if (PyErr_Occurred()) PyErr_Clear();
|
|
20
|
+
|
|
21
|
+
PyObject *zero = PyLong_FromLong(0);
|
|
22
|
+
if (!zero) return NULL; /* Memory Error? */
|
|
23
|
+
|
|
24
|
+
int gt = PyObject_RichCompareBool(x, zero, Py_GT) + 1; /* 2: True; 1: False; 0: Error */
|
|
25
|
+
int lt = PyObject_RichCompareBool(x, zero, Py_LT) + 1;
|
|
26
|
+
int res = gt - lt; /* Result, if nothing special */
|
|
27
|
+
int eq = PyObject_RichCompareBool(x, zero, Py_EQ) + 1; /* Used only to process NaN and errors */
|
|
28
|
+
|
|
29
|
+
Py_DECREF(zero); /* Not used anymore */
|
|
30
|
+
|
|
31
|
+
/* gt, lt, eq can be 0, 1, 2; let them be digits in the ternary number system */
|
|
32
|
+
/* code = 9*gt+3*lt+eq is the value of the number written in the ternary system
|
|
33
|
+
with these digits, with possible decimal values from 0 to 26;
|
|
34
|
+
0 means that all three comparisons returned Error (quite possible for inappropriate type);
|
|
35
|
+
26 means that all were True (extremely strange, the argument is > 0, and < 0, and == 0) */
|
|
36
|
+
/* We also use 9 = 8+1, 3 = 4-1, and replace multiplication by 8 and 4 with shift */
|
|
37
|
+
int code = (gt << 3) + (lt << 2) + eq + res;
|
|
38
|
+
/* (gt<<3) = 8*gt; (lt<<2) = 4*gt; code = 8*gt + 4*lt + eq + (gt-lt) = 9*gt+3*lt+eq */
|
|
39
|
+
|
|
40
|
+
switch (code) {
|
|
41
|
+
case 13: { /* 111₃ -> 8+4+1+0: possible NaN (False, False, False) */
|
|
42
|
+
int self_eq = PyObject_RichCompareBool(x, x, Py_EQ);
|
|
43
|
+
switch (self_eq) {
|
|
44
|
+
case -1: return NULL; /* Error in __eq__, we keep current Python error */
|
|
45
|
+
case 0: return PyFloat_FromDouble(Py_NAN); /* NaN: not equal to itself */
|
|
46
|
+
default: goto error; /* Not a NaN: equals to itself; not comparable to 0 */
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
case 14: /* 112₃ -> 8+4+2+0: x == 0 (res= 0) (False, False, True ) */
|
|
50
|
+
case 16: /* 121₃ -> 8+8+1-1: x < 0 (res=-1) (False, True, False) */
|
|
51
|
+
case 22: /* 211₃ -> 16+4+1+1: x > 0 (res= 1) (True, False, False) */
|
|
52
|
+
return PyLong_FromLong((long)res);
|
|
53
|
+
default: /* No more valid cases */
|
|
54
|
+
goto error;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
error:
|
|
58
|
+
if (PyErr_Occurred()) {
|
|
59
|
+
PyObject *type, *value, *traceback;
|
|
60
|
+
/* Extract the current error */
|
|
61
|
+
PyErr_Fetch(&type, &value, &traceback);
|
|
62
|
+
PyErr_NormalizeException(&type, &value, &traceback);
|
|
63
|
+
|
|
64
|
+
/* Prepare the argument details */
|
|
65
|
+
PyObject *repr = PyObject_Repr(x);
|
|
66
|
+
const char *type_name = Py_TYPE(x)->tp_name;
|
|
67
|
+
|
|
68
|
+
/* Prepare the old error as string */
|
|
69
|
+
PyObject *old_msg = PyObject_Str(value);
|
|
70
|
+
const char *old_msg_str = old_msg ? PyUnicode_AsUTF8(old_msg) : "unknown error";
|
|
71
|
+
|
|
72
|
+
/* Format the new message */
|
|
73
|
+
PyErr_Format(PyExc_TypeError,
|
|
74
|
+
"signum.sign: invalid argument `%.160s` (type '%.80s'). "
|
|
75
|
+
"Inner error: %.320s",
|
|
76
|
+
repr ? PyUnicode_AsUTF8(repr) : "???",
|
|
77
|
+
type_name,
|
|
78
|
+
old_msg_str);
|
|
79
|
+
|
|
80
|
+
/* Clean memory */
|
|
81
|
+
Py_XDECREF(repr);
|
|
82
|
+
Py_XDECREF(old_msg);
|
|
83
|
+
Py_XDECREF(type);
|
|
84
|
+
Py_XDECREF(value);
|
|
85
|
+
Py_XDECREF(traceback);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
PyObject *repr = PyObject_Repr(x);
|
|
89
|
+
const char *type_name = Py_TYPE(x)->tp_name;
|
|
90
|
+
|
|
91
|
+
if (repr) {
|
|
92
|
+
PyErr_Format(PyExc_TypeError,
|
|
93
|
+
"signum.sign: invalid argument `%.160s`. "
|
|
94
|
+
"Type '%.80s' does not support order comparisons (>, <, ==) "
|
|
95
|
+
"or NaN detection.",
|
|
96
|
+
PyUnicode_AsUTF8(repr),
|
|
97
|
+
type_name);
|
|
98
|
+
Py_DECREF(repr);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
PyErr_Format(PyExc_TypeError,
|
|
102
|
+
"signum.sign: invalid argument of type '%.80s', "
|
|
103
|
+
"which does not support order comparisons (>, <, ==) and printing.",
|
|
104
|
+
type_name);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return NULL;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* --- FORMALITIES --- */
|
|
111
|
+
|
|
112
|
+
/* List of implemented methods */
|
|
113
|
+
static PyMethodDef SignumMethods[] = {
|
|
114
|
+
{"sign", (PyCFunction)signum_sign, METH_O, "Return the sign of x: -1, 0, 1, or NaN."},
|
|
115
|
+
{NULL, NULL, 0, NULL} /* Stop-string */
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/* Module description */
|
|
119
|
+
static struct PyModuleDef signummodule = {
|
|
120
|
+
PyModuleDef_HEAD_INIT,
|
|
121
|
+
"signum", /* Module name for import */
|
|
122
|
+
"Fast signum implementation with ternary logic.",
|
|
123
|
+
-1,
|
|
124
|
+
SignumMethods
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/* Module initialization */
|
|
128
|
+
PyMODINIT_FUNC PyInit_signum(void) {
|
|
129
|
+
return PyModule_Create(&signummodule);
|
|
130
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from math import nan, isnan, inf
|
|
3
|
+
from signum import sign
|
|
4
|
+
import unittest
|
|
5
|
+
except ImportError as e:
|
|
6
|
+
print(e)
|
|
7
|
+
print("To pass these signum tests, you should have 'math', 'signum', and 'unittest' modules installed")
|
|
8
|
+
print("Terminated, no tests passed")
|
|
9
|
+
exit(1)
|
|
10
|
+
|
|
11
|
+
class TestSignum(unittest.TestCase):
|
|
12
|
+
|
|
13
|
+
def trace(self, pcnt, cnt, what):
|
|
14
|
+
delta = cnt - pcnt
|
|
15
|
+
pl = 's' if delta > 1 else ''
|
|
16
|
+
print(f"{delta} test{pl} for {what} passed, total {cnt} tests passed")
|
|
17
|
+
|
|
18
|
+
def test_sign(self):
|
|
19
|
+
counter = 0
|
|
20
|
+
# --- int
|
|
21
|
+
prev_counter = counter
|
|
22
|
+
self.assertEqual(sign(-5), -1); counter += 1
|
|
23
|
+
self.assertEqual(sign(-1), -1); counter += 1
|
|
24
|
+
self.assertEqual(sign(0), 0); counter += 1
|
|
25
|
+
self.assertEqual(sign(1), 1); counter += 1
|
|
26
|
+
self.assertEqual(sign(5), 1); counter += 1
|
|
27
|
+
self.trace(prev_counter, counter, "'int'")
|
|
28
|
+
# ------ bool
|
|
29
|
+
prev_counter = counter
|
|
30
|
+
self.assertEqual(sign(True), 1); counter += 1
|
|
31
|
+
self.assertEqual(sign(False), 0); counter += 1
|
|
32
|
+
self.trace(prev_counter, counter, "'bool'")
|
|
33
|
+
# ------ big numbers
|
|
34
|
+
prev_counter = counter
|
|
35
|
+
self.assertEqual(sign(10**1000), 1); counter += 1
|
|
36
|
+
self.assertEqual(sign(-10**1000), -1); counter += 1
|
|
37
|
+
self.assertEqual(sign(10**1000-10**1000), 0); counter += 1
|
|
38
|
+
self.trace(prev_counter, counter, "big 'int'")
|
|
39
|
+
|
|
40
|
+
# --- float
|
|
41
|
+
prev_counter = counter
|
|
42
|
+
self.assertEqual(sign(-5.0), -1); counter += 1
|
|
43
|
+
self.assertEqual(sign(-1.0), -1); counter += 1
|
|
44
|
+
self.assertEqual(sign(0.0), 0); counter += 1
|
|
45
|
+
self.assertEqual(sign(1.0), 1); counter += 1
|
|
46
|
+
self.assertEqual(sign(5.0), 1); counter += 1
|
|
47
|
+
self.trace(prev_counter, counter, "'float'")
|
|
48
|
+
# ------ -0.0 and +0.0
|
|
49
|
+
prev_counter = counter
|
|
50
|
+
self.assertEqual(sign(float('-0.0')), 0); counter += 1
|
|
51
|
+
self.assertEqual(sign(float('+0.0')), 0); counter += 1
|
|
52
|
+
self.trace(prev_counter, counter, "±0.0")
|
|
53
|
+
# ------ -inf and inf
|
|
54
|
+
prev_counter = counter
|
|
55
|
+
self.assertEqual(sign(-inf), -1); counter += 1
|
|
56
|
+
self.assertEqual(sign(inf), 1); counter += 1
|
|
57
|
+
self.trace(prev_counter, counter, "infinity")
|
|
58
|
+
# ------ -nan (the same as nan), nan
|
|
59
|
+
prev_counter = counter
|
|
60
|
+
self.assertTrue(isnan(sign(float('-nan')))); counter += 1
|
|
61
|
+
self.assertTrue(isnan(sign(nan))); counter += 1
|
|
62
|
+
self.assertTrue(isnan(sign(0.0*nan))); counter += 1
|
|
63
|
+
self.trace(prev_counter, counter, "NaN")
|
|
64
|
+
|
|
65
|
+
# --- Fraction
|
|
66
|
+
try:
|
|
67
|
+
from fractions import Fraction
|
|
68
|
+
have_fractions = True
|
|
69
|
+
except ImportError as e:
|
|
70
|
+
have_fractions = False
|
|
71
|
+
print(e)
|
|
72
|
+
print("No 'fractions' module found in your installation. Tests for 'Fraction' are skipped")
|
|
73
|
+
if have_fractions:
|
|
74
|
+
prev_counter = counter
|
|
75
|
+
self.assertEqual(sign(Fraction(-5, 2)), -1); counter += 1
|
|
76
|
+
self.assertEqual(sign(Fraction(-1, 2)), -1); counter += 1
|
|
77
|
+
self.assertEqual(sign(Fraction(0, 2)), 0); counter += 1
|
|
78
|
+
self.assertEqual(sign(Fraction(1, 2)), 1); counter += 1
|
|
79
|
+
self.assertEqual(sign(Fraction(5, 2)), 1); counter += 1
|
|
80
|
+
self.trace(prev_counter, counter, "'Fraction'")
|
|
81
|
+
|
|
82
|
+
# --- Decimal
|
|
83
|
+
try:
|
|
84
|
+
from decimal import Decimal
|
|
85
|
+
have_decimal = True
|
|
86
|
+
except ImportError as e:
|
|
87
|
+
have_decimal = False
|
|
88
|
+
print(e)
|
|
89
|
+
print("No 'decimal' module found in your installation. Tests for 'Decimal' are skipped")
|
|
90
|
+
if have_decimal:
|
|
91
|
+
prev_counter = counter
|
|
92
|
+
self.assertEqual(sign(Decimal(-5.5)), -1); counter += 1
|
|
93
|
+
self.assertEqual(sign(Decimal(-1.5)), -1); counter += 1
|
|
94
|
+
self.assertEqual(sign(Decimal(0.0)), 0); counter += 1
|
|
95
|
+
self.assertEqual(sign(Decimal(1.5)), 1); counter += 1
|
|
96
|
+
self.assertEqual(sign(Decimal(5.5)), 1); counter += 1
|
|
97
|
+
self.trace(prev_counter, counter, "'Decimal'")
|
|
98
|
+
# ------ Decimal NaN
|
|
99
|
+
prev_counter = counter
|
|
100
|
+
self.assertTrue(isnan(sign(Decimal('NaN')))); counter += 1
|
|
101
|
+
self.trace(prev_counter, counter, "Decimal NaN")
|
|
102
|
+
|
|
103
|
+
# --- sympy
|
|
104
|
+
try:
|
|
105
|
+
import sympy
|
|
106
|
+
have_sympy = True
|
|
107
|
+
except ImportError as e:
|
|
108
|
+
have_sympy = False
|
|
109
|
+
print(e)
|
|
110
|
+
print("No 'sympy' module found in your installation. Tests for 'sympy' are skipped")
|
|
111
|
+
if have_sympy:
|
|
112
|
+
x_sym = sympy.Symbol('x')
|
|
113
|
+
expr = x_sym
|
|
114
|
+
val = expr.subs(x_sym, -3.14)
|
|
115
|
+
prev_counter = counter
|
|
116
|
+
self.assertEqual(sign(val), -1); counter += 1
|
|
117
|
+
self.assertEqual(sign(sympy.Rational(3, 4)), 1); counter += 1
|
|
118
|
+
self.trace(prev_counter, counter, "sympy")
|
|
119
|
+
# ------ sympy.nan
|
|
120
|
+
prev_counter = counter
|
|
121
|
+
self.assertTrue(isnan(sign(sympy.nan))); counter += 1
|
|
122
|
+
self.trace(prev_counter, counter, "sympy.nan")
|
|
123
|
+
|
|
124
|
+
# --- New custom class (testing possible future extentions)
|
|
125
|
+
# This class has no __float__ that tests one subtle branch in the C++ code
|
|
126
|
+
class MyNumber:
|
|
127
|
+
def __init__(self, value):
|
|
128
|
+
self.value = value
|
|
129
|
+
def __gt__(self, other):
|
|
130
|
+
return self.value > other
|
|
131
|
+
def __lt__(self, other):
|
|
132
|
+
return self.value < other
|
|
133
|
+
def __eq__(self, other):
|
|
134
|
+
return self.value == other
|
|
135
|
+
def __repr__(self):
|
|
136
|
+
return f'MyNumber({self.value})'
|
|
137
|
+
|
|
138
|
+
prev_counter = counter
|
|
139
|
+
self.assertEqual(sign(MyNumber(-5)), -1); counter += 1
|
|
140
|
+
self.assertEqual(sign(MyNumber(-1)), -1); counter += 1
|
|
141
|
+
self.assertEqual(sign(MyNumber(0)), 0); counter += 1
|
|
142
|
+
self.assertEqual(sign(MyNumber(1)), 1); counter += 1
|
|
143
|
+
self.assertEqual(sign(MyNumber(5)), 1); counter += 1
|
|
144
|
+
with self.assertRaisesRegex(TypeError, r'signum\.sign: invalid argument `MyNumber\(nan\)`'):
|
|
145
|
+
sign(MyNumber(nan))
|
|
146
|
+
counter += 1
|
|
147
|
+
self.trace(prev_counter, counter, "new custom class")
|
|
148
|
+
|
|
149
|
+
# Testing inappropriate arguments and types (non-scalar, non-comparable, etc.)
|
|
150
|
+
# --- No arguments and three arguments
|
|
151
|
+
prev_counter = counter
|
|
152
|
+
with self.assertRaisesRegex(TypeError, r'signum\.sign\(\) takes exactly one argument \(0 given\)'):
|
|
153
|
+
sign()
|
|
154
|
+
counter += 1
|
|
155
|
+
with self.assertRaisesRegex(TypeError, r'signum\.sign\(\) takes exactly one argument \(3 given\)'):
|
|
156
|
+
sign(-1, 0, 1)
|
|
157
|
+
counter += 1
|
|
158
|
+
self.trace(prev_counter, counter, "invalid number of arguments")
|
|
159
|
+
|
|
160
|
+
# --- None, str, list, complex, set
|
|
161
|
+
tests = [(r"`None`", None),
|
|
162
|
+
(r"`'5\.0'`", '5.0'),
|
|
163
|
+
(r"`'nan'`", 'nan'),
|
|
164
|
+
(r"`'number 5'`", 'number 5'),
|
|
165
|
+
(r"`\[-8\.75\]`", [-8.75]),
|
|
166
|
+
(r"`\(-1\+1j\)`", -1+1j),
|
|
167
|
+
(r"`\{-3\.14\}`", {-3.14}),
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
prev_counter = counter
|
|
171
|
+
for msg, obj in tests:
|
|
172
|
+
with self.subTest(obj=obj):
|
|
173
|
+
with self.assertRaisesRegex(TypeError,
|
|
174
|
+
r'signum\.sign: invalid argument ' + msg):
|
|
175
|
+
sign(obj)
|
|
176
|
+
counter += 1
|
|
177
|
+
self.trace(prev_counter, counter, "inappropriate types")
|
|
178
|
+
|
|
179
|
+
print(f'Success, {counter} tests passed.')
|
|
180
|
+
|
|
181
|
+
# print('--- int')
|
|
182
|
+
# print("sign(-5):", sign(-5))
|
|
183
|
+
# print("sign(-1):", sign(-1))
|
|
184
|
+
# print("sign(0):", sign(0))
|
|
185
|
+
# print("sign(1):", sign(1))
|
|
186
|
+
# print("sign(5):", sign(5))
|
|
187
|
+
#
|
|
188
|
+
# print('------ bool')
|
|
189
|
+
# print("sign(True):", sign(True))
|
|
190
|
+
# print("sign(False):", sign(False))
|
|
191
|
+
#
|
|
192
|
+
# print('------ big numbers')
|
|
193
|
+
# print('sign(10**1000):', sign(10**1000))
|
|
194
|
+
# print('sign(-10**1000):', sign(-10**1000))
|
|
195
|
+
# print('sign(10**1000-10**1000):', sign(10**1000-10**1000))
|
|
196
|
+
#
|
|
197
|
+
# print('\n--- float')
|
|
198
|
+
# print("sign(-5.0):", sign(-5.0))
|
|
199
|
+
# print("sign(-1.0):", sign(-1.0))
|
|
200
|
+
# print("sign(0.0):", sign(0.0))
|
|
201
|
+
# print("sign(1.0):", sign(1.0))
|
|
202
|
+
# print("sign(5.0):", sign(5.0))
|
|
203
|
+
# print('------ -0.0 and +0.0')
|
|
204
|
+
# print("sign(float('-0.0')):", sign(float('-0.0')))
|
|
205
|
+
# print("sign(float('+0.0')):", sign(float('+0.0')))
|
|
206
|
+
# print('------ -inf and inf')
|
|
207
|
+
# print("sign(-inf):", sign(-inf))
|
|
208
|
+
# print("sign(inf):", sign(inf))
|
|
209
|
+
# print('------ -nan and nan')
|
|
210
|
+
# print("sign(float('-nan')):", sign(float('-nan')))
|
|
211
|
+
# print("sign(nan):", sign(nan))
|
|
212
|
+
# print("sign(0.0*nan):", sign(0.0*nan))
|
|
213
|
+
#
|
|
214
|
+
# print('\n--- Fraction')
|
|
215
|
+
# print("sign(Fraction(-5, 2)):", sign(Fraction(-5, 2)))
|
|
216
|
+
# print("sign(Fraction(-1, 2)):", sign(Fraction(-1, 2)))
|
|
217
|
+
# print("sign(Fraction(0, 2)):", sign(Fraction(0, 2)))
|
|
218
|
+
# print("sign(Fraction(1, 2)):", sign(Fraction(1, 2)))
|
|
219
|
+
# print("sign(Fraction(5, 2)):", sign(Fraction(5, 2)))
|
|
220
|
+
#
|
|
221
|
+
# print('\n--- Decimal')
|
|
222
|
+
# print("sign(Decimal(-5.5)):", sign(Decimal(-5.5)))
|
|
223
|
+
# print("sign(Decimal(-1.5)):", sign(Decimal(-1.5)))
|
|
224
|
+
# print("sign(Decimal(0.0)):", sign(Decimal(0.0)))
|
|
225
|
+
# print("sign(Decimal(1.5)):", sign(Decimal(1.5)))
|
|
226
|
+
# print("sign(Decimal(5.5)):", sign(Decimal(5.5)))
|
|
227
|
+
#
|
|
228
|
+
# print("------ Decimal('NaN')")
|
|
229
|
+
# print("sign(Decimal('NaN')):", sign(Decimal('NaN')))
|
|
230
|
+
#
|
|
231
|
+
# print('\n--- sympy (substitution and Rational)')
|
|
232
|
+
# x_sym = sympy.Symbol('x')
|
|
233
|
+
# expr = x_sym
|
|
234
|
+
# val = expr.subs(x_sym, -3.14)
|
|
235
|
+
# print(f"Type of val is {type(val)}")
|
|
236
|
+
# print(f"Type of (val > 0) is {type(val > 0)}")
|
|
237
|
+
# print("sign(val):", sign(val))
|
|
238
|
+
# print("sign(sympy.Rational(3, 4)):", sign(sympy.Rational(3, 4)))
|
|
239
|
+
#
|
|
240
|
+
# print('------ sympy.nan')
|
|
241
|
+
# print("sign(sympy.nan):", sign(sympy.nan))
|
|
242
|
+
#
|
|
243
|
+
# print('\n--- My Custom Class That Have >, <, == With Numbers But Nothing Else')
|
|
244
|
+
# print("sign(MyNumber(-5)):", sign(MyNumber(-5)))
|
|
245
|
+
# print("sign(MyNumber(-1)):", sign(MyNumber(-1)))
|
|
246
|
+
# print("sign(MyNumber(0)):", sign(MyNumber(0)))
|
|
247
|
+
# print("sign(MyNumber(1)):", sign(MyNumber(1)))
|
|
248
|
+
# print("sign(MyNumber(5.1)):", sign(MyNumber(5.1)))
|
|
249
|
+
# try:
|
|
250
|
+
# print("sign(MyNumber(nan)):", sign(MyNumber(nan)))
|
|
251
|
+
# except TypeError as e:
|
|
252
|
+
# print(e)
|
|
253
|
+
#
|
|
254
|
+
# print('\n--- No arguments')
|
|
255
|
+
# try:
|
|
256
|
+
# print("sign():", sign())
|
|
257
|
+
# except TypeError as e:
|
|
258
|
+
# print(e)
|
|
259
|
+
#
|
|
260
|
+
# print('\n--- Three arguments')
|
|
261
|
+
# try:
|
|
262
|
+
# print("sign(-1, 0, 1):", sign(-1, 0, 1))
|
|
263
|
+
# except TypeError as e:
|
|
264
|
+
# print(e)
|
|
265
|
+
#
|
|
266
|
+
# print('\n--- None')
|
|
267
|
+
# try:
|
|
268
|
+
# print("sign(None):", sign(None))
|
|
269
|
+
# except TypeError as e:
|
|
270
|
+
# print(e)
|
|
271
|
+
#
|
|
272
|
+
# print('\n--- str')
|
|
273
|
+
# try:
|
|
274
|
+
# print("sign('5.0'):", sign('5.0'))
|
|
275
|
+
# except TypeError as e:
|
|
276
|
+
# print(e)
|
|
277
|
+
#
|
|
278
|
+
# try:
|
|
279
|
+
# print("sign('nan'):", sign('nan'))
|
|
280
|
+
# except TypeError as e:
|
|
281
|
+
# print(e)
|
|
282
|
+
#
|
|
283
|
+
# try:
|
|
284
|
+
# print("sign('number 5'):", sign('number 5'))
|
|
285
|
+
# except TypeError as e:
|
|
286
|
+
# print(e)
|
|
287
|
+
#
|
|
288
|
+
# print('\n--- list')
|
|
289
|
+
# try:
|
|
290
|
+
# print("sign([-8.75]):", sign([-8.75]))
|
|
291
|
+
# except TypeError as e:
|
|
292
|
+
# print(e)
|
|
293
|
+
#
|
|
294
|
+
# print('\n--- complex')
|
|
295
|
+
# try:
|
|
296
|
+
# print("sign(-1+1j):", sign(-1+1j))
|
|
297
|
+
# except TypeError as e:
|
|
298
|
+
# print(e)
|
|
299
|
+
#
|
|
300
|
+
# print('\n--- set')
|
|
301
|
+
# try:
|
|
302
|
+
# print("sign({-3.14}):", sign({-3.14}))
|
|
303
|
+
# except TypeError as e:
|
|
304
|
+
# print(e)
|
|
305
|
+
|
|
306
|
+
if __name__ == '__main__':
|
|
307
|
+
unittest.main()
|