Instantly share code, notes, and snippets.
Last activeAugust 29, 2015 14:13
Save PythonCHB/6e9ef7732a9074d9337a to your computer and use it in GitHub Desktop.
Sample implementation of an "closeness" checker for floating point for Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| A proposed implementation for an "is_close" implementation for floating point | |
| (and complex) numbers for python. | |
| This is an attempt to implement the approach used by Boost: | |
| http://www.boost.org/doc/libs/1_34_0/libs/test/doc/components/test_tools/floating_point_comparison.html | |
| With adjustments inspired by discussion on python-ideas list the code written | |
| by Steven D'Aprano for the statistics module tests. | |
| """ | |
| defis_close(u,v,tol=1e-12,min_tol=0.0): | |
| """ | |
| Determines if two values are close to each other. Two values are close if | |
| the absolute value of their difference is less than tol times both of the | |
| values. i.e. tol is a relative difference. The result is symmetric, i.e.: | |
| is_close(u,v) == is_close(v,u) | |
| Note that if one of the values is zero, then nothing is "relatively close" | |
| to it (except zero itself). If you want a tolerance for near-zero | |
| values, then you can set min_tol to a value greater than zero. But be | |
| cautious if u an v may be very small, the min_tol could overwhelm any | |
| relative tolerance computed. | |
| :param u: one of the values | |
| :param v: the other value | |
| :param tol=1e-12: the relative tolerance | |
| NOTE: an arbitrary value for now -- how to choose good default? | |
| :param min_tol=None: The maximum absolute tolerance near zero. If one | |
| of the | |
| arguments is zero, then no value greater than | |
| zero will ever be "close" to zero on a relative | |
| scale. | |
| NOTE: -inf, inf and NaN behave as they "should" | |
| """ | |
| iftol<0ormin_tol<0: | |
| raiseValueError('error tolerances must be non-negative') | |
| ifu==v:# short-circuit exact equality | |
| returnTrue | |
| diff=abs(u-v) | |
| ## NOTE: using and, rather than if checks or max() allows it to run a bit | |
| ## faster, and lets NaN and inf do the right thing automagically. | |
| result= ((diff<=tol*abs(u))and | |
| (diff<=tol*abs(v))or | |
| (diff<=min_tol) | |
| ) | |
| returnresult | |
| if__name__=="__main__": | |
| ## some simple tests | |
| # same values had better work! | |
| exact_values_examples= [(2.0,2.0), | |
| (0.1e200,0.1e200), | |
| ] | |
| foru,vinexact_values_examples: | |
| ifnotis_close(u,v,tol=1e-12): | |
| print("FAIL: {},{} should be close".format(u,v)) | |
| # negative and positive zero | |
| assertis_close(0.0,-0.0) | |
| # very close values: | |
| close_enough_examples= [(1.000000000001,1.000000000002), | |
| (1e12+1.0,1e12+2.0), | |
| (1e13-1.0,1e13-2.0), | |
| (-1e12-1.0,-1e12-2.0), | |
| ] | |
| tol=1e-12 | |
| foru,vinclose_enough_examples: | |
| ifnotis_close(u,v,tol=tol): | |
| print("FAIL: {},{} should be close to tol: {}".format(u,v,tol)) | |
| tol=1e-14 | |
| foru,vinclose_enough_examples: | |
| ifis_close(u,v,tol=tol): | |
| print("FAIL: {},{} should be not be close to tol: {}" | |
| .format(u,v,tol)) | |
| # ## potential overflow: | |
| # ## note the boost docs talk about this, but I can't get it to overflow | |
| # ## and cause a problem. Maybe they were concerned about really large | |
| # ## values of tol??? | |
| overflow_examples= [(1e308+1e294,1e308+2e294), | |
| ] | |
| tol=1e-12 | |
| foru,vinoverflow_examples: | |
| ifnotis_close(u,v,tol=tol): | |
| print("FAIL: {},{} should be close to tol: {}".format(u,v,tol)) | |
| # ## checking close to zero | |
| zero_examples= [(0.0,1e-15), | |
| (1e-15,0.0), | |
| ] | |
| tol=1e-13 | |
| min_tol=0.0# nothing should be close | |
| foru,vinzero_examples: | |
| ifis_close(u,v,tol=tol,min_tol=min_tol): | |
| print("FAIL: {},{} should be NOT close to tol: {}".format(u,v,tol)) | |
| tol=1e-13 | |
| min_tol=1e-12# very small should hold. | |
| foru,vinzero_examples: | |
| ifnotis_close(u,v,tol=tol,min_tol=min_tol): | |
| print("FAIL: {},{} should be close to within min_tol: {}".format(u,v,min_tol)) | |
| ## NaN, etc tests | |
| nan=float('nan') | |
| inf=float('inf') | |
| neginf=-inf | |
| non_real_examples= [(nan,1.0), | |
| (1.0,nan), | |
| (nan,nan), | |
| (inf,1.0), | |
| (1e300,inf), | |
| (-inf,-1e300), | |
| (-1e305,-inf) | |
| ] | |
| tol=1e-13 | |
| min_tol=1e-12# very small should hold. | |
| foru,vinnon_real_examples: | |
| ifis_close(u,v,tol=tol,min_tol=min_tol): | |
| print("FAIL: {},{} should NOT be close to within min_tol: {}".format(u,v,min_tol)) | |
| non_real_equal= [(inf,inf), | |
| (-inf,inf), | |
| ] | |
| tol=1e-13 | |
| min_tol=1e-12# very small should hold. | |
| foru,vinnon_real_equal: | |
| ifnotis_close(u,v,tol=tol,min_tol=min_tol): | |
| print("FAIL: {},{} should be close to within min_tol: {}".format(u,v,min_tol)) | |
| # ## complex tests | |
| # ## complex numbers will be handled by: | |
| # ## is_close(x.real, y.real) and is_close(x.imag, y.imag) | |
| # ## (but i haven't written any code for that yet) | |
| # # complex_examples = [(1.0+1.0j, 1.000000000001+1.0j ), | |
| # # (1.0+1.0j, 1.0+1.000000000001j ), | |
| # # (-1.0+1.0j, -1.000000000001+1.0j ), | |
| # # (1.0-1.0j, 1.0-0.999999999999j ), | |
| # # ] | |
| # # tol = 1e-12 | |
| # # for u, v in complex_examples: | |
| # # if not is_close(u, v, tol=tol): | |
| # # print("FAIL: {},{} should be close to tol: {}".format(u, v, tol)) | |
| # # tol = 1e-13 | |
| # # for u, v in complex_examples: | |
| # # if is_close(u, v, tol=tol): | |
| # # print("FAIL: {},{} should be NOT close to tol: {}".format(u, v, tol)) |
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment