├── LICENSE ├── README.md ├── mpy_decimal ├── __init__.py └── mpy_decimal.py ├── setup.py └── tests ├── example1.py ├── perf_decimal_number.py ├── perf_pi_pico.txt └── test_decimal_number.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 mpy-dev 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decimal floating point arithmetic for micropython 2 | This Python module for [*micropython*](https://micropython.org/) provides support for decimal floating point arithmetic. It tries to overcome the limitations of single precision float numbers (32-bit) and provides a solution when double precision float numbers (64 bit) are not enough. 3 | 4 | The Python Standard Library contains the wonderful module [*decimal*](https://docs.python.org/3/library/decimal.html), but it has not been ported to *micropython*. This module provides a small, but valuable, part of the functionality of *decimal*. 5 | 6 | ## Introduction 7 | 8 | The module **mpy_decimal** defines the class **DecimalNumber** that contains all the functionality for decimal floating point arithmetic. A **DecimalNumber** can be of arbitrary precision. Internally, it is composed of an *int* that contains all the digits of the **DecimalNumber**, an *int* equal to the number of decimal places and a *bool* that determines whether the **DecimalNumber** is positive or negative. Example: 9 | 10 | DecimalNumber: -12345678901.23456789 11 | number = 1234567890123456789 12 | decimals = 8 13 | positive = False 14 | 15 | The precision of **DecimalNumber** is mainly limited by available memory and procesing power. **DecimalNumber** uses the concept **scale**, which is the number of decimal places that the class uses for its numbers and operations. The concept is similar to the use of 'scale' in the calculator and language [*bc*](https://www.gnu.org/software/bc/manual/html_mono/bc.html). The default value for **scale** is 16. It is a global value of the class that can be changed at any time. For rounding, **DecimalNumber** uses [*round half to even*](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even). 16 | 17 | ## Performance ## 18 | 19 | All the internal operations of **DecimalNumber** are done with integers (*int* built-in type of Python) and the number of decimals are adjusted according to the operation. It is fast, but not as fast as Python's *decimal* class because **DecimalNumber** is pure Python and *decimal* is written in C. The *test* folder contains the file "*perf_decimal_number.py*" that calculates the performance of **DecimalNumber** on the device where it runs. This is the output of that program executed on a [*Raspberry Pi Pico*](https://www.raspberrypi.org/products/raspberry-pi-pico/). Basic operations take about one millisecond with scale = 16: 20 | 21 | +---------------------------------------------------------------+ 22 | | SYSTEM INFORMATION | 23 | +---------------------------------------------------------------+ 24 | Implementation name: micropython 25 | Implementation version: 1.17.0 26 | Implementation platform: rp2 27 | CPU frequency: 125 Mhz 28 | 29 | +---------------------------------------------------------------+ 30 | | PERFORMANCE WITH SCALE = 16 | 31 | +---------------------------------------------------------------+ 32 | Scale (max. decimals): 16 33 | Iterations per test: 1000 34 | Number 1: 63107666423864.1503618336011148 35 | Number 2: 21513455188640.8462728528253754 36 | Addition (n1 + n2): 1.564 ms 37 | Subtraction (n1 - n2): 1.695 ms 38 | Multiplication (n1 * n2): 0.988 ms 39 | Division (n1 / n2): 1.184 ms 40 | Square root abs(n1): 3.984 ms 41 | Power: (pi/2) ** 15 9.799 ms 42 | DecimalNumber from int: 0.378 ms 43 | DecimalNumber from string: 3.685 ms 44 | Iterations per test: 10 45 | Sine: sin(0.54321) 85.80001 ms 46 | Cosine: cos(0.54321) 86.7 ms 47 | Tangent: tan(0.54321) 206.7 ms 48 | Arcsine: asin(0.54321) 270.2 ms 49 | Arccosine: acos(0.65432) 468.8 ms 50 | Arctangent: atan(1.2345) 485.5 ms 51 | Arctangent2: atan2(1.23, 2.34) 349.2 ms 52 | Exponential: exp(12.345) 151.0 ms 53 | Natural logarithm: ln(12.345) 152.0 ms 54 | 55 | +---------------------------------------------------------------+ 56 | | PERFORMANCE WITH SCALE = 50 | 57 | +---------------------------------------------------------------+ 58 | Scale (max. decimals): 50 59 | Iterations per test: 400 60 | Number 1: 1008440840554324243744283306032711702.13787157234850455642441824006520576852374144010113 61 | Number 2: -603053031456028063831871487068.81514266625502376325215658564112814751837118860547 62 | Addition (n1 + n2): 1.9575 ms 63 | Subtraction (n1 - n2): 2.25 ms 64 | Multiplication (n1 * n2): 1.4625 ms 65 | Division (n1 / n2): 1.52 ms 66 | Square root abs(n1): 12.455 ms 67 | Power: (pi/2) ** 15 11.7425 ms 68 | DecimalNumber from int: 0.385 ms 69 | DecimalNumber from string: 10.055 ms 70 | Iterations per test: 4 71 | Sine: sin(0.54321) 186.25 ms 72 | Cosine: cos(0.54321) 184.5 ms 73 | Tangent: tan(0.54321) 398.75 ms 74 | Arcsine: asin(0.54321) 831.25 ms 75 | Arccosine: acos(0.65432) 1322.25 ms 76 | Arctangent: atan(1.2345) 1377.75 ms 77 | Arctangent2: atan2(1.23, 2.34) 893.4999 ms 78 | Exponential: exp(12.345) 281.75 ms 79 | Natural logarithm: ln(12.345) 281.75 ms 80 | 81 | +---------------------------------------------------------------+ 82 | | CALCULATING PI | 83 | +---------------------------------------------------------------+ 84 | Pi with 300 decimals: 5.862 s 85 | 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141274 86 | 87 | ## How to use 88 | 89 | To test this module on a PC, you can start by importing the module: 90 | 91 | ```python 92 | from mpy_decimal.mpy_decimal import * 93 | ``` 94 | 95 | For testing on a Micropython board, you have probably copied the file 'mpy_decimal.py' to the board or a compiled version of it: 'mpy_decimal.mpy'. There is a guide that explains how to compile this module and how to copy it to a device at the end of this document. You can import the module with: 96 | 97 | ```python 98 | from mpy_decimal import * 99 | ``` 100 | 101 | If you need your code to run on both, a computer and a micropython board, you will probably need to run different code depending on the device. You can do it this way: 102 | 103 | ```python 104 | import sys 105 | 106 | if sys.implementation.name == "cpython": 107 | # ... your imports or other code for CPython here ... 108 | 109 | if sys.implementation.name == "micropython": 110 | # ... your imports or other code for Micropython here ... 111 | ``` 112 | 113 | (Note: 'sys' is standard Python and it has nothing to do with **DecimalNumber**) 114 | 115 | ### Initialization ### 116 | A **DecimalNumber** with default value, equal to zero: 117 | 118 | 119 | ```python 120 | n = DecimalNumber() 121 | ``` 122 | 123 | An integer, for example, 748: 124 | 125 | ```python 126 | n = DecimalNumber(748) 127 | ``` 128 | 129 | A decimal number, for example, 93402.5184: 130 | 131 | ```python 132 | n = DecimalNumber(934025184, 4) 133 | ``` 134 | 135 | Notice that the first parameter is an integer with all the digits and the second one the number of decimals. 136 | 137 | The same number can be created providing a string with the number: 138 | 139 | ```python 140 | n = DecimalNumber("93402.5184") 141 | ``` 142 | 143 | ### Printing and formating ### 144 | Numbers can be printed using 'print()': 145 | 146 | ```python 147 | print(n) 148 | # Result: 93402.5184 149 | ``` 150 | 151 | They can be converted to a string using 'str()': 152 | 153 | ```python 154 | str(n) 155 | ``` 156 | 157 | 158 | The method **to_string_thousands()** of **DecimalNumber** returns a string with the number formatted with ',' as thousands separator. Decimals figures are not modified: 159 | 160 | ```python 161 | print(n.to_string_thousands()) 162 | # Result: 93,402.5184 163 | ``` 164 | 165 | Micropython can be used to print information on displays with a limited number of characters. For example, on a 16x2 LCD (two lines of 16 characters). For these kind of cases exists the method **to_string_max_length()**. It limits the representation of the number to a maximum length of characters, including '.' and '-'. The minimum value is 8. If decimals cannot fit in, they are discarded. If the integer part of the number is bigger than the maximum length, the result is the string "Overflow". Some examples: 166 | 167 | ```python 168 | n = DecimalNumber("123456789.012") 169 | # ¹¹¹¹ 170 | # ¹²³⁴⁵⁶⁷⁸⁹⁰¹²³ 171 | 172 | print(n.to_string_max_length(12)) 173 | # Result: 123456789.01 174 | 175 | print(n.to_string_max_length(11)) 176 | # Result: 123456789 177 | 178 | print(n.to_string_max_length(8)) 179 | # Result: Overflow 180 | ``` 181 | 182 | ### Modifying the **scale** of **DecimalNumber** ### 183 | 184 | **scale** is a global value of the class **DecimalNumber** that stores the number of decimals that the class uses for its numbers an operations. The default value is 16. **DecimalNumber.get_scale()** returns the current **scale** and the method **DecimalNumber.set_scale()** sets **scale**: 185 | 186 | ```python 187 | current_scale = DecimalNumber.get_scale() # Gets the scale 188 | DecimalNumber.set_scale(100) # Sets the scale to 100 189 | 190 | # ... Code to calculate something using the new scale 191 | 192 | DecimalNumber.set_scale(current_scale) # Back to the previous scale 193 | ``` 194 | 195 | ### Operations ### 196 | 197 | **Basic operations** 198 | 199 | The basic operations (addition, subtraction, multiplication and division) allow to mix **DecimalNumber** objects and *int* objects. The result is always a **DecimalNumber**. Examples: 200 | 201 | ```python 202 | a = DecimalNumber("7.3329") 203 | b = DecimalNumber("157.82") 204 | 205 | # Addition 206 | c = a + b # DecimalNumber + DecimalNumber 207 | c = a + 5 # DecimalNumber + int 208 | c = 5 + a # int + DecimalNumber 209 | a += b # DecimalNumber + DecimalNumber 210 | a += 3 # DecimalNumber + int 211 | 212 | # Subtraction 213 | c = a - b # DecimalNumber - DecimalNumber 214 | c = a - 5 # DecimalNumber - int 215 | c = 5 - a # int - DecimalNumber 216 | a -= b # DecimalNumber - DecimalNumber 217 | a -= 3 # DecimalNumber - int 218 | 219 | # Multiplication 220 | c = a * b # DecimalNumber * DecimalNumber 221 | c = a * 5 # DecimalNumber * int 222 | c = 5 * a # int * DecimalNumber 223 | a *= b # DecimalNumber * DecimalNumber 224 | a *= 3 # DecimalNumber * int 225 | 226 | # Division 227 | c = a / b # DecimalNumber / DecimalNumber 228 | c = a / 5 # DecimalNumber / int 229 | c = 5 / a # int / DecimalNumber 230 | a /= b # DecimalNumber / DecimalNumber 231 | a /= 3 # DecimalNumber / int 232 | ``` 233 | **Exponentiation** 234 | 235 | The operands for exponentition are a **DecimalNumber**, the base, and an *int*, the exponent. It calculates the base raised to the exponent. Examples: 236 | 237 | ```python 238 | a = DecimalNumber("1.01234567") 239 | b = a ** 15 # a¹⁵ = 1.2020774344056969 240 | 241 | # 11th Mersenne prime = 2¹⁰⁷ - 1 = 162259276829213363391578010288127 242 | m11 = DecimalNumber(2) ** 107 - 1 243 | ``` 244 | 245 | **Square root** 246 | 247 | It calculates the square root of a positive **DecimalNumber**. For negative numbers, a **DecimalNumberExceptionMathDomainError** exception is raised. Example: 248 | 249 | ```python 250 | a = DecimalNumber("620433.785") 251 | b = a.square_root() 252 | print(b) # Result: 787.6761929879561873 253 | ``` 254 | 255 | **Absolute** 256 | 257 | It returns the absolute value of a **DecimalNumber**. Examples: 258 | 259 | ```python 260 | a = DecimalNumber("12.762") 261 | b = DecimalNumber("-12.762") 262 | print(abs(a)) # Result: 12.762 263 | print(abs(b)) # Result: 12.762 264 | ``` 265 | 266 | **Unary - operator** 267 | 268 | Given a **DecimalNumber** n, it returns -n. Example: 269 | 270 | ```python 271 | a = DecimalNumber("12.762") 272 | b = DecimalNumber("-12.762") 273 | print(-a) # Result: -12.762 274 | print(-b) # Result: 12.762 275 | ``` 276 | 277 | **Unary + operator** 278 | 279 | Given a **DecimalNumber** n, it returns +n. This can give the idea that it does nothing to the number n. That is true if the number of decimals of n is less or equal than the **scale** of **DecimalNumber**. If it is not, if the number of decimals of n is greater than **scale**, the decimals of n are reduced and rounded to match **scale**. 280 | 281 | This operator can be useful to adjust the **scale** of a number to the **scale** of **DecimalNumber** after changing the **scale** with the method **DecimalNumber.set_scale()**. An example of this is a method that increases **scale** to obtain better accuracy in the calculation of a number x. Before returning from the method, it sets **scale** back to the value it had before entering the method and it returns +x instead of just x, adjusting the decimals of x to **scale**. Example: 282 | 283 | ```python 284 | a = DecimalNumber(2) 285 | DecimalNumber.set_scale(30) # Sets the scale = 30 286 | r2 = a.square_root() # Calculates the square root of 2 with 30 decimals 287 | print(r2) # 1.414213562373095048801688724209 288 | DecimalNumber.set_scale(10) # Sets the scale = 10 289 | print(r2) # 1.414213562373095048801688724209 290 | # Changing the scale does not modify a number 291 | # unless an operation is made with it, like, 292 | # for example, the unary + operator: 293 | print(+r2) # 1.4142135624 294 | ``` 295 | 296 | ### Comparisons operators ### 297 | 298 | These are the common comparison operators we all know: <, <=, ==, !=, >=, >. 299 | 300 | A **DecimalNumber** can be compared to other **DecimalNumber** or to an *int* number. Examples: 301 | 302 | ```python 303 | a = DecimalNumber("1.2") 304 | b = DecimalNumber("8.3") 305 | print(a > b) # False 306 | print(b > a) # True 307 | print(a == b) # False 308 | print(a != b) # True 309 | print(a > 0) # True 310 | print(1 > b) # False 311 | ``` 312 | 313 | ### Other mathematical functions ### 314 | 315 | **exp()**: exponential function ex. 316 | 317 | **ln()**: natural logarithm (base e). 318 | 319 | **Trigonometric functions**: 320 | 321 | **sin()**: sine. 322 | 323 | **cos()**: cosine. 324 | 325 | **tan()**: tangent. 326 | 327 | The argument of trigonometric functions sin(), cos() and tan() is an angle expressed in radians. 328 | 329 | **asin()**: arcsine. 330 | 331 | **acos()**: arccosine. 332 | 333 | **atan()**: arctangent. 334 | 335 | **atan2()**: 2-argument arctangent. 336 | 337 | The functions asin(), acos(), atan() and atan2() return an angle in radians. 338 | 339 | Example: 340 | 341 | ```python 342 | n = DecimalNumber("0.732") 343 | n.exp() # 2.0792349218188443 344 | n.ln() # -0.3119747650208255 345 | n.sin() # 0.6683586490759965 346 | n.cos() # 0.7438391736157144 347 | n.tan() # 0.8985257469396026 348 | n.asin() # 0.821252884452186 349 | n.acos() # 0.7495434423427106 350 | n.atan() # 0.6318812315412357 351 | n2 = DecimalNumber("1.732") 352 | DecimalNumber.atan(n, n2) # 0.3998638956924461 353 | ``` 354 | 355 | ### Other methods ### 356 | 357 | **to_int_truncate()** returns and *int* that contains the integer part of a **DecimalNumber** after truncating the decimals. 358 | 359 | **to_int_round()** returns and *int* that contains the integer part of a **DecimalNumber** after rounding it to zero decimals. 360 | 361 | Example: 362 | 363 | ```python 364 | a = DecimalNumber("623897401.877314") 365 | print(a.to_int_truncate()) # 623897401 366 | print(a.to_int_round()) # 623897402 367 | ``` 368 | 369 | **clone()** returns a new **DecimalNumber** object as a clone of a **DecimalNumber**. Example: 370 | 371 | ```python 372 | a = DecimalNumber("657.31") 373 | b = a.clone() 374 | print(a == b) # True: they are equal numbers. 375 | print(a is b) # False: they are different objects. 376 | ``` 377 | 378 | **copy_from()** copies into a **DecimalNumber** other **DecimalNumber** passed as a parameter. The reason for this method is that Python does not allow to overload the *assign* operator: '='. "b = a" would make b to point to object a, they would be the same object. We need a way to copy one into the other while remaining independent objects. Example: 379 | 380 | ```python 381 | a = DecimalNumber("657.31") 382 | b = DecimalNumber("-1.9") 383 | b = a 384 | print(a, b) # 657.31 657.31 385 | print(a is b) # True: they are the same object 386 | 387 | # Let's try again with 'copy_from()' 388 | 389 | a = DecimalNumber("657.31") 390 | b = DecimalNumber("-1.9") 391 | b.copy_from(a) 392 | print(a, b) # 657.31 657.31 393 | print(a is b) # False: they are different objects. 394 | ``` 395 | 396 | **pi()** is a class method that returns the number PI with as many decimals as the **scale** of **DecimalNumber**. Example: 397 | 398 | ```python 399 | pi = DecimalNumber.pi() # Default scale, equal to 16 400 | print(DecimalNumber.pi()) # 3.1415926535897932 401 | DecimalNumber.set_scale(36) # Set scale = 36 402 | print(DecimalNumber.pi()) # 3.141592653589793238462643383279502884 403 | ``` 404 | 405 | PI is precalculated with 100 decimals and stored in the class. If **pi()** method is used with **scale** <= 100, PI is not calculated, but returned using the precalculated value. If **scale** is set to a value greater than 100, for example, 300, PI is calculated, stored in the class and returned. After that, the precalculated limit is 300 instead of 100, and any call to **pi()** with a **scale** <= 300 returns the value of PI from the precalculated value. 406 | 407 | To calculate PI, this method uses the very fast algorithm present on the section [Recipes](https://docs.python.org/3/library/decimal.html#recipes) of the documentation for the module **decimal**, part of Python Standard Library. 408 | 409 | **e()** is a class method that returns the number e number, the base of natural logarithms, with as many decimals as the **scale** of **DecimalNumber**. Its value is precalculated and it functions in a similar way as pi(). Example: 410 | 411 | ```python 412 | e = DecimalNumber.e() # Default scale, equal to 16 413 | print(DecimalNumber.pi()) # 2.7182818284590452 414 | DecimalNumber.set_scale(36) # Set scale = 36 415 | print(DecimalNumber.e()) # 2.718281828459045235360287471352662498 416 | ``` 417 | 418 | ### Other considerations ### 419 | 420 | **DecimalNumber** class can operate mixing *int* numbers and **DecimalNumber** objects. *float* numbers were not considered because of their imprecision. 421 | 422 | Try this using CPython (on a PC): 423 | ```python 424 | print( (0.1 + 0.1 + 0.1) == 0.3 ) # False!!! 425 | ``` 426 | 427 | There is nothing wrong with Python, it is the way *float* numbers work. 428 | 429 | Try this using Micropython (I have used a Raspberry Pi Pico): 430 | 431 | ```python 432 | # Compares 10¹¹ * 10⁻² to 10⁹, which should be True 433 | print( (1e11 * 1e-2) == 1e9 ) # False!!! 434 | ``` 435 | 436 | 437 | ## Exceptions ## 438 | 439 | This module defines four exceptions: 440 | 441 | * **DecimalNumberExceptionParseError**: a 442 | **DecimalNumber** can be initialize providing a string that contains a number. If the content of the string cannot be parsed as a correct number, this exception is raised. 443 | 444 | * **DecimalNumberExceptionBadInit**: this exception is raised when a negative number of decimals is provided when initializing a **DecimalNumber**. 445 | 446 | * **DecimalNumberExceptionMathDomainError**: this exception occurs when trying to calculate the square root of a negative number or atan2(0, 0). 447 | 448 | * **DecimalNumberExceptionDivisionByZeroError**: this is the division by zero exception. 449 | 450 | 451 | ## Example ## 452 | 453 | This example shows how to use **DecimalNumber** to solve quadratic equations. 454 | 455 | ```python 456 | from mpy_decimal.mpy_decimal import * 457 | 458 | 459 | def solve_quadratic_equation(a: DecimalNumber, b: DecimalNumber, c: DecimalNumber) -> Tuple[bool, DecimalNumber, DecimalNumber]: 460 | """It solves quadratic equations: 461 | a * x² + b * x + c = 0 462 | x₁ = (-b + sqrt(b*b - 4*a*c)) / (2*a) 463 | x₂ = (-b - sqrt(b*b - 4*a*c)) / (2*a) 464 | """ 465 | 466 | # This is done by catching the Exception raised when calculating 467 | # the square root of a negative number. It is instructive, but it 468 | # can be avoided by simply checking if (b * b - 4 * a * c) is a 469 | # negative number before calling square_root(). 470 | try: 471 | r = (b * b - 4 * a * c).square_root() 472 | x1 = (-b + r) / (2 * a) 473 | x2 = (-b - r) / (2 * a) 474 | return True, x1, x2 475 | except DecimalNumberExceptionMathDomainError: 476 | return False, None, None 477 | 478 | 479 | def string_equation(a: DecimalNumber, b: DecimalNumber, c: DecimalNumber) -> str: 480 | return "{0}x² {1}x {2} = 0".format( 481 | a, 482 | "- " + str(abs(b)) if b < 0 else '+ ' + str(b), 483 | "- " + str(abs(c)) if c < 0 else '+ ' + str(c) 484 | ) 485 | 486 | 487 | list_equations = [ 488 | (7, -5, -9), 489 | (1, -3, 10), 490 | (4, 25, 21), 491 | (1, 3, -10) 492 | ] 493 | 494 | print("-" * 50) 495 | for e in list_equations: 496 | a = DecimalNumber(e[0]) 497 | b = DecimalNumber(e[1]) 498 | c = DecimalNumber(e[2]) 499 | solution, x1, x2 = solve_quadratic_equation(a, b, c) 500 | print(string_equation(a, b, c)) 501 | if solution: 502 | print(" x₁ =", x1) 503 | print(" x₂ =", x2) 504 | else: 505 | print(" The equation does not have a real solution.") 506 | print("-" * 50) 507 | ``` 508 | 509 | This is what the program prints: 510 | 511 | -------------------------------------------------- 512 | 7x² - 5x - 9 = 0 513 | x1 = 1.545951212649517 514 | x2 = -0.8316654983638027 515 | -------------------------------------------------- 516 | 1x² - 3x + 10 = 0 517 | The equation does not have a real solution. 518 | -------------------------------------------------- 519 | 4x² + 25x + 21 = 0 520 | x1 = -1 521 | x2 = -5.25 522 | -------------------------------------------------- 523 | 1x² + 3x - 10 = 0 524 | x₁ = 2 525 | x₂ = -5 526 | -------------------------------------------------- 527 | 528 | 529 | ## Using DecimalNumber on a Micropython board ## 530 | 531 | This is not exactly part of this module, but someone might find it useful. 532 | 533 | **".mpy" files**: a micropython board can run ".py" files directly. It compiles them before executing them, and that takes time. It is a good practice to copy to your micropython board a precompiled version, a ".mpy" file. 534 | 535 | You can compile the Python file "mpy_decimal.py", that contains all the functionality of **DecimalNumber**, before copying it to your micropython board: 536 | 537 | ```shell 538 | python -m mpy_cross mpy_decimal.py 539 | ``` 540 | 541 | It creates the file "mpy_decimal.mpy". You should have **mpy_cross** installed. More information at: [https://pypi.org/project/mpy-cross/](https://pypi.org/project/mpy-cross/) 542 | 543 | The next step is copying the ".mpy" file to your micropython board. You can copy python source code files with an editor, for example [**Thonny**](https://thonny.org/), but it does not copy (as of today) ".mpy" files. The tool [**mpy-repl-tool[mount]**](https://mpy-repl-tool.readthedocs.io/en/latest/index.html) can help you connect your computer to your micropython board. It also allows to mount your board and use it as any other disk or memory unit of your computer. 544 | -------------------------------------------------------------------------------- /mpy_decimal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpy-dev/micropython-decimal-number/15e1216e19bda5aca0456485b2b7ccc317ee2795/mpy_decimal/__init__.py -------------------------------------------------------------------------------- /mpy_decimal/mpy_decimal.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.implementation.name == "cpython": # micropython does not include 'typing' module 4 | from typing import Tuple 5 | if sys.implementation.name == "micropython": # Just in case... 6 | pass 7 | 8 | 9 | class DecimalNumber: 10 | """DecimalNumber is a class for decimal floating point arithmetic with arbitrary precision.""" 11 | VERSION = (1, 0, 0) 12 | VERSION_NAME = "v1.0.0 - August 2021" 13 | DEFAULT_SCALE: int = 16 14 | DECIMAL_SEP: str = "." 15 | THOUSANDS_SEP: str = "," 16 | USE_THOUSANDS_SEP: bool = False 17 | PI_NUMBER: int = 31415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679 18 | PI_SCALE: int = 100 19 | E_NUMBER: int = 27182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274 20 | E_SCALE: int = 100 21 | LN2_NUMBER: int = 6931471805599453094172321214581765680755001343602552541206800094933936219696947156058633269964186875 22 | LN2_SCALE: int = 100 23 | _scale: int = DEFAULT_SCALE 24 | 25 | def __init__(self, number=0, decimals: int = 0) -> None: 26 | """Initialization of a DecimalNumber. 27 | These are this posibilities: 28 | 1) No parameters => number = 0. Example: DecimalNumber() 29 | 2) An integer => number = integer. Example: DecimalNumber(1) 30 | 3) Two integers => number and decimals. Example: Decimal(12345, 3) => Number = 12.345 31 | 4) One string that contains the number. Example: Decimal("12.345") => Number = 12.345 32 | """ 33 | if isinstance(number, int): 34 | self._is_positive: bool = (number >= 0) 35 | self._number: int = number if number >= 0 else -number 36 | if decimals >= 0: 37 | self._num_decimals: int = decimals 38 | else: 39 | raise DecimalNumberExceptionMathDomainError( 40 | "__init__: the number of decimals must be positive") 41 | self._reduce_to_scale() 42 | elif isinstance(number, str): 43 | self.copy_from(DecimalNumber._from_string(number)) 44 | else: 45 | raise DecimalNumberExceptionBadInit( 46 | "Only 'int' or 'str' instances are allowed for initialization") 47 | 48 | @classmethod 49 | def pi(cls) -> "DecimalNumber": 50 | """Calculation of PI using the very fast algorithm present on the 51 | documentation of the module "decimal" of the Python Standard Library: 52 | https://docs.python.org/3/library/decimal.html#recipes 53 | """ 54 | # If it is precalculated 55 | if DecimalNumber.PI_SCALE >= DecimalNumber.get_scale(): 56 | s: DecimalNumber = DecimalNumber(DecimalNumber.PI_NUMBER, DecimalNumber.PI_SCALE) 57 | else: 58 | # Calculates PI 59 | scale: int = DecimalNumber.get_scale() 60 | # extra digits for intermediate steps 61 | DecimalNumber.set_scale(scale + 4) 62 | lasts = DecimalNumber(0) 63 | t = DecimalNumber(3) 64 | s = DecimalNumber(3) 65 | n = DecimalNumber(1) 66 | na = DecimalNumber(0) 67 | d = DecimalNumber(0) 68 | da = DecimalNumber(24) 69 | eight = DecimalNumber(8) 70 | thirtytwo = DecimalNumber(32) 71 | while s != lasts: 72 | lasts.copy_from(s) 73 | n += na 74 | na += eight 75 | d += da 76 | da += thirtytwo 77 | t = (t * n) / d 78 | s += t 79 | DecimalNumber.set_scale(scale) 80 | # Stores the calculated PI 81 | DecimalNumber.PI_NUMBER = (+s)._number # + adjusts to the scale 82 | DecimalNumber.PI_SCALE = (+s)._num_decimals 83 | return +s 84 | 85 | @classmethod 86 | def e(cls) -> "DecimalNumber": 87 | """Calculation of e. 88 | It uses the Taylor series: 89 | e = 1/0! + 1/1! + 1/2! + 1/3! + ... + 1/n! 90 | """ 91 | # If it is precalculated 92 | if DecimalNumber.E_SCALE >= DecimalNumber.get_scale(): 93 | e: DecimalNumber = DecimalNumber(DecimalNumber.E_NUMBER, DecimalNumber.E_SCALE) 94 | else: 95 | scale: int = DecimalNumber.get_scale() 96 | # extra digits for intermediate steps 97 | DecimalNumber.set_scale(scale + 4) 98 | 99 | i = DecimalNumber(0) 100 | f = DecimalNumber(1) 101 | e = DecimalNumber(1) 102 | e2 = DecimalNumber(0) 103 | one = DecimalNumber(1) 104 | while e2 != e: 105 | e2.copy_from(e) 106 | i += one # counter 107 | f *= i 108 | t = one / f 109 | e += t 110 | 111 | DecimalNumber.set_scale(scale) 112 | # Stores the calculated E 113 | DecimalNumber.E_NUMBER = (+e)._number # + adjusts to the scale 114 | DecimalNumber.E_SCALE = (+e)._num_decimals 115 | return +e 116 | 117 | @classmethod 118 | def ln2(cls) -> "DecimalNumber": 119 | """Calculation of ln(2). 120 | ln(2) = -ln(1/2) = -ln(1 - 1/2) 121 | It uses the Taylor series: 122 | ln(1-x) = -x -x²/2 - x³/3 ... 123 | ln(2) = x + x²/2 + x³/3 ... for x = 1/2 124 | """ 125 | # If it is precalculated 126 | if DecimalNumber.LN2_SCALE >= DecimalNumber.get_scale(): 127 | e: DecimalNumber = DecimalNumber(DecimalNumber.LN2_NUMBER, DecimalNumber.LN2_SCALE) 128 | else: 129 | scale: int = DecimalNumber.get_scale() 130 | DecimalNumber.set_scale(scale + 4) # extra digits for intermediate steps 131 | 132 | i = DecimalNumber(0) # counter 133 | half = DecimalNumber(5, 1) # 0.5 134 | x = DecimalNumber(1) 135 | one = DecimalNumber(1) 136 | e = DecimalNumber(0) 137 | e2 = DecimalNumber(1) 138 | while e2 != e: 139 | e2.copy_from(e) 140 | i += one 141 | x *= half 142 | e += x / i 143 | 144 | DecimalNumber.set_scale(scale) 145 | # Stores the calculated LN2 146 | DecimalNumber.LN2_NUMBER = (+e)._number # + adjusts to the scale 147 | DecimalNumber.LN2_SCALE = (+e)._num_decimals 148 | return +e 149 | 150 | def exp(self, inc_scale: bool = True) -> "DecimalNumber": 151 | """Calculates exp(n) 152 | Works for any x, but for speed, it should have |x| < 1. 153 | For an arbitrary number, to guarantee that |x| < 1, it uses: 154 | exp(x) = exp(x - m * log(2)) * 2 ^ m ; where m = floor(x / log(2)) 155 | 156 | Scale is increased if 'inc_false' is True. 157 | """ 158 | scale = DecimalNumber.get_scale() 159 | # Calculating the necessary extra scale: 160 | extra = (abs(self) / DecimalNumber("2.3")).to_int_round() + 10 161 | DecimalNumber.set_scale(scale + extra) 162 | if abs(self) <= 1: 163 | r = DecimalNumber._exp_lt_1(self, inc_scale) 164 | else: 165 | m = (self / DecimalNumber.ln2()).to_int_truncate() 166 | r = DecimalNumber._exp_lt_1(self - m * DecimalNumber.ln2()) * (2 ** m) 167 | 168 | DecimalNumber.set_scale(scale) 169 | return +r 170 | 171 | @staticmethod 172 | def _exp_lt_1(n: "DecimalNumber", inc_scale: bool = True) -> "DecimalNumber": 173 | """ Auxiliary function to calculates exp(n) 174 | Expects |n| < 1 to converge rapidly 175 | """ 176 | if n == 1: 177 | e = DecimalNumber.e() 178 | elif n == -1: 179 | e = 1 / DecimalNumber.e() 180 | else: 181 | i = DecimalNumber(0) 182 | x = DecimalNumber(1) 183 | f = DecimalNumber(1) 184 | e = DecimalNumber(1) 185 | e2 = DecimalNumber(0) 186 | one = DecimalNumber(1) 187 | while e2 != e: 188 | e2.copy_from(e) 189 | i += one # counter 190 | x *= n 191 | f *= i 192 | t = x / f 193 | e += t 194 | 195 | # if inc_scale: 196 | # DecimalNumber.set_scale(scale) 197 | return +e 198 | 199 | def ln(self) -> "DecimalNumber": 200 | """Calculates ln(n) 201 | Newton's method is used to solve: e**a - x = 0 ; a = ln(x) 202 | """ 203 | if self == 1: 204 | return DecimalNumber(0) 205 | if self == 0: 206 | raise DecimalNumberExceptionMathDomainError("ln(0) = -Infinite") 207 | if self < 0: 208 | raise DecimalNumberExceptionMathDomainError("ln(x) exists for x > 0") 209 | n = self 210 | scale: int = DecimalNumber.get_scale() 211 | 212 | # Estimate first value 213 | DecimalNumber.set_scale(10) # Low scale for this is enough 214 | e = DecimalNumber.e() 215 | y0 = DecimalNumber(0) 216 | y1 = DecimalNumber(1) 217 | one = DecimalNumber(1) 218 | p: DecimalNumber = e.clone() 219 | while p < n: 220 | y1 += one 221 | p *= e 222 | 223 | DecimalNumber.set_scale(scale) # Restores scale 224 | DecimalNumber.set_scale(DecimalNumber.get_scale() + 10) # extra digits for intermediate steps 225 | two = DecimalNumber(2) 226 | while y0 != y1: 227 | y0.copy_from(y1) 228 | y1 = y0 + two * ((n - y0.exp(False)) / (n + y0.exp(False))) 229 | 230 | DecimalNumber.set_scale(scale) 231 | return +y1 232 | 233 | def sin(self) -> "DecimalNumber": 234 | """Calculates sin(x). x = radians 235 | It uses the Taylor series: sin(x) = x - x³/3! + x⁵/5! - x⁷/7! ... 236 | """ 237 | x = self.clone() 238 | scale: int = DecimalNumber.get_scale() 239 | DecimalNumber.set_scale(scale + 4) # extra digits for intermediate steps 240 | 241 | negative_radians: bool = (x < 0) 242 | if negative_radians: 243 | x = -x 244 | # Calculates x mod 2π 245 | pi = DecimalNumber.pi() 246 | f: int = (x / (pi * 2)).to_int_truncate() 247 | if f > 0: 248 | x -= f * 2 * pi 249 | 250 | # Determines the quadrant and reduces the range of x to 0 - π/2 251 | # sin(-x) = -sin(x) ; cos(-x) = cos(x) ; tan(-x) = -tan(x) 252 | half_pi = pi / 2 253 | r = half_pi.clone() 254 | quadrant: int = 1 255 | while x > r: 256 | r += half_pi 257 | quadrant += 1 258 | 259 | if quadrant == 2: 260 | x = pi - x 261 | elif quadrant == 3: 262 | x = x - pi 263 | elif quadrant == 4: 264 | x = 2 * pi - x 265 | 266 | i = DecimalNumber(1) # counter 267 | two = DecimalNumber(2) 268 | n = x.clone() 269 | d = DecimalNumber(1) 270 | s = DecimalNumber(1) 271 | e = n.clone() 272 | e2 = DecimalNumber(0) 273 | while e2 != e: 274 | e2.copy_from(e) 275 | i += two 276 | n *= x * x 277 | d *= i * (i - 1) 278 | s = -s 279 | e += (n * s) / d 280 | 281 | if quadrant > 2: 282 | e = -e 283 | if negative_radians: 284 | e = -e 285 | 286 | DecimalNumber.set_scale(scale) 287 | return +e 288 | 289 | def cos(self) -> "DecimalNumber": 290 | """Calculates cos(x). x = radians 291 | It uses the Taylor series: cos(x) = 1 - x²/2! + x⁴/4! - x⁶/6! ... 292 | """ 293 | x = self.clone() 294 | scale: int = DecimalNumber.get_scale() 295 | DecimalNumber.set_scale(scale + 4) # extra digits for intermediate steps 296 | 297 | if (x < 0): # cos(-x) = cos(x) 298 | x = -x 299 | 300 | # Calculates x mod 2π 301 | pi = DecimalNumber.pi() 302 | f: int = (x / (pi * 2)).to_int_truncate() 303 | if f > 0: 304 | x -= f * 2 * pi 305 | 306 | # Determines the quadrant and reduces the range of x to 0 - π/2 307 | half_pi = pi / 2 308 | r = half_pi.clone() 309 | quadrant: int = 1 310 | while x > r: 311 | r += half_pi 312 | quadrant += 1 313 | 314 | if quadrant == 2: 315 | x = pi - x 316 | elif quadrant == 3: 317 | x = x - pi 318 | elif quadrant == 4: 319 | x = 2 * pi - x 320 | 321 | i = DecimalNumber(1) # counter 322 | two = DecimalNumber(2) 323 | n = DecimalNumber(1) 324 | d = DecimalNumber(1) 325 | s = DecimalNumber(1) 326 | e = n.clone() 327 | e2 = DecimalNumber(0) 328 | while e2 != e: 329 | e2.copy_from(e) 330 | n *= x * x 331 | d *= i * (i + 1) 332 | i += two 333 | s = -s 334 | e += (n * s) / d 335 | 336 | if quadrant == 2 or quadrant == 3: 337 | e = -e 338 | 339 | DecimalNumber.set_scale(scale) 340 | return +e 341 | 342 | def tan(self) -> "DecimalNumber": 343 | """Calculates tan(x) = sin(x) / cos(x). x = radians """ 344 | x = self.clone() 345 | 346 | # Calculates x mod 2π 347 | pi = DecimalNumber.pi() 348 | f: int = (x / (pi * 2)).to_int_truncate() 349 | if f > 0: 350 | x -= f * 2 * pi 351 | 352 | half_pi = pi / 2 353 | three_halves_pi = (3 * pi) / 2 354 | # Determines the quadrant 355 | r = half_pi.clone() 356 | quadrant: int = 1 357 | while x > r: 358 | r += half_pi 359 | quadrant += 1 360 | 361 | # tan(x) = sin(x) / cos(x) ; if cos(x) == 0 => tan(x) = ∞ 362 | 363 | if self == half_pi or self == three_halves_pi: 364 | raise DecimalNumberExceptionDivisionByZeroError("tan(x) = ±Infinite") 365 | else: 366 | scale: int = DecimalNumber.get_scale() 367 | DecimalNumber.set_scale(scale + 4) 368 | s = x.sin() 369 | c = x.cos() 370 | if c == 0: 371 | DecimalNumber.set_scale(scale) 372 | raise DecimalNumberExceptionDivisionByZeroError("tan(x) = ±Infinite") 373 | else: 374 | t = s / c 375 | DecimalNumber.set_scale(scale) 376 | return +t 377 | 378 | def asin(self) -> "DecimalNumber": 379 | """Calculates asin(x) 380 | It uses the Taylor series: arcsin(x) = x + 3x³/6 + 15x⁵/336 + ... 381 | It converges very slowly for |x| near 1. To avoid values near 1: 382 | If |n| between 0 and 0.707: arcsin(x) is calculated using the series. 383 | if |n| between 0.707 and 1: arcsin(x) is calculated as pi/2 - arcsin( sqrt(1 - x²) ) 384 | This guarantees arcsin(x) using series with x <= 0.707 ; (sqrt(1/2)). 385 | """ 386 | if self >= -1 and self <= 1: 387 | if self == -1: 388 | return -(DecimalNumber.pi() / 2) 389 | elif self == 1: 390 | return (DecimalNumber.pi() / 2) 391 | elif self == 0: 392 | return DecimalNumber(0) 393 | 394 | scale: int = DecimalNumber.get_scale() 395 | DecimalNumber.set_scale(DecimalNumber.get_scale() + 4) # extra digits for intermediate steps 396 | 397 | trick: bool = False 398 | if abs(self) > DecimalNumber("0.707"): 399 | trick = True 400 | x = (1 - self * self).square_root() 401 | else: 402 | x = self.clone() 403 | 404 | i = DecimalNumber(1) # counter 405 | one = DecimalNumber(1) 406 | two = DecimalNumber(2) 407 | four = DecimalNumber(4) 408 | n = DecimalNumber(1) 409 | d = DecimalNumber(1) 410 | n2 = x.clone() 411 | e = x.clone() 412 | e2 = DecimalNumber(0) 413 | counter: int = 0 414 | while e2 != e: 415 | e2.copy_from(e) 416 | n *= i 417 | i += two 418 | d *= i - one 419 | n2 *= x * x 420 | e += (n * n2) / (d * i) 421 | 422 | if trick: 423 | if self._is_positive: 424 | e = DecimalNumber.pi() / 2 - e 425 | else: 426 | e = e - DecimalNumber.pi() / 2 427 | 428 | DecimalNumber.set_scale(scale) 429 | return +e 430 | else: 431 | raise DecimalNumberExceptionMathDomainError("asin(x) admits -1 <= x <= 1 only") 432 | 433 | def acos(self) -> "DecimalNumber": 434 | """Calculates acos(x) 435 | It uses the equivalence: acos(x) = π/2 - asin(x) 436 | """ 437 | if self >= -1 and self <= 1: 438 | scale: int = DecimalNumber.get_scale() 439 | DecimalNumber.set_scale(DecimalNumber.get_scale() + 4) # extra digits for intermediate steps 440 | 441 | a = (DecimalNumber.pi() / 2) - self.asin() 442 | 443 | DecimalNumber.set_scale(scale) 444 | return +a 445 | else: 446 | raise DecimalNumberExceptionMathDomainError("acos(x) admits -1 <= x <= 1 only") 447 | 448 | def atan(self) -> "DecimalNumber": 449 | """Calculates atan(x) 450 | It uses: atan(x) = asin( x / sqrt(1 + x²) ) 451 | """ 452 | scale: int = DecimalNumber.get_scale() 453 | DecimalNumber.set_scale(DecimalNumber.get_scale() + 4) # extra digits for intermediate steps 454 | one = DecimalNumber(1) 455 | v = self / (one + self * self).square_root() 456 | a = v.asin() 457 | 458 | DecimalNumber.set_scale(scale) 459 | return +a 460 | 461 | @staticmethod 462 | def atan2(y: "DecimalNumber", x: "DecimalNumber") -> "DecimalNumber": 463 | """Calculates atan2(y, x), 2-argument arctangent 464 | It uses: 465 | if x > 0: atan(y/x) 466 | if x < 0: 467 | if y >= 0: atan(y/x) + pi 468 | if y < 0: atan(y/x) - pi 469 | if x = 0: 470 | if y > 0: +pi/2 471 | if y < 0: -pi/2 472 | if y = 0: undefined 473 | """ 474 | if isinstance(y, int): 475 | y = DecimalNumber(y) 476 | if isinstance(x, int): 477 | x = DecimalNumber(x) 478 | 479 | scale: int = DecimalNumber.get_scale() 480 | DecimalNumber.set_scale(DecimalNumber.get_scale() + 4) # extra digits for intermediate steps 481 | r = DecimalNumber() 482 | if x == 0: 483 | if y == 0: 484 | raise DecimalNumberExceptionMathDomainError( 485 | "Undefined value for atan2(0, 0)") 486 | elif y > 0: 487 | r = (DecimalNumber.pi() / 2) 488 | else: 489 | r = (-DecimalNumber.pi() / 2) 490 | else: 491 | r = (y / x).atan() 492 | if x < 0: 493 | if y >= 0: 494 | r += DecimalNumber.pi() 495 | else: 496 | r -= DecimalNumber.pi() 497 | 498 | DecimalNumber.set_scale(scale) 499 | return +r 500 | 501 | @staticmethod 502 | def version() -> str: 503 | """Returns a tuple (MINOR, MINOR, PATCH) with the version of DecimalNumber""" 504 | return DecimalNumber.VERSION 505 | 506 | @staticmethod 507 | def version_name() -> str: 508 | """Returns a string with the version of DecimalNumber""" 509 | return DecimalNumber.VERSION_NAME 510 | 511 | @staticmethod 512 | def set_scale(num_digits: int) -> None: 513 | """Sets the scale. 514 | Scale is a class value, the maximum number of decimals that a DecimalNumber can have. 515 | The default value is 16. The maximum value is only limited by the available 516 | memory and computer power.""" 517 | if num_digits >= 0: 518 | DecimalNumber._scale = num_digits 519 | else: 520 | raise DecimalNumberExceptionMathDomainError( 521 | "set_scale: scale must be positive") 522 | 523 | @staticmethod 524 | def get_scale() -> int: 525 | """Gets the current scale value.""" 526 | return DecimalNumber._scale 527 | 528 | @staticmethod 529 | def _parse_number(number: str) -> Tuple[bool, int, int]: 530 | """This is a static and auxiliary method to parse a string containing 531 | a number. If the string is parsed as a number, it returns three values: 532 | True --> string correctly parsed as number. 533 | Integer containing all the digits of the number. 534 | Integer representing the number of decimals. 535 | For example: "-12345.678" will be parsed and the values returned will be: 536 | (True, -12345678, 3) 537 | If the parsing fails, it returns (Falsem 0, 0). 538 | Note: this is faster than using a regular expression. Also, when using 539 | the regular expression "^\-?[0-9]+\.?[0-9]*" and exception was raised when 540 | using micropython when the string "number" was long. 541 | """ 542 | # True: correct 543 | # Note: 544 | step: int = 1 # 1: '-', 2: [0-9], 3: '.', 4: [0-9] 545 | position: int = 0 546 | integer_number: int = 0 547 | is_positive: bool = True 548 | num_decimals: int = 0 549 | number = tuple(number,) # Faster than indexing the string 550 | length: int = len(number) 551 | digits: str = "0123456789" 552 | last_valid: int = 0 553 | while position < length: 554 | if step == 1: 555 | if number[position] == '-': 556 | is_positive = False 557 | position += 1 558 | step = 2 559 | elif step == 2: 560 | if digits.find(number[position]) != -1: # [0-9]+ 561 | integer_number = integer_number * \ 562 | 10 + int(number[position]) 563 | position += 1 564 | last_valid = position 565 | else: 566 | step = 3 567 | elif step == 3: 568 | if number[position] == DecimalNumber.DECIMAL_SEP: 569 | position += 1 570 | last_valid = position 571 | step = 4 572 | elif step == 4: 573 | if digits.find(number[position]) != -1: # [0-9]* 574 | integer_number = integer_number * \ 575 | 10 + int(number[position]) 576 | num_decimals += 1 577 | position += 1 578 | last_valid = position 579 | else: 580 | break 581 | if last_valid == length: 582 | if not is_positive: 583 | integer_number = -integer_number 584 | return (True, integer_number, num_decimals) 585 | else: 586 | return (False, 0, 0) 587 | 588 | @staticmethod 589 | def _from_string(number: str) -> "DecimalNumber": 590 | """static and auxiliary method to create a DecimalNumber from a string.""" 591 | correct, integer_number, num_decimals = DecimalNumber._parse_number( 592 | number) 593 | if not correct: 594 | raise DecimalNumberExceptionParseError( 595 | "Syntax error parsing '{0}'".format(number)) 596 | else: 597 | n = DecimalNumber(integer_number, num_decimals) 598 | return n 599 | 600 | @staticmethod 601 | def _make_integer_comparable(n1: "DecimalNumber", n2: "DecimalNumber") -> Tuple[int]: 602 | """Static and auxiliary method to creates two integers from two DecimalNumber, 603 | without decimals, that can be compared (or sum) by taking into account their decimals. 604 | Examples: 605 | n1: 12345.678, n2: 5.4321098 --> i1: 123456780000, i2: 54321098 606 | n1: 345.1, n2: 7.65: --> i1: 34510, i2: 765 607 | """ 608 | max_decimals: int = max(n1._num_decimals, n2._num_decimals) 609 | n1_number: int = n1._number 610 | if not n1._is_positive: 611 | n1_number = -n1_number 612 | n2_number: int = n2._number 613 | if not n2._is_positive: 614 | n2_number = -n2_number 615 | if max_decimals > n1._num_decimals: 616 | n1_number *= 10 ** (max_decimals - n1._num_decimals) 617 | if max_decimals > n2._num_decimals: 618 | n2_number *= 10 ** (max_decimals - n2._num_decimals) 619 | return (n1_number, n2_number) 620 | 621 | @staticmethod 622 | def _isqrt(n: int) -> int: 623 | """Static and auxiliary method to calculate the square root 624 | of an integer. 625 | It uses Newton's method with integer division. 626 | """ 627 | if n < 0: 628 | return 0 629 | # Calculates initial value 630 | t: int = n 631 | x1: int = 1 632 | while t > 100: 633 | x1 *= 10 634 | t //= 100 635 | # Uses Newton's method 636 | x2: int = (x1 + n // x1) // 2 637 | while abs(x2 - x1) > 1: 638 | x1 = x2 639 | x2 = (x1 + n // x1) // 2 640 | return x2 641 | 642 | def clone(self) -> "DecimalNumber": 643 | """Returns a new DecimalNumber as a clone of self.""" 644 | n = DecimalNumber() 645 | n._number = self._number 646 | n._num_decimals = self._num_decimals 647 | n._is_positive = self._is_positive 648 | return n 649 | 650 | def copy_from(self, other: "DecimalNumber") -> None: 651 | """It copies on self other DecimalNumber.""" 652 | self._number = other._number 653 | self._num_decimals = other._num_decimals 654 | self._is_positive = other._is_positive 655 | 656 | def square_root(self) -> "DecimalNumber": 657 | """Calculates the square root of a DecimalNumber. 658 | It converts the DecimalNumber to an integer (without decimals), calculates 659 | its square root using _isqrt() and then it sets the decimals. 660 | """ 661 | if not self._is_positive: 662 | raise DecimalNumberExceptionMathDomainError( 663 | "No square root for negative numbers") 664 | 665 | n = DecimalNumber() 666 | num_integer: int = self._number 667 | num_integer *= (10 ** (DecimalNumber.get_scale() * 2)) 668 | additional_decimals: int = 0 669 | if (self._num_decimals % 2) == 1: 670 | num_integer *= 10 671 | additional_decimals = 1 672 | 673 | num_integer = DecimalNumber._isqrt(num_integer) 674 | n._number = num_integer 675 | n._num_decimals = ( 676 | (self._num_decimals + additional_decimals) // 2) + DecimalNumber.get_scale() 677 | n._reduce_to_scale() 678 | return n 679 | 680 | def __add__(self, other: "DecimalNumber") -> "DecimalNumber": 681 | """Adds two DecimalNumber. 682 | Returns (self + other) 683 | """ 684 | if isinstance(other, int): 685 | other = DecimalNumber(other) 686 | 687 | # 123 + 456 : 123 688 | # : 456 689 | # : 579 690 | # 123 + 4.56 : 123 0 691 | # : 4 56 692 | # : 127 56 --> 127 + Apply 2 decimals to 56 --> 0.56 693 | # 123 + 0.0456 : 123 0 694 | # : 0 456 695 | # : 123 456 --> 123 + Apply 4 decimals to 456 --> 0.0456 696 | # 123.723 + 4.56 : 123 723 697 | # : 4 560 --> Apply 3 decimals to 56 --> 560 698 | # : 127 1283 --> 127 + Apply 3 decimals to 1283 --> 0.283 --> Add 1 to 127 --> 128 699 | # 0.0123 + 0.56 : 0 123 700 | # : 0 56 --> Apply 4 decimals to 56 --> 5600 701 | # : 0 5723 --> 123 + 5600 702 | 703 | max_decimals: int = max(self._num_decimals, other._num_decimals) 704 | 705 | a_factor: int = 10 ** self._num_decimals 706 | b_factor: int = 10 ** other._num_decimals 707 | 708 | a_integer: int = self._number // a_factor 709 | a_decimals: int = self._number % a_factor 710 | b_integer: int = other._number // b_factor 711 | b_decimals: int = other._number % b_factor 712 | 713 | if self._num_decimals < max_decimals: 714 | a_decimals *= (10 ** (max_decimals - self._num_decimals)) 715 | 716 | if other._num_decimals < max_decimals: 717 | b_decimals *= (10 ** (max_decimals - other._num_decimals)) 718 | 719 | c_factor: int = max(a_factor, b_factor) 720 | a_all: int = a_integer * c_factor + a_decimals 721 | b_all: int = b_integer * c_factor + b_decimals 722 | 723 | c_all: int = (a_all if self._is_positive else -a_all) + (b_all if other._is_positive else -b_all) 724 | c_is_positive: bool = (c_all > 0) 725 | if c_all < 0: 726 | c_all = -c_all 727 | 728 | new_number = DecimalNumber(c_all, max_decimals) 729 | new_number._is_positive = c_is_positive 730 | 731 | new_number._reduce_to_scale() 732 | 733 | return new_number 734 | 735 | def __iadd__(self, other: "DecimalNumber") -> "DecimalNumber": 736 | """Adds a DecimalNumber to itself. 737 | Returns (self += other) 738 | """ 739 | n = self.__add__(other) 740 | self._number = n._number 741 | self._num_decimals = n._num_decimals 742 | self._is_positive = n._is_positive 743 | return self 744 | 745 | def __radd__(self, other: int) -> "DecimalNumber": 746 | """Reverse add. 747 | It is called for (integer + DecimalNumber). 748 | At this moment, micropython does not support it. 749 | """ 750 | return self.__add__(DecimalNumber(other)) 751 | 752 | def __sub__(self, other: "DecimalNumber") -> "DecimalNumber": 753 | if isinstance(other, int): 754 | other = DecimalNumber(other) 755 | s = other.clone() 756 | s._is_positive = not s._is_positive 757 | return self.__add__(s) 758 | 759 | def __isub__(self, other: "DecimalNumber") -> "DecimalNumber": 760 | n = self.__sub__(other) 761 | self._number = n._number 762 | self._num_decimals = n._num_decimals 763 | self._is_positive = n._is_positive 764 | return self 765 | 766 | def __rsub__(self, other: int) -> "DecimalNumber": 767 | return DecimalNumber(other).__sub__(self) 768 | 769 | def __mul__(self, other: "DecimalNumber") -> "DecimalNumber": 770 | if isinstance(other, int): 771 | other = DecimalNumber(other) 772 | a_integer: int = self._number if self._is_positive else -self._number 773 | b_integer: int = other._number if other._is_positive else -other._number 774 | c_integer: int = a_integer * b_integer 775 | new_number = DecimalNumber( 776 | c_integer, self._num_decimals + other._num_decimals) 777 | return new_number 778 | 779 | def __imul__(self, other: "DecimalNumber") -> "DecimalNumber": 780 | n = self.__mul__(other) 781 | self._number = n._number 782 | self._num_decimals = n._num_decimals 783 | self._is_positive = n._is_positive 784 | return self 785 | 786 | def __rmul__(self, other: int) -> "DecimalNumber": 787 | return self.__mul__(DecimalNumber(other)) 788 | 789 | def __truediv__(self, other: "DecimalNumber") -> "DecimalNumber": 790 | if isinstance(other, int): 791 | other = DecimalNumber(other) 792 | # a_integer: int = self._number if self._is_positive else -self._number 793 | # b_integer: int = other._number if other._is_positive else -other._number 794 | a_integer: int 795 | b_integer: int 796 | a_integer, b_integer = DecimalNumber._make_integer_comparable(self, other) 797 | if b_integer != 0: 798 | c_factor: int = 10 ** (DecimalNumber.get_scale() + 2) 799 | c_integer: int = (a_integer * c_factor) // b_integer 800 | new_number = DecimalNumber( 801 | c_integer, (DecimalNumber.get_scale() + 2)) 802 | else: 803 | raise DecimalNumberExceptionDivisionByZeroError("Division by zero") 804 | return new_number 805 | 806 | def __itruediv__(self, other: "DecimalNumber") -> "DecimalNumber": 807 | n = self.__truediv__(other) 808 | self._number = n._number 809 | self._num_decimals = n._num_decimals 810 | self._is_positive = n._is_positive 811 | return self 812 | 813 | def __rtruediv__(self, other: int) -> "DecimalNumber": 814 | return DecimalNumber(other).__truediv__(self) 815 | 816 | def __pow__(self, other: int) -> "DecimalNumber": 817 | # Exponentition by squaring: https://en.wikipedia.org/wiki/Exponentiation_by_squaring 818 | e: int = other 819 | x = self.clone() 820 | x._is_positive = True 821 | if other == 0: 822 | return DecimalNumber(1) 823 | scale: int = DecimalNumber.get_scale() 824 | 825 | # Calculating the necessary extra scale: 826 | extra = abs(other) * (len(str(self._number)) - self._num_decimals) 827 | # extra digits for intermediate steps 828 | DecimalNumber.set_scale(scale + extra) 829 | if other < 0: 830 | x = DecimalNumber(1) / x 831 | other = -other 832 | y = DecimalNumber(1) 833 | while other > 1: 834 | if (other % 2) == 0: 835 | x *= x 836 | other //= 2 837 | else: 838 | y *= x 839 | x *= x 840 | other = (other - 1) // 2 841 | x *= y 842 | DecimalNumber.set_scale(scale) 843 | if not self._is_positive and (e % 2) == 1: 844 | return -x 845 | else: 846 | return +x 847 | 848 | def __neg__(self) -> "DecimalNumber": 849 | n = self.clone() 850 | n._is_positive = not self._is_positive 851 | n._reduce_to_scale() 852 | return n 853 | 854 | def __pos__(self) -> "DecimalNumber": 855 | n = self.clone() 856 | n._reduce_to_scale() 857 | return n 858 | 859 | def __abs__(self) -> "DecimalNumber": 860 | n = self.clone() 861 | n._is_positive = True 862 | n._reduce_to_scale() 863 | return n 864 | 865 | def __lt__(self, other: "DecimalNumber") -> bool: # Less than 866 | if isinstance(other, int): 867 | other = DecimalNumber(other) 868 | n1, n2 = DecimalNumber._make_integer_comparable(self, other) 869 | return (n1 < n2) 870 | 871 | def __le__(self, other: "DecimalNumber") -> bool: # Less than or equal to 872 | if isinstance(other, int): 873 | other = DecimalNumber(other) 874 | n1, n2 = DecimalNumber._make_integer_comparable(self, other) 875 | return (n1 <= n2) 876 | 877 | def __eq__(self, other: "DecimalNumber") -> bool: # Equal to 878 | if isinstance(other, int): 879 | other = DecimalNumber(other) 880 | n1, n2 = DecimalNumber._make_integer_comparable(self, other) 881 | return (n1 == n2) 882 | 883 | def __ne__(self, other: "DecimalNumber") -> bool: # Not equal to 884 | if isinstance(other, int): 885 | other = DecimalNumber(other) 886 | n1, n2 = DecimalNumber._make_integer_comparable(self, other) 887 | return (n1 != n2) 888 | 889 | def __gt__(self, other: "DecimalNumber") -> bool: # Greater than 890 | if isinstance(other, int): 891 | other = DecimalNumber(other) 892 | n1, n2 = DecimalNumber._make_integer_comparable(self, other) 893 | return (n1 > n2) 894 | 895 | def __ge__(self, other: "DecimalNumber") -> bool: # Greater than or equal to 896 | if isinstance(other, int): 897 | other = DecimalNumber(other) 898 | n1, n2 = DecimalNumber._make_integer_comparable(self, other) 899 | return (n1 >= n2) 900 | 901 | def __str__(self, thousands: bool = False) -> str: 902 | # Integer / Decimals: String 903 | # 12345 / 0: 12345 904 | # 12345 / 1: 1234.5 905 | # 12345 / 2: 123.45 906 | # 12345 / 3: 12.345 907 | # 12345 / 4: 1.2345 908 | # 12345 / 5: 0.12345 909 | # 12345 / 6: 0.012345 910 | # 12345 / 7: 0.0012345 911 | # 12345 / 8: 0.00012345 912 | str_number: str = str( 913 | self._number) if self._number >= 0 else str(-self._number) 914 | if self._num_decimals != 0: 915 | num_digits: int = len(str_number) 916 | if self._num_decimals < num_digits: 917 | str_number = str_number[:( 918 | num_digits - self._num_decimals)] + "." + str_number[-self._num_decimals:] 919 | else: 920 | str_number = "0" + "." + \ 921 | ("0" * (self._num_decimals - num_digits)) + str_number 922 | 923 | if thousands: 924 | pos_decimal: int = str_number.find(".") 925 | if pos_decimal == -1: 926 | first_part: str = str_number 927 | second_part: str = "" 928 | else: 929 | first_part: str = str_number[:pos_decimal] 930 | second_part: str = str_number[pos_decimal + 1:] 931 | first_part = "{:,d}".format(int(first_part)) 932 | ##### Commenting this part to not separate decimals ############################### 933 | # if len(second_part) > 0: 934 | # # Note: reversing with second_part[::-1] is not available for micropython 935 | # second_part = "{:,d}".format(int( ''.join(reversed(second_part)) )) 936 | # second_part = ''.join(reversed(second_part)) 937 | ################################################################################### 938 | str_number = first_part 939 | if len(second_part) > 0: 940 | str_number += "." + second_part 941 | 942 | str_number = str_number.replace(".", "#") 943 | str_number = str_number.replace(",", DecimalNumber.THOUSANDS_SEP) 944 | str_number = str_number.replace("#", DecimalNumber.DECIMAL_SEP) 945 | 946 | if not self._is_positive: 947 | str_number = "-" + str_number 948 | 949 | return str_number 950 | 951 | def __repr__(self) -> str: 952 | return 'DecimalNumber("' + str(self) + '")' 953 | 954 | def to_int_truncate(self) -> int: 955 | return self._number // (10 ** self._num_decimals) 956 | 957 | def to_int_round(self) -> int: 958 | n = self.clone() 959 | s = DecimalNumber.get_scale() 960 | DecimalNumber.set_scale(0) 961 | n._reduce_to_scale() 962 | DecimalNumber.set_scale(s) 963 | return n._number 964 | 965 | def to_string_thousands(self) -> str: 966 | return self.__str__(True) 967 | 968 | # Returns a string representing the number limited to N characters, including '.', '-' and, optionally thousands. 969 | # It is useful to limit the number to the length of a calculator's LCD display, for example. 970 | # If the number does not fit, it returns "Overflow". 971 | def to_string_max_length(self, max_length: int, thousands: bool = False) -> None: 972 | if max_length < 8: 973 | max_length = 8 974 | 975 | str_number: str = self.__str__(thousands) 976 | # 1,234,567,890.1234567 977 | # If the number of characters before '.' is greater than max_length --> Overflow 978 | pos_point: int = str_number.find('.') 979 | if pos_point == -1: # No decimals 980 | pos_point = len(str_number) 981 | if pos_point > max_length: 982 | return "Overflow" 983 | else: 984 | str_number = str_number[:max_length] 985 | # If there are decimals, we can eliminate trailing zeros 986 | pos_point: int = str_number.find('.') 987 | if pos_point != -1: 988 | # 123.34000 989 | while str_number[-1:] == '0': 990 | str_number = str_number[:-1] 991 | # If the last character is a point, it can be deleted 992 | if str_number[-1:] == '.': 993 | str_number = str_number[:-1] 994 | if str_number == "-0": 995 | str_number = "0" 996 | return str_number 997 | 998 | def _eliminate_decimal_trailing_zeros(self) -> None: 999 | while self._num_decimals > 0 and (self._number % 10) == 0: 1000 | self._number //= 10 1001 | self._num_decimals -= 1 1002 | 1003 | def _reduce_to_scale(self) -> None: 1004 | if self._num_decimals > DecimalNumber.get_scale(): 1005 | # Round half to even: https://en.wikipedia.org/wiki/Rounding#Round_half_to_even 1006 | 1007 | # Example: 1008 | # scale = 3 1009 | # Number: 123.456789 1010 | # n = 123456789, decimals = 6 1011 | # It should be 123.457 ; n = 123457, decimals = scale = 3 1012 | 1013 | n: int = self._number 1014 | s: int = self._num_decimals - DecimalNumber.get_scale() # s: 6 - 3 = 3 1015 | ds: int = (10 ** s) 1016 | 1017 | v: int = n % (ds * 10) # v: n % 10**4 = 6789 1000 1018 | b: int = v % ds # b: v % 10**3 = 789 1019 | a: int = v // ds # a: v // 10**3 = 6 1020 | m: int = ds // 2 # m: 10**3 // 2 = 500 (to be compared to b) 1021 | 1022 | if (a % 2) == 1: # Calculating differences to get to the nearest even 1023 | if b < m: 1024 | x: int = -b 1025 | else: 1026 | x: int = ds - b 1027 | else: 1028 | if b <= m: 1029 | x: int = -b 1030 | else: 1031 | x: int = ds - b 1032 | 1033 | self._number = (n + x) // ds 1034 | self._num_decimals = DecimalNumber.get_scale() 1035 | 1036 | self._eliminate_decimal_trailing_zeros() 1037 | 1038 | if self._number == 0 and not self._is_positive: # Prevents -0 1039 | self._is_positive = True 1040 | 1041 | 1042 | class DecimalNumberException(Exception): 1043 | pass 1044 | 1045 | 1046 | class DecimalNumberExceptionParseError(DecimalNumberException): 1047 | def __init__(self, *args: object) -> None: 1048 | if args: 1049 | self.message = args[0] 1050 | else: 1051 | self.message = None 1052 | 1053 | def __str__(self) -> str: 1054 | if self.message: 1055 | return "DecimalNumberExceptionParseError: {0}".format(self.message) 1056 | else: 1057 | return "DecimalNumberExceptionParseError" 1058 | 1059 | 1060 | class DecimalNumberExceptionBadInit(DecimalNumberException): 1061 | def __init__(self, *args: object) -> None: 1062 | if args: 1063 | self.message = args[0] 1064 | else: 1065 | self.message = None 1066 | 1067 | def __str__(self) -> str: 1068 | if self.message: 1069 | return "DecimalNumberExceptionBadInit: {0}".format(self.message) 1070 | else: 1071 | return "DecimalNumberExceptionBadInit" 1072 | 1073 | 1074 | class DecimalNumberExceptionMathDomainError(DecimalNumberException): 1075 | def __init__(self, *args: object) -> None: 1076 | if args: 1077 | self.message = args[0] 1078 | else: 1079 | self.message = None 1080 | 1081 | def __str__(self) -> str: 1082 | if self.message: 1083 | return "DecimalNumberExceptionMathDomainError: {0}".format(self.message) 1084 | else: 1085 | return "DecimalNumberExceptionMathDomainError" 1086 | 1087 | 1088 | class DecimalNumberExceptionDivisionByZeroError(DecimalNumberException): 1089 | def __init__(self, *args: object) -> None: 1090 | if args: 1091 | self.message = args[0] 1092 | else: 1093 | self.message = None 1094 | 1095 | def __str__(self) -> str: 1096 | if self.message: 1097 | return "DecimalNumberExceptionDivisionByZeroError: {0}".format(self.message) 1098 | else: 1099 | return "DecimalNumberExceptionDivisionByZeroError" 1100 | 1101 | 1102 | if __name__ == "__main__": 1103 | print("DecimalNumber module -", DecimalNumber.VERSION) 1104 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup ( 4 | name = 'Micropython DecimalNumber', 5 | version = '1.0', 6 | packages = find_packages() 7 | ) 8 | -------------------------------------------------------------------------------- /tests/example1.py: -------------------------------------------------------------------------------- 1 | from mpy_decimal.mpy_decimal import * 2 | 3 | 4 | def solve_quadratic_equation(a: DecimalNumber, b: DecimalNumber, c: DecimalNumber) -> Tuple[bool, DecimalNumber, DecimalNumber]: 5 | """It solves quadratic equations: 6 | a * x² + b * x + c = 0 7 | x₁ = (-b + sqrt(b*b - 4*a*c)) / (2*a) 8 | x₂ = (-b - sqrt(b*b - 4*a*c)) / (2*a) 9 | """ 10 | 11 | # This is done by catching the Exception raised when calculating 12 | # the square root of a negative number. It is instructive, but it 13 | # can be avoided by simply checking if (b * b - 4 * a * c) is a 14 | # negative number before calling square_root(). 15 | try: 16 | r = (b * b - 4 * a * c).square_root() 17 | x1 = (-b + r) / (2 * a) 18 | x2 = (-b - r) / (2 * a) 19 | return True, x1, x2 20 | except DecimalNumberExceptionMathDomainError: 21 | return False, None, None 22 | 23 | 24 | def string_equation(a: DecimalNumber, b: DecimalNumber, c: DecimalNumber) -> str: 25 | return "{0}x² {1}x {2} = 0".format( 26 | a, 27 | "- " + str(abs(b)) if b < 0 else '+ ' + str(b), 28 | "- " + str(abs(c)) if c < 0 else '+ ' + str(c) 29 | ) 30 | 31 | 32 | list_equations = [ 33 | (7, -5, -9), 34 | (1, -3, 10), 35 | (4, 25, 21), 36 | (1, 3, -10) 37 | ] 38 | 39 | print("-" * 50) 40 | for e in list_equations: 41 | a = DecimalNumber(e[0]) 42 | b = DecimalNumber(e[1]) 43 | c = DecimalNumber(e[2]) 44 | solution, x1, x2 = solve_quadratic_equation(a, b, c) 45 | print(string_equation(a, b, c)) 46 | if solution: 47 | print(" x₁ =", x1) 48 | print(" x₂ =", x2) 49 | else: 50 | print(" The equation does not have a real solution.") 51 | print("-" * 50) 52 | -------------------------------------------------------------------------------- /tests/perf_decimal_number.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import random 3 | from mpy_decimal.mpy_decimal import * 4 | 5 | # Imports modules and it sets limits depending on the implementation 6 | if sys.implementation.name == "cpython": 7 | import traceback 8 | import time 9 | iteration_limit: int = 100000 10 | iteration_limit2: int = 40000 11 | pi_decimals: int = 1000 12 | if sys.implementation.name == "micropython": 13 | import machine 14 | import utime 15 | iteration_limit: int = 1000 16 | iteration_limit2: int = 400 17 | pi_decimals: int = 300 18 | 19 | format_str: str = "{:<36}" 20 | 21 | def system_machine_info() -> None: 22 | """It prints system information.""" 23 | print(format_str.format("Implementation name:"), sys.implementation.name) 24 | print(format_str.format("Implementation version:"), 25 | "{0}.{1}.{2}".format( 26 | sys.implementation.version[0], 27 | sys.implementation.version[1], 28 | sys.implementation.version[2] 29 | ) 30 | ) 31 | print(format_str.format("Implementation platform:"), sys.platform) 32 | if sys.implementation.name == "micropython": 33 | print(format_str.format("CPU frequency:"), 34 | machine.freq() // 1000000, "Mhz") 35 | 36 | def get_time_ms() -> int: 37 | """It gets the time in miliseconds. 38 | The way to get it depends on the implementation.""" 39 | if sys.implementation.name == "cpython": 40 | return round(time.time() * 1000) 41 | if sys.implementation.name == "micropython": 42 | return utime.ticks_ms() 43 | 44 | def gen_random_number() -> DecimalNumber: 45 | """Generates a random number with a number of decimals equal to scale. 46 | The number of digits for the integer part is between scale/2 and scale. 47 | It can be either positive or negative. 48 | abs(gen_random_number()) can be used for postive numbers only. 49 | """ 50 | n: int = 0 51 | length: int = DecimalNumber.get_scale( 52 | ) + random.randrange(DecimalNumber.get_scale() // 2, DecimalNumber.get_scale()) 53 | for _ in range(0, length): 54 | n = n * 10 + random.randrange(0, 9) 55 | if random.randrange(0, 2) == 0: 56 | n = -n 57 | return DecimalNumber(n, DecimalNumber.get_scale()) 58 | 59 | def perf_decimal_number(limit1: int, limit2: int) -> None: 60 | global iteration_limit 61 | 62 | """Performance calculations of DecimalNumber class""" 63 | print(format_str.format("Scale (max. decimals):"), DecimalNumber.get_scale()) 64 | print(format_str.format("Iterations per test:"), limit1) 65 | 66 | n1 = gen_random_number() 67 | zero: bool = True 68 | while zero: 69 | n2 = gen_random_number() 70 | zero = (n2 == DecimalNumber(0)) 71 | print(format_str.format("Number 1:"), n1) 72 | print(format_str.format("Number 2:"), n2) 73 | 74 | # Addition 75 | t = get_time_ms() 76 | for _ in range(0, limit1): 77 | n3 = n1 + n2 78 | t = get_time_ms() - t 79 | print(format_str.format("Addition (n1 + n2):"), t / limit1, "ms") 80 | 81 | # Subtraction 82 | t = get_time_ms() 83 | for _ in range(0, limit1): 84 | n3 = n1 - n2 85 | t = get_time_ms() - t 86 | print(format_str.format("Subtraction (n1 - n2):"), t / limit1, "ms") 87 | 88 | # Multiplication 89 | t = get_time_ms() 90 | for _ in range(0, limit1): 91 | n3 = n1 * n2 92 | t = get_time_ms() - t 93 | print(format_str.format("Multiplication (n1 * n2):"), t / limit1, "ms") 94 | 95 | # Division 96 | t = get_time_ms() 97 | for _ in range(0, limit1): 98 | n3 = n1 / n2 99 | t = get_time_ms() - t 100 | print(format_str.format("Division (n1 / n2):"), t / limit1, "ms") 101 | 102 | # Square root 103 | n = abs(n1) 104 | t = get_time_ms() 105 | for _ in range(0, limit1): 106 | n3 = n.square_root() 107 | t = get_time_ms() - t 108 | print(format_str.format("Square root abs(n1):"), t / limit1, "ms") 109 | 110 | # Power 111 | n = DecimalNumber.pi() / 2 112 | e: int = 15 113 | t = get_time_ms() 114 | for _ in range(0, limit1): 115 | n3 = n ** e 116 | t = get_time_ms() - t 117 | print(format_str.format("Power: (pi/2) ** 15"), t / limit1, "ms") 118 | 119 | # Creation from integer 120 | n = n1._number 121 | d = n1._num_decimals 122 | t = get_time_ms() 123 | for _ in range(0, limit1): 124 | n3 = DecimalNumber(n, d) 125 | t = get_time_ms() - t 126 | print(format_str.format("DecimalNumber from int:"), t / limit1, "ms") 127 | 128 | # Creation from string 129 | n = str(n1) 130 | t = get_time_ms() 131 | for _ in range(0, limit1): 132 | n3 = DecimalNumber(n) 133 | t = get_time_ms() - t 134 | print(format_str.format("DecimalNumber from string:"), t / limit1, "ms") 135 | 136 | 137 | # From this point, the iterations are reduced 138 | print(format_str.format("Iterations per test:"), limit2) 139 | 140 | # Sine 141 | n = DecimalNumber("0.54321") 142 | t = get_time_ms() 143 | for _ in range(0, limit2): 144 | n3 = n.sin() 145 | t = get_time_ms() - t 146 | print(format_str.format("Sine: sin(" + str(n) + ")"), t / limit2, "ms") 147 | 148 | # Cosine 149 | n = DecimalNumber("0.54321") 150 | t = get_time_ms() 151 | for _ in range(0, limit2): 152 | n3 = n.sin() 153 | t = get_time_ms() - t 154 | print(format_str.format("Cosine: cos(" + str(n) + ")"), t / limit2, "ms") 155 | 156 | # Tangent 157 | n = DecimalNumber("0.54321") 158 | t = get_time_ms() 159 | for _ in range(0, limit2): 160 | n3 = n.tan() 161 | t = get_time_ms() - t 162 | print(format_str.format("Tangent: tan(" + str(n) + ")"), t / limit2, "ms") 163 | 164 | 165 | # Arcsine 166 | n = DecimalNumber("0.54321") 167 | t = get_time_ms() 168 | for _ in range(0, limit2): 169 | n3 = n.asin() 170 | t = get_time_ms() - t 171 | print(format_str.format("Arcsine: asin(" + str(n) + ")"), t / limit2, "ms") 172 | 173 | # Arccosine 174 | n = DecimalNumber("0.65432") 175 | t = get_time_ms() 176 | for _ in range(0, limit2): 177 | n3 = n.acos() 178 | t = get_time_ms() - t 179 | print(format_str.format("Arccosine: acos(" + str(n) + ")"), t / limit2, "ms") 180 | 181 | # Arctangent 182 | n = DecimalNumber("1.2345") 183 | t = get_time_ms() 184 | for _ in range(0, limit2): 185 | n3 = n.atan() 186 | t = get_time_ms() - t 187 | print(format_str.format("Arctangent: atan(" + str(n) + ")"), t / limit2, "ms") 188 | 189 | # 2-argument arctangent 190 | n = DecimalNumber("2.3456") 191 | n2 = DecimalNumber("1.2334") 192 | t = get_time_ms() 193 | for _ in range(0, limit2): 194 | n3 = DecimalNumber.atan2(n, n2) 195 | t = get_time_ms() - t 196 | print(format_str.format("Arctangent2: atan2(" + str(n) + ", " + str(n2) + ")"), t / limit2, "ms") 197 | 198 | # Exponential 199 | n = DecimalNumber("12.345") 200 | t = get_time_ms() 201 | for _ in range(0, limit2): 202 | n3 = n.exp() 203 | t = get_time_ms() - t 204 | print(format_str.format("Exponential: exp(" + str(n) + ")"), t / limit2, "ms") 205 | 206 | # Natural logarithm 207 | n = DecimalNumber("12.345") 208 | t = get_time_ms() 209 | for _ in range(0, limit2): 210 | n3 = n.exp() 211 | t = get_time_ms() - t 212 | print(format_str.format("Natural logarithm: ln(" + str(n) + ")"), t / limit2, "ms") 213 | 214 | def perf_decimal_number_pi() -> None: 215 | """Performance of the calculation of PI.""" 216 | global pi_decimals 217 | 218 | # Calculating PI 219 | # PI is precalculated up to 100 decimals. 220 | # We need to set scale > 100 to actually calculated. 221 | current_scale = DecimalNumber.get_scale() 222 | DecimalNumber.set_scale(pi_decimals) 223 | t = get_time_ms() 224 | pi = DecimalNumber.pi() 225 | t = get_time_ms() - t 226 | print(format_str.format("Pi with " + str(pi_decimals) + " decimals:"), t/1000, "s") 227 | print(pi) 228 | DecimalNumber.set_scale(current_scale) 229 | 230 | def print_title(title: str) -> None: 231 | """Auxiliary function to print a title.""" 232 | line: str = '+' + ('-' * 73) + '+' 233 | print("") 234 | print(line) 235 | print("| " + "{:<69}".format(title) + " |") 236 | print(line) 237 | 238 | 239 | print_title("SYSTEM INFORMATION") 240 | system_machine_info() 241 | 242 | print_title("PERFORMANCE WITH SCALE = 16") 243 | DecimalNumber.set_scale(16) 244 | perf_decimal_number(iteration_limit, iteration_limit // 100) 245 | 246 | print_title("PERFORMANCE WITH SCALE = 50") 247 | DecimalNumber.set_scale(50) 248 | perf_decimal_number(iteration_limit2, iteration_limit2 // 100) 249 | 250 | print_title("CALCULATING PI") 251 | perf_decimal_number_pi() 252 | -------------------------------------------------------------------------------- /tests/perf_pi_pico.txt: -------------------------------------------------------------------------------- 1 | +---------------------------------------------------------------+ 2 | | SYSTEM INFORMATION | 3 | +---------------------------------------------------------------+ 4 | Implementation name: micropython 5 | Implementation version: 1.17.0 6 | Implementation platform: rp2 7 | CPU frequency: 125 Mhz 8 | 9 | +---------------------------------------------------------------+ 10 | | PERFORMANCE WITH SCALE = 16 | 11 | +---------------------------------------------------------------+ 12 | Scale (max. decimals): 16 13 | Iterations per test: 1000 14 | Number 1: 63107666423864.1503618336011148 15 | Number 2: 21513455188640.8462728528253754 16 | Addition (n1 + n2): 1.564 ms 17 | Subtraction (n1 - n2): 1.695 ms 18 | Multiplication (n1 * n2): 0.988 ms 19 | Division (n1 / n2): 1.184 ms 20 | Square root abs(n1): 3.984 ms 21 | Power: (pi/2) ** 15 9.799 ms 22 | DecimalNumber from int: 0.378 ms 23 | DecimalNumber from string: 3.685 ms 24 | Iterations per test: 10 25 | Sine: sin(0.54321) 85.80001 ms 26 | Cosine: cos(0.54321) 86.7 ms 27 | Tangent: tan(0.54321) 206.7 ms 28 | Arcsine: asin(0.54321) 270.2 ms 29 | Arccosine: acos(0.65432) 468.8 ms 30 | Arctangent: atan(1.2345) 485.5 ms 31 | Arctangent2: atan2(1.23, 2.34) 349.2 ms 32 | Exponential: exp(12.345) 151.0 ms 33 | Natural logarithm: ln(12.345) 152.0 ms 34 | 35 | +---------------------------------------------------------------+ 36 | | PERFORMANCE WITH SCALE = 50 | 37 | +---------------------------------------------------------------+ 38 | Scale (max. decimals): 50 39 | Iterations per test: 400 40 | Number 1: 1008440840554324243744283306032711702.13787157234850455642441824006520576852374144010113 41 | Number 2: -603053031456028063831871487068.81514266625502376325215658564112814751837118860547 42 | Addition (n1 + n2): 1.9575 ms 43 | Subtraction (n1 - n2): 2.25 ms 44 | Multiplication (n1 * n2): 1.4625 ms 45 | Division (n1 / n2): 1.52 ms 46 | Square root abs(n1): 12.455 ms 47 | Power: (pi/2) ** 15 11.7425 ms 48 | DecimalNumber from int: 0.385 ms 49 | DecimalNumber from string: 10.055 ms 50 | Iterations per test: 4 51 | Sine: sin(0.54321) 186.25 ms 52 | Cosine: cos(0.54321) 184.5 ms 53 | Tangent: tan(0.54321) 398.75 ms 54 | Arcsine: asin(0.54321) 831.25 ms 55 | Arccosine: acos(0.65432) 1322.25 ms 56 | Arctangent: atan(1.2345) 1377.75 ms 57 | Arctangent2: atan2(1.23, 2.34) 893.4999 ms 58 | Exponential: exp(12.345) 281.75 ms 59 | Natural logarithm: ln(12.345) 281.75 ms 60 | 61 | +---------------------------------------------------------------+ 62 | | CALCULATING PI | 63 | +---------------------------------------------------------------+ 64 | Pi with 300 decimals: 5.862 s 65 | 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141274 66 | -------------------------------------------------------------------------------- /tests/test_decimal_number.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import random 3 | from mpy_decimal.mpy_decimal import * 4 | 5 | if sys.implementation.name == "cpython": 6 | import traceback 7 | if sys.implementation.name == "micropython": 8 | pass 9 | 10 | 11 | class TestDecimalNumber(): 12 | 13 | def __init__(self) -> None: 14 | """Initializes TestDecimalNumber, creating a counter for the tests run""" 15 | self.test_counter: int = 0 16 | 17 | def assertRaises(self, exc, function) -> None: 18 | """Method to assert that an exception is raised. 19 | It takes as parameters de Exception expected to occur an a function to be executed. 20 | """ 21 | try: 22 | function() 23 | return False # Exception not raised 24 | except Exception as e: 25 | if isinstance(e, exc): 26 | return True # Expected exception raised 27 | else: 28 | return False # Other exception raised 29 | 30 | def assertEqual(self, v1, v2, message: str) -> bool: 31 | """Assert that parameters v1 and v2 are equal. 32 | It prints the message provided as parameter in case v1 and v2 are not equal. 33 | """ 34 | if v1 == v2: 35 | return True 36 | else: 37 | print("\t" + message) 38 | return False 39 | 40 | def assertTrue(self, v, message: str) -> bool: 41 | """Assert that parameter v is True. 42 | It prints the message provided as parameter in case v is not true. 43 | """ 44 | if v: 45 | return True 46 | else: 47 | print("\t" + message) 48 | return False 49 | 50 | def assertFalse(self, v, message: str) -> bool: 51 | """Assert that parameter v is False. 52 | It prints the message provided as parameter in case v is not false. 53 | """ 54 | if not v: 55 | return True 56 | else: 57 | print("\t" + message) 58 | return False 59 | 60 | def test_init(self) -> bool: 61 | """Tests that method __init__() method works correctly. 62 | It tests that a negative number of decimals raises an Exception. 63 | Init accepts a string also, but that is tested by 'test_parse_number' 64 | and 'test_from_string'. 65 | """ 66 | self.test_counter += 1 67 | failed: bool = False 68 | # Creation of a number with negative number of decimals 69 | if not self.assertRaises(DecimalNumberExceptionMathDomainError, lambda: DecimalNumber(1, -5)): 70 | failed = True 71 | return failed 72 | 73 | def test_parse_number(self) -> bool: 74 | """Tests that method _parse_number() works correctly. 75 | This method tests if a string containing a DecimalNumber number is valid. 76 | """ 77 | self.test_counter += 1 78 | failed: bool = False 79 | # Parsing a string to create a number 80 | list_invalid = [ 81 | "1..4", "-", "1.-4", "--5", "0..", "12a345", "123v", "7O" 82 | ] 83 | list_valid = [ 84 | "0", "0.", "0.1", "0.01", "1", "12", "-0", "-0.", "-0.1", "-0.01", "-1", "-12", 85 | "12.34", "-12.34", "3.141592653589793238462643383279", "123456789012345", "98765.43210" 86 | ] 87 | for n in list_invalid: 88 | if not self.assertFalse(DecimalNumber._parse_number(n)[0], "Incorrect parsing of {0} as a number".format(n)): 89 | failed = True 90 | for n in list_valid: 91 | if not self.assertTrue(DecimalNumber._parse_number(n)[0], "Incorrect parsing of {0} as a number".format(n)): 92 | failed = True 93 | 94 | return failed 95 | 96 | def test_from_string(self) -> bool: 97 | """Tests that method _from_string() of DecimalNumber works correctly. 98 | It tests that the number is parsed as valid a the Decimal Number is created. 99 | The list of valid number should not raised and exception. 100 | The list of invalid numbers a tested for a "parse error' exception. 101 | """ 102 | self.test_counter += 1 103 | failed: bool = False 104 | # Parsing a string to create a number 105 | list_valid = [ 106 | "0", "0.", "0.1", "0.01", "1", "12", "-0", "-0.", "-0.1", "-0.01", "-1", "-12", 107 | "12.34", "-12.34", "3.141592653589793238462643383279", "123456789012345", "98765.43210" 108 | ] 109 | list_invalid = [ 110 | "1..4", "-", "1.-4", "--5", "0..", "12a345", "7O" 111 | ] 112 | for n in list_invalid: 113 | if not self.assertRaises(DecimalNumberExceptionParseError, lambda: DecimalNumber(n)): 114 | failed = True 115 | for n in list_valid: 116 | number = DecimalNumber(n) 117 | return failed 118 | 119 | def test_set_scale(self) -> bool: 120 | """Tests that method set_scale() of DecimalNumber works correctly. 121 | It tests that the scale of the class that is set is the same as the scale that can be got. 122 | """ 123 | self.test_counter += 1 124 | failed: bool = False 125 | # scale must be positive 126 | if not self.assertRaises(DecimalNumberExceptionMathDomainError, lambda: DecimalNumber.set_scale(-5)): 127 | failed = True 128 | 129 | # scale is really set 130 | current_scale: int = DecimalNumber.get_scale() 131 | DecimalNumber.set_scale(77) 132 | new_scale: int = DecimalNumber.get_scale() 133 | DecimalNumber.set_scale(current_scale) 134 | if not self.assertEqual(new_scale, 77, "Method 'set_scale': setting the scale to 77 did not work"): 135 | failed = True 136 | return failed 137 | 138 | def test_square_root(self) -> bool: 139 | """Tests that method square_root() of DecimalNumber works correctly. 140 | It processes a list of numbers with their corresponding scale. 141 | Calculates their square root using the scale specified and test the known result. 142 | Also, it checks that the square root of a negative number raises an exception. 143 | """ 144 | self.test_counter += 1 145 | failed: bool = False 146 | list_values = [ # scale, number, square root 147 | ("16", "1", "1"), 148 | ("16", "2", "1.414213562373095"), 149 | ("16", "3", "1.7320508075688772"), 150 | ("16", "4", "2"), 151 | ("16", "5", "2.2360679774997896"), 152 | ("16", "123456789", "11111.1110605555554405"), 153 | ("100", "6785678591231241027553456732298341", 154 | "82375230447211745.7368866296777842344492194107322494030818769086621615900281496437753626890475423118552055519336463298") 155 | ] 156 | for n in list_values: 157 | current_scale: int = DecimalNumber.get_scale() 158 | DecimalNumber.set_scale(int(n[0])) 159 | sr = str(DecimalNumber(n[1]).square_root()) 160 | if not self.assertEqual(sr, n[2], "Error calculating square_root({0})".format(n[1])): 161 | failed = True 162 | DecimalNumber.set_scale(current_scale) 163 | 164 | if not self.assertRaises(DecimalNumberExceptionMathDomainError, lambda: DecimalNumber(-1).square_root()): 165 | failed = True 166 | return failed 167 | 168 | def test_pi(self) -> bool: 169 | """Tests that method pi() of DecimalNumber works correctly. 170 | It tests it with scale = 100, meaning 100 decimals. 171 | Pi with up to 100 decimals is precalculated. It tests that is returned correctly. 172 | Pi with 200 decimals is calculated and checked. 173 | """ 174 | self.test_counter += 1 175 | failed: bool = False 176 | # PI with 100 decimals is already calculated. It is checked and the it is calculated with 200 decimals 177 | current_scale: int = DecimalNumber.get_scale() 178 | DecimalNumber.set_scale(100) 179 | if not self.assertEqual( 180 | str(DecimalNumber.pi()), 181 | "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679", 182 | "Value of PI with one hundred decimals is incorrect" 183 | ): 184 | failed = True 185 | DecimalNumber.set_scale(200) 186 | if not self.assertEqual( 187 | str(DecimalNumber.pi()), 188 | "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196", 189 | "Value of PI with two hundred decimals is incorrect" 190 | ): 191 | failed = True 192 | DecimalNumber.set_scale(current_scale) 193 | return failed 194 | 195 | def test_add_iadd(self) -> bool: 196 | """Tests that methods __add__() and __iadd__() of DecimalNumber work correctly. 197 | It tests a list of numbers and their known addition. 198 | """ 199 | list_numbers = [ 200 | ("0", "0", "0"), 201 | ("0", "1", "1"), 202 | ("1", "0", "1"), 203 | ("-1", "0", "-1"), 204 | ("0", "-1", "-1"), 205 | ("-1", "-1", "-2"), 206 | ("0.5", "0.123456789", "0.623456789"), 207 | ("0.987654321", "0.5", "1.487654321"), 208 | ("1000001", "0.000001", "1000001.000001"), 209 | ("1000001", "-0.000001", "1000000.999999"), 210 | ("0.000001", "-1000001", "-1000000.999999"), 211 | ("4533.04", "955.671", "5488.711"), 212 | ("-194.406", "-37893.4", "-38087.806"), 213 | ("-93670.7", "805.483", "-92865.217"), 214 | ("508.77", "156.918", "665.688"), 215 | ("710.233", "5645.94", "6356.173"), 216 | ("8249.8", "-8285.15", "-35.35"), 217 | ("309.046", "174.381", "483.427"), 218 | ("6346.2", "4780.7", "11126.9"), 219 | ("-78586.1", "-753.691", "-79339.791"), 220 | ("-25283.3", "69385.1", "44101.8"), 221 | ("55960", "-3160.14", "52799.86"), 222 | ("90505", "-99802.7", "-9297.7"), 223 | ("-9199.8", "-58138.1", "-67337.9"), 224 | ("643.213", "11178.3", "11821.513"), 225 | ("-38046.6", "242.139", "-37804.461") 226 | ] 227 | self.test_counter += 1 228 | failed: bool = False 229 | for n in list_numbers: 230 | a = DecimalNumber(n[0]) 231 | b = DecimalNumber(n[1]) 232 | c = DecimalNumber(n[2]) 233 | cc = a + b 234 | aa = DecimalNumber(n[0]) 235 | aa += b 236 | 237 | if not self.assertEqual(c, cc, "Incorrect addition for ({0} + {1})".format(str(a), str(b))): 238 | failed = True 239 | if not self.assertEqual(aa, cc, "Incorrect addition to itself for ({0} + {1})".format(str(a), str(b))): 240 | failed = True 241 | 242 | # Addition with integers 243 | if sys.implementation.name == "cpython": 244 | one_int: int = 1 245 | minus_one_int: int = -1 246 | three = DecimalNumber(3) 247 | minus_three = DecimalNumber(-3) 248 | if not self.assertEqual(one_int + three, DecimalNumber("4"), "Incorrect addition for ({0} + {1})".format(one_int, three)): 249 | failed = True 250 | if not self.assertEqual(one_int + minus_three, DecimalNumber("-2"), "Incorrect addition for ({0} + {1})".format(one_int, minus_three)): 251 | failed = True 252 | if not self.assertEqual(minus_one_int + three, DecimalNumber("2"), "Incorrect addition for ({0} + {1})".format(minus_one_int, three)): 253 | failed = True 254 | if not self.assertEqual(minus_one_int + minus_three, DecimalNumber("-4"), "Incorrect addition for ({0} + {1})".format(minus_one_int, minus_three)): 255 | failed = True 256 | 257 | return failed 258 | 259 | def test_sub_iub(self) -> bool: 260 | """Tests that methods __sub__() and __isub__() of DecimalNumber work correctly. 261 | It tests a list of numbers and their known subtraction. 262 | """ 263 | list_numbers = [ 264 | ("0", "0", "0"), 265 | ("0", "1", "-1"), 266 | ("1", "0", "1"), 267 | ("-1", "0", "-1"), 268 | ("0", "-1", "1"), 269 | ("-1", "1", "-2"), 270 | ("-1", "-1", "0"), 271 | ("0.5", "0.123456789", "0.376543211"), 272 | ("0.987654321", "-0.5", "1.487654321"), 273 | ("1000001", "-0.000001", "1000001.000001"), 274 | ("1000001", "0.000001", "1000000.999999"), 275 | ("0.000001", "1000001", "-1000000.999999"), 276 | ("4533.04", "955.671", "3577.369"), 277 | ("-194.406", "-37893.4", "37698.994"), 278 | ("-93670.7", "805.483", "-94476.183"), 279 | ("508.77", "156.918", "351.852"), 280 | ("710.233", "5645.94", "-4935.707"), 281 | ("8249.8", "-8285.15", "16534.95"), 282 | ("309.046", "174.381", "134.665"), 283 | ("6346.2", "4780.7", "1565.5"), 284 | ("-78586.1", "-753.691", "-77832.409"), 285 | ("-25283.3", "69385.1", "-94668.4"), 286 | ("55960", "-3160.14", "59120.14"), 287 | ("90505", "-99802.7", "190307.7"), 288 | ("-9199.8", "-58138.1", "48938.3"), 289 | ("643.213", "11178.3", "-10535.087"), 290 | ("-38046.6", "242.139", "-38288.739") 291 | ] 292 | self.test_counter += 1 293 | failed: bool = False 294 | for n in list_numbers: 295 | a = DecimalNumber(n[0]) 296 | b = DecimalNumber(n[1]) 297 | c = DecimalNumber(n[2]) 298 | cc = a - b 299 | aa = DecimalNumber(n[0]) 300 | aa -= b 301 | 302 | if not self.assertEqual(c, cc, "Incorrect subtruction for ({0} - {1})".format(str(a), str(b))): 303 | failed = True 304 | if not self.assertEqual(aa, cc, "Incorrect subtruction from itself for ({0} - {1})".format(str(a), str(b))): 305 | failed = True 306 | 307 | # Subtruction with integers 308 | if sys.implementation.name == "cpython": 309 | one_int: int = 1 310 | minus_one_int: int = -1 311 | three = DecimalNumber(3) 312 | minus_three = DecimalNumber(-3) 313 | if not self.assertEqual(one_int - three, DecimalNumber("-2"), "Incorrect subtruction for ({0} - {1})".format(one_int, three)): 314 | failed = True 315 | if not self.assertEqual(one_int - minus_three, DecimalNumber("4"), "Incorrect subtruction for ({0} - {1})".format(one_int, minus_three)): 316 | failed = True 317 | if not self.assertEqual(minus_one_int - three, DecimalNumber("-4"), "Incorrect subtruction for ({0} - {1})".format(minus_one_int, three)): 318 | failed = True 319 | if not self.assertEqual(minus_one_int - minus_three, DecimalNumber("2"), "Incorrect subtruction for ({0} - {1})".format(minus_one_int, minus_three)): 320 | failed = True 321 | 322 | return failed 323 | 324 | def test_mul_imul(self) -> bool: 325 | """Tests that methods __mul__() and __imul__() of DecimalNumber work correctly. 326 | It tests a list of numbers and their known multiplication. 327 | """ 328 | list_numbers = [ 329 | ("0", "13579.2468", "0"), 330 | ("1.1", "1.1", "1.21"), 331 | ("1.1", "-1.1", "-1.21"), 332 | ("-1.1", "1.1", "-1.21"), 333 | ("-1.1", "-1.1", "1.21"), 334 | ("0.000001", "123456", "0.123456"), 335 | ("123456", "0.000001", "0.123456"), 336 | ("0.0000001", "123456", "0.0123456"), 337 | ("123456", "0.0000001", "0.0123456"), 338 | ("0.00001", "123456", "1.23456"), 339 | ("123456", "0.00001", "1.23456"), 340 | ("4533.04", "955.671", "4332094.86984"), 341 | ("-194.406", "-37893.4", "7366704.3204"), 342 | ("-93670.7", "805.483", "-75450156.4481"), 343 | ("508.77", "156.918", "79835.17086"), 344 | ("710.233", "5645.94", "4009932.90402"), 345 | ("8249.8", "-8285.15", "-68350830.47"), 346 | ("309.046", "174.381", "53891.750526"), 347 | ("6346.2", "4780.7", "30339278.34"), 348 | ("-78586.1", "-753.691", "59229636.2951"), 349 | ("-25283.3", "69385.1", "-1754284298.83"), 350 | ("55960", "-3160.14", "-176841434.4"), 351 | ("90505", "-99802.7", "-9032643363.5"), 352 | ("-9199.8", "-58138.1", "534858892.38"), 353 | ("643.213", "11178.3", "7190027.8779"), 354 | ("-38046.6", "242.139", "-9212565.6774") 355 | ] 356 | self.test_counter += 1 357 | failed: bool = False 358 | for n in list_numbers: 359 | a = DecimalNumber(n[0]) 360 | b = DecimalNumber(n[1]) 361 | c = DecimalNumber(n[2]) 362 | cc = a * b 363 | aa = DecimalNumber(n[0]) 364 | aa *= b 365 | 366 | if not self.assertEqual(c, cc, "Incorrect multiplication for ({0} - {1})".format(str(a), str(b))): 367 | failed = True 368 | if not self.assertEqual(aa, cc, "Incorrect multiplication by itself for ({0} - {1})".format(str(a), str(b))): 369 | failed = True 370 | 371 | # Multiplication with integers 372 | if sys.implementation.name == "cpython": 373 | one_int: int = 1 374 | minus_one_int: int = -1 375 | three = DecimalNumber(3) 376 | minus_three = DecimalNumber(-3) 377 | if not self.assertEqual(one_int * three, DecimalNumber("3"), "Incorrect multiplication for ({0} * {1})".format(one_int, three)): 378 | failed = True 379 | if not self.assertEqual(one_int * minus_three, DecimalNumber("-3"), "Incorrect multiplication for ({0} * {1})".format(one_int, minus_three)): 380 | failed = True 381 | if not self.assertEqual(minus_one_int * three, DecimalNumber("-3"), "Incorrect multiplication for ({0} * {1})".format(minus_one_int, three)): 382 | failed = True 383 | if not self.assertEqual(minus_one_int * minus_three, DecimalNumber("3"), "Incorrect multiplication for ({0} * {1})".format(minus_one_int, minus_three)): 384 | failed = True 385 | 386 | return failed 387 | 388 | def test_truediv(self) -> bool: 389 | """Tests that method __truediv__() of DecimalNumber works correctly. 390 | It multiplies two numbers and test that the result divided by one of the numbers 391 | return the other number. 392 | """ 393 | self.test_counter += 1 394 | failed: bool = False 395 | # Creates 100 random multiplications and checks them 396 | for _ in range(0, 100): 397 | a = (random.randrange(0, 990) * 1000 + 398 | random.randrange(0, 1000)) / 1000 399 | if random.randrange(0, 2) == 0: 400 | a = -a 401 | 402 | zero: bool = True 403 | while zero: 404 | b = (random.randrange(0, 990) * 1000 + 405 | random.randrange(0, 1000)) / 1000 406 | if random.randrange(0, 2) == 0: 407 | b = -b 408 | zero = (b == 0.0) 409 | 410 | aa = DecimalNumber(str(a)) 411 | bb = DecimalNumber(str(b)) 412 | cc = aa * bb 413 | 414 | aa2 = cc / bb 415 | 416 | if not self.assertEqual(aa, aa2, "Incorrect division for ({0} / {1})".format(str(a), str(b))): 417 | failed = True 418 | 419 | list_numbers = [ 420 | ("0", "1", "0"), 421 | ("0", "-1", "0"), 422 | ("10", "0.125", "80"), 423 | ("-10", "0.125", "-80"), 424 | ("10", "-0.125", "-80"), 425 | ("-10", "-0.125", "80"), 426 | ("12345", "181", "68.2044198895027624309392265193370165745856353591160220994475138121546961325966850828729281767955801105") 427 | ] 428 | scale = DecimalNumber.get_scale() 429 | DecimalNumber.set_scale(100) 430 | self.test_counter += 1 431 | failed: bool = False 432 | for n in list_numbers: 433 | a = DecimalNumber(n[0]) 434 | b = DecimalNumber(n[1]) 435 | c = DecimalNumber(n[2]) 436 | 437 | cc = a / b 438 | 439 | if not self.assertEqual(c, cc, "Incorrect division for ({0} / {1})".format(str(a), str(b))): 440 | failed = True 441 | 442 | DecimalNumber.set_scale(scale) 443 | 444 | if not self.assertRaises(DecimalNumberExceptionDivisionByZeroError, lambda: DecimalNumber(1) / DecimalNumber(0)): 445 | failed = True 446 | 447 | # Division with integers 448 | if sys.implementation.name == "cpython": 449 | one_int: int = 1 450 | minus_one_int: int = -1 451 | five = DecimalNumber(5) 452 | minus_five = DecimalNumber(-5) 453 | if not self.assertEqual(one_int / five, DecimalNumber("0.2"), "Incorrect division for ({0} / {1})".format(one_int, five)): 454 | failed = True 455 | if not self.assertEqual(one_int / minus_five, DecimalNumber("-0.2"), "Incorrect division for ({0} / {1})".format(one_int, minus_five)): 456 | failed = True 457 | if not self.assertEqual(minus_one_int / five, DecimalNumber("-0.2"), "Incorrect division for ({0} / {1})".format(minus_one_int, five)): 458 | failed = True 459 | if not self.assertEqual(minus_one_int / minus_five, DecimalNumber("0.2"), "Incorrect division for ({0} / {1})".format(minus_one_int, minus_five)): 460 | failed = True 461 | 462 | return failed 463 | 464 | @staticmethod 465 | def self_division_zero(): 466 | a = DecimalNumber(1) 467 | a /= 0 468 | 469 | def test_itruediv(self) -> bool: 470 | """Tests that method __itruediv__() of DecimalNumber works correctly. 471 | It multiplies two numbers and test that the result divided by one of the numbers 472 | return the other number. 473 | """ 474 | self.test_counter += 1 475 | failed: bool = False 476 | # Creates 100 random divisions to itself and checks them 477 | for _ in range(0, 100): 478 | a = (random.randrange(0, 990) * 1000 + 479 | random.randrange(0, 1000)) / 1000 480 | if random.randrange(0, 2) == 0: 481 | a = -a 482 | 483 | b = (random.randrange(0, 990) * 1000 + 484 | random.randrange(0, 1000)) / 1000 485 | if random.randrange(0, 2) == 0: 486 | b = -b 487 | 488 | aa = DecimalNumber(str(a)) 489 | bb = DecimalNumber(str(b)) 490 | cc = aa * bb 491 | aa2 = cc.clone() 492 | aa2 /= bb 493 | 494 | if not self.assertEqual(aa2, aa, "Incorrect division of itself for ({0} /= {1})".format(str(a), str(b))): 495 | failed = True 496 | 497 | list_numbers = [ 498 | ("0", "1", "0"), 499 | ("0", "-1", "0"), 500 | ("10", "0.125", "80"), 501 | ("-10", "0.125", "-80"), 502 | ("10", "-0.125", "-80"), 503 | ("-10", "-0.125", "80"), 504 | ("12345", "181", "68.2044198895027624309392265193370165745856353591160220994475138121546961325966850828729281767955801105") 505 | ] 506 | scale = DecimalNumber.get_scale() 507 | DecimalNumber.set_scale(100) 508 | self.test_counter += 1 509 | failed: bool = False 510 | for n in list_numbers: 511 | a = DecimalNumber(n[0]) 512 | b = DecimalNumber(n[1]) 513 | c = DecimalNumber(n[2]) 514 | 515 | aa2 = a.clone() 516 | aa2 /= b 517 | 518 | if not self.assertEqual(c, aa2, "Incorrect division of itself for ({0} / {1})".format(str(a), str(b))): 519 | failed = True 520 | 521 | DecimalNumber.set_scale(scale) 522 | 523 | if not self.assertRaises(DecimalNumberExceptionDivisionByZeroError, lambda: TestDecimalNumber.self_division_zero()): 524 | failed = True 525 | 526 | return failed 527 | 528 | def test_neg(self) -> bool: 529 | """Tests that method __neg__() of DecimalNumber works correctly. 530 | Given a number n, it tests that -n returns the correct result. 531 | """ 532 | self.test_counter += 1 533 | failed: bool = False 534 | n = DecimalNumber(12345) 535 | n2 = -n 536 | if not self.assertTrue((n._number == n2._number and n._is_positive and not n2._is_positive), "Error on __neg__ method"): 537 | failed = True 538 | return failed 539 | 540 | def test_pos(self) -> bool: 541 | """Tests that method __pos__() of DecimalNumber works correctly. 542 | Given a number n, it tests that +n returns the correct result. 543 | """ 544 | self.test_counter += 1 545 | failed: bool = False 546 | n = DecimalNumber(-12345) 547 | n2 = +n 548 | if not self.assertTrue((n._number == n2._number and n._is_positive == n2._is_positive), "Error on __pos__ method"): 549 | failed = True 550 | return failed 551 | 552 | def test_abs(self) -> bool: 553 | """Tests that method __abs__() of DecimalNumber works correctly. 554 | It tests that abs(n), being n either negative or positive, returns +n. 555 | """ 556 | self.test_counter += 1 557 | failed: bool = False 558 | n = DecimalNumber(12345) 559 | n2 = abs(n) 560 | if not self.assertTrue((n._number == n2._number and n2._is_positive), "Error on __abs__ method"): 561 | failed = True 562 | n = DecimalNumber(-12345) 563 | n2 = abs(n) 564 | if not self.assertTrue((n._number == n2._number and n2._is_positive), "Error on __abs__ method"): 565 | failed = True 566 | return failed 567 | 568 | def test_compare(self) -> bool: 569 | """Tests that methods: 570 | __lt__(), __le__(), __eq__(), __ne__(), __ge__(), __gt__() 571 | of DecimalNumber works correctly. 572 | Two numbers are created and they are compare for: <, <=, ==, !=, >=, > 573 | """ 574 | self.test_counter += 1 575 | failed: bool = False 576 | n1 = DecimalNumber("12.3") 577 | n1b: int = 12 578 | n2 = DecimalNumber("-0.98765") 579 | 580 | if not self.assertFalse(n1 < n2, "Error evaluating {0} < {1}".format(n1, n2)): 581 | failed = True 582 | if not self.assertTrue(n2 < n1, "Error evaluating {0} < {1}".format(n2, n1)): 583 | failed = True 584 | if sys.implementation.name == "cpython": 585 | if not self.assertFalse(n1b < n2, "Error evaluating {0} < {1}".format(n1b, n2)): 586 | failed = True 587 | if not self.assertTrue(n2 < n1b, "Error evaluating {0} < {1}".format(n2, n1b)): 588 | failed = True 589 | 590 | if not self.assertFalse(n1 <= n2, "Error evaluating {0} <= {1}".format(n1, n2)): 591 | failed = True 592 | if not self.assertTrue(n2 <= n1, "Error evaluating {0} <= {1}".format(n1, n1)): 593 | failed = True 594 | if not self.assertTrue(n1 <= n1, "Error evaluating {0} <= {1}".format(n1, n1)): 595 | failed = True 596 | if not self.assertTrue(n2 <= n2, "Error evaluating {0} <= {1}".format(n2, n2)): 597 | failed = True 598 | if sys.implementation.name == "cpython": 599 | if not self.assertFalse(n1b <= n2, "Error evaluating {0} <= {1}".format(n1b, n2)): 600 | failed = True 601 | if not self.assertTrue(n2 <= n1b, "Error evaluating {0} <= {1}".format(n1, n1b)): 602 | failed = True 603 | 604 | if not self.assertFalse(n1 == n2, "Error evaluating {0} == {1}".format(n1, n2)): 605 | failed = True 606 | if not self.assertFalse(n2 == n1, "Error evaluating {0} == {1}".format(n1, n1)): 607 | failed = True 608 | if not self.assertTrue(n1 == n1, "Error evaluating {0} == {1}".format(n1, n1)): 609 | failed = True 610 | if not self.assertTrue(n2 == n2, "Error evaluating {0} == {1}".format(n2, n2)): 611 | failed = True 612 | if sys.implementation.name == "cpython": 613 | if not self.assertFalse(n1b == n2, "Error evaluating {0} == {1}".format(n1b, n2)): 614 | failed = True 615 | if not self.assertFalse(n2 == n1b, "Error evaluating {0} == {1}".format(n1, n1b)): 616 | failed = True 617 | 618 | 619 | if not self.assertTrue(n1 != n2, "Error evaluating {0} != {1}".format(n1, n2)): 620 | failed = True 621 | if not self.assertTrue(n2 != n1, "Error evaluating {0} != {1}".format(n1, n1)): 622 | failed = True 623 | if not self.assertFalse(n1 != n1, "Error evaluating {0} != {1}".format(n1, n1)): 624 | failed = True 625 | if not self.assertFalse(n2 != n2, "Error evaluating {0} != {1}".format(n2, n2)): 626 | failed = True 627 | if sys.implementation.name == "cpython": 628 | if not self.assertTrue(n1b != n2, "Error evaluating {0} != {1}".format(n1b, n2)): 629 | failed = True 630 | if not self.assertTrue(n2 != n1b, "Error evaluating {0} != {1}".format(n1, n1b)): 631 | failed = True 632 | 633 | if not self.assertTrue(n1 >= n2, "Error evaluating {0} >= {1}".format(n1, n2)): 634 | failed = True 635 | if not self.assertFalse(n2 >= n1, "Error evaluating {0} >= {1}".format(n1, n1)): 636 | failed = True 637 | if not self.assertTrue(n1 >= n1, "Error evaluating {0} >= {1}".format(n1, n1)): 638 | failed = True 639 | if not self.assertTrue(n2 >= n2, "Error evaluating {0} >= {1}".format(n2, n2)): 640 | failed = True 641 | if sys.implementation.name == "cpython": 642 | if not self.assertTrue(n1b >= n2, "Error evaluating {0} >= {1}".format(n1b, n2)): 643 | failed = True 644 | if not self.assertFalse(n2 >= n1b, "Error evaluating {0} >= {1}".format(n1, n1b)): 645 | failed = True 646 | 647 | if not self.assertTrue(n1 > n2, "Error evaluating {0} > {1}".format(n1, n2)): 648 | failed = True 649 | if not self.assertFalse(n2 > n1, "Error evaluating {0} > {1}".format(n2, n1)): 650 | failed = True 651 | if sys.implementation.name == "cpython": 652 | if not self.assertTrue(n1b > n2, "Error evaluating {0} > {1}".format(n1b, n2)): 653 | failed = True 654 | if not self.assertFalse(n2 > n1b, "Error evaluating {0} > {1}".format(n2, n1b)): 655 | failed = True 656 | 657 | return failed 658 | 659 | def test_to_string_thousands(self) -> bool: 660 | """Tests that method _to_string_thousands() of DecimalNumber works correctly. 661 | Indirectly, it checks __str__() also. 662 | It tests a list of numbers and their corresponding representation separating 663 | the thousands with ','. 664 | """ 665 | self.test_counter += 1 666 | failed: bool = False 667 | list_numbers = [ 668 | ("1", "1"), 669 | ("1.1", "1.1"), 670 | ("1.23456789012345", "1.23456789012345"), 671 | ("12.3456789012345", "12.3456789012345"), 672 | ("123.456789012345", "123.456789012345"), 673 | ("1234.56789012345", "1,234.56789012345"), 674 | ("12345.6789012345", "12,345.6789012345"), 675 | ("123456.789012345", "123,456.789012345"), 676 | ("1234567.89012345", "1,234,567.89012345"), 677 | ("12345678.9012345", "12,345,678.9012345"), 678 | ("123456789.012345", "123,456,789.012345"), 679 | ("1234567890.12345", "1,234,567,890.12345"), 680 | ] 681 | for n in list_numbers: 682 | number = DecimalNumber(n[0]) 683 | if not self.assertEqual(n[1], number.to_string_thousands(), "Error in to_string_thousands"): 684 | failed = True 685 | return failed 686 | 687 | def test_to_string_max_length(self) -> bool: 688 | """Tests that method __to_string_max_length() of DecimalNumber works correctly. 689 | That functions limits the length of the string returned. 690 | It tests a list of numbers and their known conversion. 691 | """ 692 | self.test_counter += 1 693 | failed: bool = False 694 | list_numbers = [ 695 | ("123", "123"), 696 | ("123.45", "123.45"), 697 | ("123.456789", "123.4567"), 698 | ("0.0067", "0.0067"), 699 | ("0.00678901234", "0.006789"), 700 | ("0.0000001", "0"), 701 | ("123.00001", "123"), 702 | ("12345678", "12345678"), 703 | ("1234567.8", "1234567"), 704 | ("12345678.5", "12345678"), 705 | ("123456789", "Overflow"), 706 | ("123456789.123", "Overflow"), 707 | ] 708 | for n in list_numbers: 709 | number = DecimalNumber(n[0]) 710 | if not self.assertEqual(n[1], number.to_string_max_length(8), "Error in to_string_max_length"): 711 | failed = True 712 | for n in list_numbers: 713 | number = DecimalNumber('-'+n[0]) 714 | result: str = n[1] 715 | if result != "0" and result != "Overflow": 716 | result = '-' + result 717 | if not self.assertEqual(result, number.to_string_max_length(9), "Error in to_string_max_length"): 718 | failed = True 719 | return failed 720 | 721 | def test_make_integer_comparable(self) -> bool: 722 | """Tests that method _make_integercomparable() of DecimalNumber works correctly. 723 | It tests a list of numbers and their known conversions needed for comparison. 724 | """ 725 | self.test_counter += 1 726 | failed: bool = False 727 | list_numbers = [ 728 | ("123", "123", 123, 123), 729 | ("123", "123.45", 12300, 12345), 730 | ("123.45", "123", 12345, 12300), 731 | ("-123", "123", -123, 123), 732 | ("123", "-123.45", 12300, -12345), 733 | ("-123.45", "123", -12345, 12300) 734 | ] 735 | for n in list_numbers: 736 | n1 = DecimalNumber(n[0]) 737 | n2 = DecimalNumber(n[1]) 738 | a, b = n1._make_integer_comparable(n1, n2) 739 | if not self.assertTrue( 740 | n[2] == a and n[3] == b, 741 | "Error in _make_integer_comparable for numbers {0} and {1}, {2} != {3} or {4} != {5}".format( 742 | n1, n2, n[2], a, n[3], b) 743 | ): 744 | failed = True 745 | return failed 746 | 747 | def test_reduce_to_scale(self) -> bool: 748 | """Tests that method __reduce_to_scale() of DecimalNumber works correctly. 749 | That functions reduces, if needed, the number of decimals of a number to 750 | not to be greater than the scale of DecimalNumber class. 751 | It tests a list of numbers and their known reduction. 752 | """ 753 | self.test_counter += 1 754 | failed: bool = False 755 | list_numbers = [ # reduced_scale, number, reduced_number 756 | (3, "123.456789", "123.457"), 757 | (3, "123.4577", "123.458"), 758 | (3, "123.4578", "123.458"), 759 | (3, "123.4579", "123.458"), 760 | (3, "123.4580", "123.458"), 761 | (3, "123.4581", "123.458"), 762 | (3, "123.4582", "123.458"), 763 | (3, "123.4583", "123.458"), 764 | (3, "123.4584", "123.458"), 765 | (3, "123.4585", "123.458"), 766 | (3, "123.4586", "123.459"), 767 | (3, "123.4587", "123.459"), 768 | (3, "123.4588", "123.459"), 769 | (3, "123.4589", "123.459"), 770 | (3, "123.4590", "123.459"), 771 | (3, "123.4591", "123.459"), 772 | (3, "123.4592", "123.459"), 773 | (3, "123.4593", "123.459"), 774 | (3, "123.4594", "123.459"), 775 | (3, "123.4595", "123.46"), 776 | (3, "123.4596", "123.46"), 777 | (3, "123.4597", "123.46"), 778 | (3, "123.4598", "123.46"), 779 | (3, "123.4599", "123.46"), 780 | (3, "123.4600", "123.46"), 781 | (3, "123.4601", "123.46"), 782 | (3, "123.4602", "123.46"), 783 | (3, "123.4603", "123.46"), 784 | (3, "123.4604", "123.46"), 785 | (3, "123.4605", "123.46"), 786 | (3, "123.4606", "123.461"), 787 | (3, "0.123456", "0.123"), 788 | (7, "0.0000123456", "0.0000123"), 789 | (3, "123", "123"), 790 | (3, "123.1", "123.1"), 791 | (3, "123.12", "123.12"), 792 | (3, "123.123", "123.123"), 793 | (3, "-123.4592", "-123.459"), 794 | (3, "-123.4593", "-123.459"), 795 | (3, "-123.4594", "-123.459"), 796 | (3, "-123.4595", "-123.46"), 797 | (3, "-123.4596", "-123.46"), 798 | (3, "-123.4597", "-123.46"), 799 | (7, "-0.0000123456", "-0.0000123"), 800 | (3, "-123", "-123"), 801 | (3, "-123.1", "-123.1"), 802 | (3, "-123.12", "-123.12"), 803 | (3, "-123.123", "-123.123"), 804 | (0, "1.1", "1"), 805 | (0, "1.2", "1"), 806 | (0, "1.3", "1"), 807 | (0, "1.4", "1"), 808 | (0, "1.5", "2"), 809 | (0, "1.6", "2"), 810 | (0, "1.7", "2"), 811 | (0, "1.8", "2"), 812 | (0, "1.9", "2"), 813 | (0, "2.0", "2"), 814 | (0, "2.1", "2"), 815 | (0, "2.2", "2"), 816 | (0, "2.3", "2"), 817 | (0, "2.4", "2"), 818 | (0, "2.5", "2"), 819 | (0, "2.6", "3"), 820 | (0, "2.7", "3"), 821 | (0, "2.8", "3"), 822 | (0, "2.9", "3"), 823 | (0, "3.0", "3"), 824 | (0, "3.1", "3") 825 | ] 826 | current_scale = DecimalNumber.get_scale() 827 | for n in list_numbers: 828 | s: int = n[0] 829 | DecimalNumber.set_scale(s) 830 | n1 = DecimalNumber(n[1]) 831 | if not self.assertTrue( 832 | str(n1) == n[2], 833 | "Error in _reduce_to_scale for numbers {0} and {1}; {2} != {3}".format( 834 | n[1], n[2], n1, n[2]) 835 | ): 836 | failed = True 837 | DecimalNumber.set_scale(current_scale) 838 | 839 | return failed 840 | 841 | def test_pow(self) -> bool: 842 | """Tests that method __pow__() of DecimalNumber works correctly. 843 | It tests a list of numbers and exponents by calculating number ** exponent 844 | and checking the expected result. 845 | """ 846 | self.test_counter += 1 847 | failed: bool = False 848 | list_numbers = [ # base, exponent, result 849 | ("1.1234567", 15, "5.7325134061519317"), 850 | ("-1.1234567", 15, "-5.7325134061519317"), 851 | ("1.1234567", 22, "12.9490959609285781"), 852 | ("-1.1234567", 22, "12.9490959609285781"), 853 | ("1.1234567", 29, "29.2505353804126336"), 854 | ("-1.1234567", 29, "-29.2505353804126336"), 855 | ("1.1234567", 36, "66.0736334507337083"), 856 | ("-1.1234567", 36, "66.0736334507337083"), 857 | ("1.1234567", 43, "149.2528249689879598"), 858 | ("-1.1234567", 43, "-149.2528249689879598"), 859 | ("1.1234567", 50, "337.145160600759872"), 860 | ("-1.1234567", 50, "337.145160600759872"), 861 | ("1.1234567", 57, "761.5725822283771598"), 862 | ("-1.1234567", 57, "-761.5725822283771598"), 863 | ("1.1234567", 64, "1720.3058675631219436"), 864 | ("-1.1234567", 64, "1720.3058675631219436"), 865 | ("1.1234567", 71, "3885.9753450061016559"), 866 | ("-1.1234567", 71, "-3885.9753450061016559"), 867 | ("1.1234567", 78, "8777.9764440297753385"), 868 | ("-1.1234567", 78, "8777.9764440297753385"), 869 | ("1.1234567", -1, "0.8901099615143156"), 870 | ("-1.1234567", -1, "-0.8901099615143156"), 871 | ("1.1234567", -4, "0.6277325453061032"), 872 | ("-1.1234567", -4, "0.6277325453061032"), 873 | ("1.1234567", -7, "0.4426960324835568"), 874 | ("-1.1234567", -7, "-0.4426960324835568"), 875 | ("1.1234567", -10, "0.3122026707745671"), 876 | ("-1.1234567", -10, "0.3122026707745671"), 877 | ("1.1234567", -13, "0.2201747937336509"), 878 | ("-1.1234567", -13, "-0.2201747937336509"), 879 | ("1.1234567", -16, "0.1552739432862173"), 880 | ("-1.1234567", -16, "0.1552739432862173"), 881 | ("1.1234567", -19, "0.1095038948591804"), 882 | ("-1.1234567", -19, "-0.1095038948591804"), 883 | ("1.1234567", -22, "0.0772254683274654"), 884 | ("-1.1234567", -22, "0.0772254683274654"), 885 | ("1.1234567", -25, "0.054461742808926"), 886 | ("-1.1234567", -25, "-0.054461742808926"), 887 | ("1.1234567", -28, "0.0384080730622221"), 888 | ("-1.1234567", -28, "0.0384080730622221") 889 | ] 890 | current_scale = DecimalNumber.get_scale() 891 | for n in list_numbers: 892 | DecimalNumber.set_scale(16) 893 | n1 = DecimalNumber(n[0]) 894 | e: int = n[1] 895 | r = n1 ** e 896 | if not self.assertTrue( 897 | n[2] == str(r), 898 | "Error in power (n ** e) for n = {0} and e = {1}; {2} != {3}".format( 899 | n1, e, r, n[2]) 900 | ): 901 | failed = True 902 | 903 | # 19th Mersenne prime number: 2**4253 - 1 904 | m19 = "190797007524439073807468042969529173669356994749940177394741882673528979787005053706368049835514900244303495954950709725762186311224148828811920216904542206960744666169364221195289538436845390250168663932838805192055137154390912666527533007309292687539092257043362517857366624699975402375462954490293259233303137330643531556539739921926201438606439020075174723029056838272505051571967594608350063404495977660656269020823960825567012344189908927956646011998057988548630107637380993519826582389781888135705408653045219655801758081251164080554609057468028203308718724654081055323215860189611391296030471108443146745671967766308925858547271507311563765171008318248647110097614890313562856541784154881743146033909602737947385055355960331855614540900081456378659068370317267696980001187750995491090350108417050917991562167972281070161305972518044872048331306383715094854938415738549894606070722584737978176686422134354526989443028353644037187375385397838259511833166416134323695660367676897722287918773420968982326089026150031515424165462111337527431154890666327374921446276833564519776797633875503548665093914556482031482248883127023777039667707976559857333357013727342079099064400455741830654320379350833236245819348824064783585692924881021978332974949906122664421376034687815350484991" 905 | n = DecimalNumber(2) 906 | p = n ** 4253 - DecimalNumber(1) 907 | DecimalNumber.set_scale(current_scale) 908 | if not self.assertTrue( 909 | m19 == str(p), 910 | "Error in power (n ** e). Calculated 19th Mersenne prime number is incorrect" 911 | ): 912 | print("19th Mersenne prime number:") 913 | print(m19) 914 | print(p) 915 | failed = True 916 | 917 | return failed 918 | 919 | def test_exp(self) -> bool: 920 | """Tests exp() 921 | It tests a list of numbers calculating exp(number) 922 | and checking the expected result. 923 | """ 924 | self.test_counter += 1 925 | failed: bool = False 926 | 927 | list_numbers = [ 928 | ('-3', '0.04978706836786394297934241565006177663169959218842'), 929 | ('-2.75', '0.06392786120670757270243002555795174930863409507877'), 930 | ('-2.50', '0.08208499862389879516952867446715980783780412101544'), 931 | ('-2.25', '0.10539922456186433678321768924069809726849107337728'), 932 | ('-2.00', '0.13533528323661269189399949497248440340763154590958'), 933 | ('-1.75', '0.17377394345044512668071725866637101601472085333991'), 934 | ('-1.50', '0.22313016014842982893328047076401252134217162936108'), 935 | ('-1.25', '0.28650479686019010032488542664783760279315079232825'), 936 | ('-1.00', '0.36787944117144232159552377016146086744581113103177'), 937 | ('-0.75', '0.47236655274101470713804655094326791297020357913648'), 938 | ('-0.50', '0.60653065971263342360379953499118045344191813548719'), 939 | ('-0.25', '0.77880078307140486824517026697832064729677229042614'), 940 | ('0', '1'), 941 | ('0.25', '1.28402541668774148407342056806243645833628086528146'), 942 | ('0.50', '1.64872127070012814684865078781416357165377610071015'), 943 | ('0.75', '2.1170000166126746685453698198370956101344915847024'), 944 | ('1.00', '2.71828182845904523536028747135266249775724709369996'), 945 | ('1.25', '3.49034295746184137613054602967226548265173439876235'), 946 | ('1.50', '4.48168907033806482260205546011927581900574986836967'), 947 | ('1.75', '5.75460267600573043686649970484269237092292230833653'), 948 | ('2.00', '7.38905609893065022723042746057500781318031557055185'), 949 | ('2.25', '9.48773583635852572055036904451173842377022496766239'), 950 | ('2.50', '12.18249396070347343807017595116796618318276779006316'), 951 | ('2.75', '15.64263188418817161021269804615665884503803503410762'), 952 | ('3.00', '20.08553692318766774092852965458171789698790783855415') 953 | ] 954 | 955 | current_scale: int = DecimalNumber.get_scale() 956 | DecimalNumber.set_scale(50) 957 | for n in list_numbers: 958 | e = str(DecimalNumber(n[0]).exp()) 959 | if not self.assertEqual(e, n[1], "Error calculating exp({0})".format(n[0])): 960 | failed = True 961 | DecimalNumber.set_scale(current_scale) 962 | 963 | return failed 964 | 965 | def test_ln(self) -> bool: 966 | """Tests ln() 967 | It tests a list of numbers calculating ln(number) 968 | and checking the expected result. 969 | """ 970 | self.test_counter += 1 971 | failed: bool = False 972 | 973 | list_numbers = [ 974 | ('0.25', '-1.38629436111989061883446424291635313615100026872051'), 975 | ('0.50', '-0.69314718055994530941723212145817656807550013436026'), 976 | ('0.75', '-0.28768207245178092743921900599382743150350971089776'), 977 | ('1.00', '0'), 978 | ('1.25', '0.22314355131420975576629509030983450337460108554801'), 979 | ('1.50', '0.40546510810816438197801311546434913657199042346249'), 980 | ('1.75', '0.55961578793542268627088850052682659348608446086135'), 981 | ('2.00', '0.69314718055994530941723212145817656807550013436026'), 982 | ('2.25', '0.81093021621632876395602623092869827314398084692499'), 983 | ('2.50', '0.91629073187415506518352721176801107145010121990826'), 984 | ('2.75', '1.01160091167847992522747933504877616367070658521691'), 985 | ('3.00', '1.09861228866810969139524523692252570464749055782275') 986 | ] 987 | 988 | current_scale: int = DecimalNumber.get_scale() 989 | DecimalNumber.set_scale(50) 990 | for n in list_numbers: 991 | e = str(DecimalNumber(n[0]).ln()) 992 | if not self.assertEqual(e, n[1], "Error calculating ln({0})".format(n[0])): 993 | failed = True 994 | DecimalNumber.set_scale(current_scale) 995 | 996 | # Check for ln(0) 997 | if not self.assertRaises(DecimalNumberExceptionMathDomainError, lambda: DecimalNumber(0).ln()): 998 | failed = True 999 | # Check for ln() of a negative number 1000 | if not self.assertRaises(DecimalNumberExceptionMathDomainError, lambda: DecimalNumber(-1).ln()): 1001 | failed = True 1002 | 1003 | return failed 1004 | 1005 | def test_e(self) -> bool: 1006 | """Tests that method e() works correctly. 1007 | It tests it with scale = 100, meaning 100 decimals. 1008 | 'e' with up to 100 decimals is precalculated. It tests that is returned correctly. 1009 | 'e' with 200 decimals is calculated and checked. 1010 | """ 1011 | self.test_counter += 1 1012 | failed: bool = False 1013 | # 'e' with 100 decimals is already calculated. It is checked and the it is calculated with 200 decimals 1014 | current_scale: int = DecimalNumber.get_scale() 1015 | DecimalNumber.set_scale(100) 1016 | if not self.assertEqual( 1017 | str(DecimalNumber.e()), 1018 | "2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274", 1019 | "Value of 'e' with one hundred decimals is incorrect" 1020 | ): 1021 | failed = True 1022 | DecimalNumber.set_scale(200) 1023 | if not self.assertEqual( 1024 | str(DecimalNumber.e()), 1025 | "2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642742746639193200305992181741359662904357290033429526059563073813232862794349076323382988075319525101901", 1026 | "Value of 'e' with two hundred decimals is incorrect" 1027 | ): 1028 | failed = True 1029 | DecimalNumber.set_scale(current_scale) 1030 | return failed 1031 | 1032 | def test_ln2(self) -> bool: 1033 | """Tests that method ln(2) works correctly. 1034 | It tests it with scale = 100, meaning 100 decimals. 1035 | ln(2) with up to 100 decimals is precalculated. It tests that is returned correctly. 1036 | ln(2) with 200 decimals is calculated and checked. 1037 | """ 1038 | self.test_counter += 1 1039 | failed: bool = False 1040 | # ln(2) with 100 decimals is already calculated. It is checked and the it is calculated with 200 decimals 1041 | current_scale: int = DecimalNumber.get_scale() 1042 | DecimalNumber.set_scale(100) 1043 | if not self.assertEqual( 1044 | str(DecimalNumber.ln2()), 1045 | "0.6931471805599453094172321214581765680755001343602552541206800094933936219696947156058633269964186875", 1046 | "Value of ln(2) with one hundred decimals is incorrect" 1047 | ): 1048 | failed = True 1049 | DecimalNumber.set_scale(200) 1050 | if not self.assertEqual( 1051 | str(DecimalNumber.ln2()), 1052 | "0.69314718055994530941723212145817656807550013436025525412068000949339362196969471560586332699641868754200148102057068573368552023575813055703267075163507596193072757082837143519030703862389167347112335", 1053 | "Value of ln(2) with two hundred decimals is incorrect" 1054 | ): 1055 | failed = True 1056 | DecimalNumber.set_scale(current_scale) 1057 | return failed 1058 | 1059 | def test_sin(self) -> bool: 1060 | """Tests sin() 1061 | It tests a list of numbers calculating sin(number) 1062 | and checking the expected result. 1063 | """ 1064 | self.test_counter += 1 1065 | failed: bool = False 1066 | 1067 | list_numbers = [ 1068 | ('0', '0'), 1069 | ('0.2', '0.19866933079506121545941262711838975037020672954021'), 1070 | ('0.4', '0.38941834230865049166631175679570526459306018344396'), 1071 | ('0.6', '0.56464247339503535720094544565865790710988808499415'), 1072 | ('0.8', '0.71735609089952276162717461058138536619278523779142'), 1073 | ('1.0', '0.84147098480789650665250232163029899962256306079837'), 1074 | ('1.2', '0.93203908596722634967013443549482599541507058820873'), 1075 | ('1.4', '0.98544972998846018065947457880609751735626167234737'), 1076 | ('1.6', '0.99957360304150516434211382554623417197949791475492'), 1077 | ('1.8', '0.97384763087819518653237317884335760670293947136523'), 1078 | ('2.0', '0.90929742682568169539601986591174484270225497144789'), 1079 | ('2.2', '0.80849640381959018430403691041611906515855960597558'), 1080 | ('2.4', '0.67546318055115092656577152534128337425336495789353'), 1081 | ('2.6', '0.51550137182146423525772693520936824389387858775426'), 1082 | ('2.8', '0.33498815015590491954385375271242210603030652888359'), 1083 | ('3.0', '0.14112000805986722210074480280811027984693326425227'), 1084 | ('3.2', '-0.05837414342757990913721741461909518512512509908293'), 1085 | ('3.4', '-0.25554110202683131924990242936373907581092037943434'), 1086 | ('3.6', '-0.44252044329485238426672734749269391091848782847472'), 1087 | ('3.8', '-0.61185789094271907573358608611888243771607580529324'), 1088 | ('4.0', '-0.75680249530792825137263909451182909413591288733647'), 1089 | ('4.2', '-0.87157577241358806001857709790882123480771186014449'), 1090 | ('4.4', '-0.95160207388951595403539233338038768420517733027774'), 1091 | ('4.6', '-0.99369100363346445613810465990882952642152580067382'), 1092 | ('4.8', '-0.99616460883584067178159646650363455682194459993781'), 1093 | ('5.0', '-0.9589242746631384688931544061559939733524615439646'), 1094 | ('5.2', '-0.88345465572015326467308444042180321999386557565688'), 1095 | ('5.4', '-0.77276448755598736235846978273423230445709536392994'), 1096 | ('5.6', '-0.6312666378723213114636691537166711930720248256872'), 1097 | ('5.8', '-0.46460217941375721141822652670258931885263532874474'), 1098 | ('6.0', '-0.2794154981989258728115554466118947596279948643182'), 1099 | ('6.2', '-0.08308940281749657800057928909836718528109967293846') 1100 | ] 1101 | 1102 | current_scale: int = DecimalNumber.get_scale() 1103 | DecimalNumber.set_scale(50) 1104 | for n in list_numbers: 1105 | s = str(DecimalNumber(n[0]).sin()) 1106 | if not self.assertEqual(s, n[1], "Error calculating sin({0})".format(n[0])): 1107 | failed = True 1108 | DecimalNumber.set_scale(current_scale) 1109 | 1110 | return failed 1111 | 1112 | def test_cos(self) -> bool: 1113 | """Tests cos() 1114 | It tests a list of numbers calculating cos(number) 1115 | and checking the expected result. 1116 | """ 1117 | self.test_counter += 1 1118 | failed: bool = False 1119 | 1120 | list_numbers = [ 1121 | ('0', '1'), 1122 | ('0.2', '0.98006657784124163112419651674816887739352436080657'), 1123 | ('0.4', '0.92106099400288508279852673205180161402585956931985'), 1124 | ('0.6', '0.82533561490967829724095249895537603887809103918847'), 1125 | ('0.8', '0.69670670934716542092074998164232492610178601370806'), 1126 | ('1.0', '0.54030230586813971740093660744297660373231042061792'), 1127 | ('1.2', '0.36235775447667357763837335562307602033994778557665'), 1128 | ('1.4', '0.16996714290024093861674803520364980292818392102853'), 1129 | ('1.6', '-0.02919952230128872620577046294649852444486472109385'), 1130 | ('1.8', '-0.22720209469308705531667430653058073247695158653826'), 1131 | ('2.0', '-0.41614683654714238699756822950076218976600077107554'), 1132 | ('2.2', '-0.58850111725534570852414261265492841629376036669873'), 1133 | ('2.4', '-0.73739371554124549960882222733478290843301289199228'), 1134 | ('2.6', '-0.85688875336894723379770215164520111235392263823324'), 1135 | ('2.8', '-0.94222234066865815258678811736615401246341423446825'), 1136 | ('3.0', '-0.98999249660044545727157279473126130239367909661559'), 1137 | ('3.2', '-0.99829477579475308466166072228358269144701258595166'), 1138 | ('3.4', '-0.96679819257946101428220153976569391119594442684891'), 1139 | ('3.6', '-0.89675841633414700587029172526593922995037606912552'), 1140 | ('3.8', '-0.7909677119144166999965681743507251864017333039176'), 1141 | ('4.0', '-0.65364362086361191463916818309775038142413359664622'), 1142 | ('4.2', '-0.49026082134069957765554488137713364673125516102182'), 1143 | ('4.4', '-0.30733286997841968311913974221771237118950331487701'), 1144 | ('4.6', '-0.11215252693505451742990782122918964248775134505344'), 1145 | ('4.8', '0.0874989834394465693202152576494876339574498905961'), 1146 | ('5.0', '0.28366218546322626446663917151355730833442259225222'), 1147 | ('5.2', '0.46851667130037695863909392660864570409989349454139'), 1148 | ('5.4', '0.63469287594263436240675183898074094918956609491144'), 1149 | ('5.6', '0.77556587851024979765580966215728192307775648420884'), 1150 | ('5.8', '0.88551951694131900416465810176148620005410899252792'), 1151 | ('6.0', '0.96017028665036602054565229792292440545193767921101'), 1152 | ('6.2', '0.99654209702321747513940262386926395252788462409872') 1153 | ] 1154 | 1155 | current_scale: int = DecimalNumber.get_scale() 1156 | DecimalNumber.set_scale(50) 1157 | for n in list_numbers: 1158 | c = str(DecimalNumber(n[0]).cos()) 1159 | if not self.assertEqual(c, n[1], "Error calculating cos({0})".format(n[0])): 1160 | failed = True 1161 | DecimalNumber.set_scale(current_scale) 1162 | 1163 | return failed 1164 | 1165 | def test_tan(self) -> bool: 1166 | """Tests tan() 1167 | It tests a list of numbers calculating tan(number) 1168 | and checking the expected result. 1169 | """ 1170 | self.test_counter += 1 1171 | failed: bool = False 1172 | 1173 | list_numbers = [ 1174 | ('0', '0'), 1175 | ('0.2', '0.20271003550867248332135827164753448262687566965163'), 1176 | ('0.4', '0.42279321873816176198163542716529033394198977271569'), 1177 | ('0.6', '0.68413680834169231707092541746333574524265408075678'), 1178 | ('0.8', '1.02963855705036401274636117282036528416821960677231'), 1179 | ('1.0', '1.55740772465490223050697480745836017308725077238152'), 1180 | ('1.2', '2.57215162212631893540999423603336395652940930604339'), 1181 | ('1.4', '5.79788371548288964370772024360369904599369751893968'), 1182 | ('1.6', '-34.23253273555741705801487543047619090177569941115324'), 1183 | ('1.8', '-4.28626167462806352545188895228026668020736003385825'), 1184 | ('2.0', '-2.18503986326151899164330610231368254343201774622766'), 1185 | ('2.2', '-1.3738230567687951601400367633334698743026332922337'), 1186 | ('2.4', '-0.916014289673410512730863247508105793993645549977'), 1187 | ('2.6', '-0.60159661308975872273608189269127978293417758666969'), 1188 | ('2.8', '-0.35552983165117587757735260363543503816953711960914'), 1189 | ('3.0', '-0.1425465430742778052956354105339134932260922849018'), 1190 | ('3.2', '0.05847385445957846762586774167681370216472247958237'), 1191 | ('3.4', '0.26431690086742526694892295392026530372697129599275'), 1192 | ('3.6', '0.49346672998490370894458164319649608827513552026627'), 1193 | ('3.8', '0.77355609050312607285870726589496034494206180873258'), 1194 | ('4.0', '1.15782128234957758313734241826732392311976276736714'), 1195 | ('4.2', '1.77777977450884096177623210090516337260894048546837'), 1196 | ('4.4', '3.0963237806497417682253024535177360615032120910449'), 1197 | ('4.6', '8.86017489564807389842749537123019978456538862298282'), 1198 | ('4.8', '-11.38487065424289926455670693026324154897067456117154'), 1199 | ('5.0', '-3.38051500624658563698270587944734390870956920828546'), 1200 | ('5.2', '-1.88564187751976469674264091289219213894792190226543'), 1201 | ('5.4', '-1.21754082462055611296736395481594505143979759288247'), 1202 | ('5.6', '-0.81394328368970213470079115394423478782654579724762'), 1203 | ('5.8', '-0.52466622194680001367291294422548426757702824110572'), 1204 | ('6.0', '-0.29100619138474915705369958886817554283115557091234'), 1205 | ('6.2', '-0.0833777148659287977660811118347761394190004557363') 1206 | ] 1207 | 1208 | current_scale: int = DecimalNumber.get_scale() 1209 | DecimalNumber.set_scale(50) 1210 | for n in list_numbers: 1211 | t = str(DecimalNumber(n[0]).tan()) 1212 | if not self.assertEqual(t, n[1], "Error calculating tan({0})".format(n[0])): 1213 | failed = True 1214 | DecimalNumber.set_scale(current_scale) 1215 | 1216 | return failed 1217 | 1218 | def test_asin(self) -> bool: 1219 | """Tests asin() 1220 | It tests a list of numbers calculating asin(number) 1221 | and checking the expected result. 1222 | """ 1223 | self.test_counter += 1 1224 | failed: bool = False 1225 | 1226 | list_numbers = [ 1227 | ('-1', '-1.57079632679489661923132169163975144209858469968756'), 1228 | ('-0.9', '-1.1197695149986341866866770558453996158951621864033'), 1229 | ('-0.8', '-0.92729521800161223242851246292242880405707410857224'), 1230 | ('-0.7', '-0.77539749661075306374035335271498711355578873864116'), 1231 | ('-0.6', '-0.64350110879328438680280922871732263804151059111531'), 1232 | ('-0.5', '-0.52359877559829887307710723054658381403286156656252'), 1233 | ('-0.4', '-0.41151684606748801938473789761733560485570113512703'), 1234 | ('-0.3', '-0.30469265401539750797200296122752916695456003170678'), 1235 | ('-0.2', '-0.20135792079033079145512555221762341024003808140223'), 1236 | ('-0.1', '-0.10016742116155979634552317945269331856867597222963'), 1237 | ( '0', '0'), 1238 | ( '0.1', '0.10016742116155979634552317945269331856867597222963'), 1239 | ( '0.2', '0.20135792079033079145512555221762341024003808140223'), 1240 | ( '0.3', '0.30469265401539750797200296122752916695456003170678'), 1241 | ( '0.4', '0.41151684606748801938473789761733560485570113512703'), 1242 | ( '0.5', '0.52359877559829887307710723054658381403286156656252'), 1243 | ( '0.6', '0.64350110879328438680280922871732263804151059111531'), 1244 | ( '0.7', '0.77539749661075306374035335271498711355578873864116'), 1245 | ( '0.8', '0.92729521800161223242851246292242880405707410857224'), 1246 | ( '0.9', '1.1197695149986341866866770558453996158951621864033'), 1247 | ( '1', '1.57079632679489661923132169163975144209858469968756') 1248 | ] 1249 | 1250 | current_scale: int = DecimalNumber.get_scale() 1251 | DecimalNumber.set_scale(50) 1252 | for n in list_numbers: 1253 | a = str(DecimalNumber(n[0]).asin()) 1254 | if not self.assertEqual(a, n[1], "Error calculating asin({0})".format(n[0])): 1255 | failed = True 1256 | DecimalNumber.set_scale(current_scale) 1257 | 1258 | return failed 1259 | 1260 | def test_acos(self) -> bool: 1261 | """Tests acos() 1262 | It tests a list of numbers calculating acos(number) 1263 | and checking the expected result. 1264 | """ 1265 | self.test_counter += 1 1266 | failed: bool = False 1267 | 1268 | list_numbers = [ 1269 | ('-1', '3.14159265358979323846264338327950288419716939937511'), 1270 | ('-0.9', '2.69056584179353080591799874748515105799374688609086'), 1271 | ('-0.8', '2.49809154479650885165983415456218024615565880825979'), 1272 | ('-0.7', '2.34619382340564968297167504435473855565437343832871'), 1273 | ('-0.6', '2.21429743558818100603413092035707408014009529080287'), 1274 | ('-0.5', '2.09439510239319549230842892218633525613144626625007'), 1275 | ('-0.4', '1.98231317286238463861605958925708704695428583481458'), 1276 | ('-0.3', '1.87548898081029412720332465286728060905314473139433'), 1277 | ('-0.2', '1.77215424758522741068644724385737485233862278108978'), 1278 | ('-0.1', '1.67096374795645641557684487109244476066726067191718'), 1279 | ('0', '1.57079632679489661923132169163975144209858469968755'), 1280 | ('0.1', '1.47062890563333682288579851218705812352990872745792'), 1281 | ('0.2', '1.36943840600456582777619613942212803185854661828532'), 1282 | ('0.3', '1.26610367277949911125931873041222227514402466798078'), 1283 | ('0.4', '1.15927948072740859984658379402241583724288356456053'), 1284 | ('0.5', '1.04719755119659774615421446109316762806572313312504'), 1285 | ('0.6', '0.92729521800161223242851246292242880405707410857224'), 1286 | ('0.7', '0.79539883018414355549096833892476432854279596104639'), 1287 | ('0.8', '0.64350110879328438680280922871732263804151059111531'), 1288 | ('0.9', '0.45102681179626243254464463579435182620342251328425'), 1289 | ('1.0', '0') 1290 | ] 1291 | 1292 | current_scale: int = DecimalNumber.get_scale() 1293 | DecimalNumber.set_scale(50) 1294 | for n in list_numbers: 1295 | a = str(DecimalNumber(n[0]).acos()) 1296 | if not self.assertEqual(a, n[1], "Error calculating acos({0})".format(n[0])): 1297 | failed = True 1298 | DecimalNumber.set_scale(current_scale) 1299 | 1300 | return failed 1301 | 1302 | def test_atan(self) -> bool: 1303 | """Tests atan() 1304 | It tests a list of numbers calculating atan(number) 1305 | and checking the expected result. 1306 | """ 1307 | self.test_counter += 1 1308 | failed: bool = False 1309 | 1310 | list_numbers = [ 1311 | ('-1', '-0.78539816339744830961566084581987572104929234984378'), 1312 | ('-0.9', '-0.73281510178650659164079207273428025198575567935826'), 1313 | ('-0.8', '-0.67474094222355266305652097360981361507400625484071'), 1314 | ('-0.7', '-0.61072596438920861654375887649023609381850306612883'), 1315 | ('-0.6', '-0.54041950027058415544357836460859991013514825146259'), 1316 | ('-0.5', '-0.46364760900080611621425623146121440202853705428612'), 1317 | ('-0.4', '-0.3805063771123648863035879168104331044974057136581'), 1318 | ('-0.3', '-0.29145679447786709199560462143289119350316759901207'), 1319 | ('-0.2', '-0.19739555984988075837004976519479029344758510378785'), 1320 | ('-0.1', '-0.09966865249116202737844611987802059024327832250431'), 1321 | ('0', '0'), 1322 | ('0.1', '0.09966865249116202737844611987802059024327832250431'), 1323 | ('0.2', '0.19739555984988075837004976519479029344758510378785'), 1324 | ('0.3', '0.29145679447786709199560462143289119350316759901207'), 1325 | ('0.4', '0.3805063771123648863035879168104331044974057136581'), 1326 | ('0.5', '0.46364760900080611621425623146121440202853705428612'), 1327 | ('0.6', '0.54041950027058415544357836460859991013514825146259'), 1328 | ('0.7', '0.61072596438920861654375887649023609381850306612883'), 1329 | ('0.8', '0.67474094222355266305652097360981361507400625484071'), 1330 | ('0.9', '0.73281510178650659164079207273428025198575567935826'), 1331 | ('1.0', '0.78539816339744830961566084581987572104929234984378') 1332 | ] 1333 | 1334 | current_scale: int = DecimalNumber.get_scale() 1335 | DecimalNumber.set_scale(50) 1336 | for n in list_numbers: 1337 | a = str(DecimalNumber(n[0]).atan()) 1338 | if not self.assertEqual(a, n[1], "Error calculating atan({0})".format(n[0])): 1339 | failed = True 1340 | DecimalNumber.set_scale(current_scale) 1341 | 1342 | return failed 1343 | 1344 | def test_atan2(self) -> bool: 1345 | """Tests atan2() 1346 | It tests a list of numbers calculating atan(number) 1347 | and checking the expected result. 1348 | """ 1349 | self.test_counter += 1 1350 | failed: bool = False 1351 | 1352 | list_numbers = [ 1353 | ('0', '1', '0'), 1354 | ('1', '1', '0.785398163397448309615660845819875721049292349843776455243736148076954101571552249657008706335529267'), 1355 | ('1', '0', '1.570796326794896619231321691639751442098584699687552910487472296153908203143104499314017412671058534'), 1356 | ('1', '-1', '2.356194490192344928846982537459627163147877049531329365731208444230862304714656748971026119006587801'), 1357 | ('0', '-1', '3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068'), 1358 | ('-1', '-1', '-2.356194490192344928846982537459627163147877049531329365731208444230862304714656748971026119006587801'), 1359 | ('-1', '0', '-1.570796326794896619231321691639751442098584699687552910487472296153908203143104499314017412671058534'), 1360 | ('-1', '1', '-0.785398163397448309615660845819875721049292349843776455243736148076954101571552249657008706335529267'), 1361 | ('12345678.90123456789', '23456789.0123456789', '0.4844779448504728440723160650537673828414181888506758249301810902601712799253653679799329328565153514') 1362 | ] 1363 | 1364 | current_scale: int = DecimalNumber.get_scale() 1365 | DecimalNumber.set_scale(100) 1366 | for n in list_numbers: 1367 | y = DecimalNumber(n[0]) 1368 | x = DecimalNumber(n[1]) 1369 | a = str(DecimalNumber.atan2(y, x)) 1370 | if not self.assertEqual(a, n[2], "Error calculating atan2({0}, {1})".format(n[0], n[1])): 1371 | failed = True 1372 | 1373 | if not self.assertRaises(DecimalNumberExceptionMathDomainError, lambda: DecimalNumber.atan2(0, 0)): 1374 | failed = True 1375 | 1376 | DecimalNumber.set_scale(current_scale) 1377 | 1378 | return failed 1379 | 1380 | 1381 | # def print_exception(exc: Exception) -> None: 1382 | # if sys.implementation.name == "cpython": 1383 | # traceback.print_exc() 1384 | # else: 1385 | # sys.print_exception(exc) 1386 | 1387 | 1388 | if __name__ == "__main__": 1389 | print("Testing the module 'decimal_number':") 1390 | 1391 | # Creates an object of the class for tests 1392 | test = TestDecimalNumber() 1393 | # Counts the number of tests that fail 1394 | failed_counter: int = 0 1395 | # Iterates through the items to find methods 'test_...' 1396 | for k, v in TestDecimalNumber.__dict__.items(): 1397 | if k.startswith("test_") and callable(v): 1398 | print("Testing: ", k) 1399 | failed: bool = TestDecimalNumber.__dict__[k](test) # Executes the test 1400 | if failed: 1401 | failed_counter += 1 1402 | 1403 | print("Tests run:", test.test_counter) 1404 | if failed_counter == 0: 1405 | print("Result:", "OK") 1406 | elif failed_counter == 1: 1407 | print("Result: 1 test failed") 1408 | else: 1409 | print("Result: {0} tests failed".format(failed_counter)) 1410 | --------------------------------------------------------------------------------