├── .clang-format ├── .gitignore ├── .readthedocs.yml ├── CHANGELOG.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── VERSION ├── appveyor.yml ├── demos ├── basic_FpBinarySwitchable_use.py ├── basic_FpBinary_use.py └── basic_fir.py ├── doc ├── Makefile ├── changelog.rst ├── conf.py ├── index.rst ├── intro.rst ├── make.bat └── objects.rst ├── release ├── __init__.py ├── building_and_releasing.rst ├── download_build.py ├── lib │ ├── __init__.py │ ├── appveyor.py │ ├── common.py │ ├── github.py │ └── pypi.py ├── release.py ├── run_build.py ├── test_all_local_build.sh └── test_all_pypi.sh ├── setup.py ├── src ├── fpbinaryarrayfuncs.c ├── fpbinaryarrayfuncs.h ├── fpbinarycommon.c ├── fpbinarycommon.h ├── fpbinarycomplexobject.c ├── fpbinarycomplexobject.h ├── fpbinaryenums.c ├── fpbinaryenums.h ├── fpbinaryglobaldoc.c ├── fpbinaryglobaldoc.h ├── fpbinarylarge.c ├── fpbinarylarge.h ├── fpbinarymodule.c ├── fpbinaryobject.c ├── fpbinaryobject.h ├── fpbinarysmall.c ├── fpbinarysmall.h ├── fpbinaryswitchable.c ├── fpbinaryswitchable.h └── fpbinaryversion.h └── tests ├── __init__.py ├── data ├── pickletest_v2_7_12_p2_linux2_64.data ├── pickletest_v3_5_2_p2_linux_64.data ├── pickletest_v3_5_2_p3_linux_64.data ├── pickletest_v3_5_2_p4_linux_64.data ├── pickletest_v3_6_5_p2_linux_64.data ├── pickletest_v3_6_5_p2_win32_32.data ├── pickletest_v3_6_5_p2_win32_64.data ├── pickletest_v3_6_5_p3_linux_64.data ├── pickletest_v3_6_5_p3_win32_32.data ├── pickletest_v3_6_5_p3_win32_64.data ├── pickletest_v3_6_5_p4_linux_64.data ├── pickletest_v3_6_5_p4_win32_32.data ├── pickletest_v3_6_5_p4_win32_64.data ├── pickletest_v3_7_5_p2_win32_64.data ├── pickletest_v3_7_5_p3_win32_64.data ├── pickletest_v3_7_5_p4_win32_64.data ├── pickletest_v3_8_0_p2_win32_64.data ├── pickletest_v3_8_0_p3_win32_64.data ├── pickletest_v3_8_0_p4_win32_64.data ├── pickletest_v3_8_0_p5_win32_64.data ├── pickletest_v3_8_2_p2_linux_64.data ├── pickletest_v3_8_2_p3_linux_64.data ├── pickletest_v3_8_2_p4_linux_64.data ├── pickletest_v3_8_2_p5_linux_64.data └── readme ├── fpbinary_mem_leak_test.py ├── fpbinary_profiling_test.py ├── porting_v3_funcs.py ├── testFpBinaryBaseClasses.py ├── testFpBinaryComplex.py ├── testFpBinarySwitchable.py ├── testFpBinaryWrapperClasses.py └── test_utils.py /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: true 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: true 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: true 29 | AfterObjCDeclaration: true 30 | AfterStruct: true 31 | AfterUnion: true 32 | BeforeCatch: true 33 | BeforeElse: true 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Custom 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | ColumnLimit: 80 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 49 | IncludeCategories: 50 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 51 | Priority: 2 52 | - Regex: '^(<|"(gtest|isl|json)/)' 53 | Priority: 3 54 | - Regex: '.*' 55 | Priority: 1 56 | IndentCaseLabels: true 57 | IndentWidth: 4 58 | IndentWrappedFunctionNames: false 59 | KeepEmptyLinesAtTheStartOfBlocks: true 60 | MacroBlockBegin: '' 61 | MacroBlockEnd: '' 62 | MaxEmptyLinesToKeep: 1 63 | NamespaceIndentation: None 64 | ObjCBlockIndentWidth: 2 65 | ObjCSpaceAfterProperty: false 66 | ObjCSpaceBeforeProtocolList: true 67 | PenaltyBreakBeforeFirstCallParameter: 19 68 | PenaltyBreakComment: 300 69 | PenaltyBreakFirstLessLess: 120 70 | PenaltyBreakString: 1000 71 | PenaltyExcessCharacter: 1000000 72 | PenaltyReturnTypeOnItsOwnLine: 60 73 | PointerAlignment: Right 74 | ReflowComments: true 75 | SortIncludes: true 76 | SpaceAfterCStyleCast: false 77 | SpaceBeforeAssignmentOperators: true 78 | SpaceBeforeParens: ControlStatements 79 | SpaceInEmptyParentheses: false 80 | SpacesBeforeTrailingComments: 1 81 | SpacesInAngles: false 82 | SpacesInContainerLiterals: true 83 | SpacesInCStyleCastParentheses: false 84 | SpacesInParentheses: false 85 | SpacesInSquareBrackets: false 86 | Standard: Cpp11 87 | TabWidth: 8 88 | UseTab: Never 89 | ... 90 | 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.so 3 | *.o 4 | *~ 5 | .python-version -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: doc/conf.py 11 | 12 | # Optionally set the version of Python and requirements required to build your docs 13 | python: 14 | version: 3.8 15 | install: 16 | - method: setuptools 17 | path: . -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | 2 | Changelog 3 | ========= 4 | 5 | `v1.5.8 `_ 6 | ---------------------------------------------------------------- 7 | 8 | **Bug fixes:** 9 | 10 | * numpy float64 type causes kernel crash in fpBinarySwitchable. `#25 `_ 11 | * Error in doc for RoundingEnum? `#26 `_ 12 | * Dead kernel when using resize method incorrectly. `#27 `_ 13 | 14 | `v1.5.7 `_ 15 | ---------------------------------------------------------------- 16 | 17 | **Bug fixes:** 18 | 19 | * Memory leak in FpBinaryComplex functions. `#23 `_ 20 | 21 | `v1.5.6 `_ 22 | ---------------------------------------------------------------- 23 | 24 | **Enhancements:** 25 | 26 | * Add support for create and resize of fixed point objects in lists and arrays. `#22 `_ 27 | 28 | 29 | `v1.5.5 `_ 30 | ---------------------------------------------------------------- 31 | 32 | **Enhancements:** 33 | 34 | * Add support for squaring fixed point numbers. `#21 `_ 35 | * Add support casting FpBinaryComplex to native complex type. `#20 `_ 36 | 37 | 38 | `v1.5.4 `_ 39 | ---------------------------------------------------------------- 40 | 41 | **Enhancements:** 42 | 43 | * Build macOS and Windows binaries for Python 3.10 and 3.11. 44 | 45 | * Note that Win32 binaries for 3.10 and later will not be supported. 46 | 47 | **Bug fixes:** 48 | 49 | * Add support for Numpy. `#9 `_ 50 | 51 | 52 | `v1.5.3 `_ 53 | ---------------------------------------------------------------- 54 | 55 | **Enhancements:** 56 | 57 | * Build macOS and Windows binaries for Python 3.9.0. 58 | 59 | 60 | `v1.5.2 `_ 61 | ---------------------------------------------------------------- 62 | 63 | **Enhancements:** 64 | 65 | * Add support for macOS installation binaries. `#19 `_ 66 | * Modify the Overflow and Rounding enums to be static classes. `#18 `_ 67 | 68 | 69 | `v1.5.1 `_ 70 | ---------------------------------------------------------------- 71 | 72 | **Enhancements:** 73 | 74 | * Add PyPi and readthedocs support. `#16 `_ 75 | 76 | **Bug fixes:** 77 | 78 | 79 | * Unpickling an FpBinary instance that was created on a larger word length machine could theoretically produce the wrong value `#15 `_ 80 | * FpBinary int() method may return incorrect value when running 32 bit python on a 64 bit machine `#14 `_ 81 | * Division on FpBinary objects may cause a crash `#13 `_ 82 | 83 | `v1.4 `_ 84 | ------------------------------------------------------------ 85 | 86 | **Bug fixes:** 87 | 88 | 89 | * Installation on Windows 10. `#12 `_ 90 | 91 | `v1.3 `_ 92 | ------------------------------------------------------------ 93 | 94 | **Enhancements:** 95 | 96 | 97 | * Added support for pickling `#10 `_ 98 | 99 | **Bug fixes:** 100 | 101 | 102 | * Copying FpBinarySwitchable objects may result in the wrong min_value or max_value. `#11 `_ 103 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/*.h 2 | include VERSION 3 | include ALPHA 4 | include README.rst -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://img.shields.io/pypi/v/fpbinary.svg 2 | :target: https://pypi.org/project/fpbinary/ 3 | 4 | .. image:: https://readthedocs.org/projects/fpbinary/badge/?version=latest 5 | :target: https://fpbinary.readthedocs.io/en/latest/ 6 | 7 | Introduction 8 | ================ 9 | 10 | fpbinary is a binary fixed point package for Python. It is written as an extension module for the CPython implementation of Python. 11 | 12 | fpbinary was created with **fast** simulation of math-intensive systems destined for digital hardware (e.g. signal processing) in mind. While Python has great support for signal processing functions, there is no offical fixed point package. Implementaions written completely in Python tend to be frustratingly slow, so fpbinary is an attempt to make fixed point simulation of large, complex hardware systems in Python viable. 13 | 14 | 15 | Documentation 16 | ============= 17 | 18 | For installation instructions, feature list and other documentation: `Read the Docs `_ 19 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.5.8 -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # ============================================================================================ 2 | # For windows-only code, we use the cmd: prefix - linux/macOS VMs ignore this. 3 | # We also use PowerShell code with the $isLinux flag to do linux-only scripting. 4 | # 5 | # Using cibuildwheel 6 | # ------------------ 7 | # Creating macOS binaries is a bit tricky because setuptools/python will use the 8 | # macOS SDK that Python was built with. SDKs are backwards compatible, so the older 9 | # SDK used the more users are catered for with one binary. The problem is that package 10 | # managers like Homebrew have python binaries that are built with the latest SDK present. 11 | # So, e.g., the Python binary from Homebrew on macOS 10.15 will use the 10.15 SDK. 12 | # Appveyor has the latest macOS, and appears to have Python binaries from Homebrew. 13 | # The binaries from Python.org, however, are built with older SDKs so as many OS versions 14 | # as possible are supported. So, we want to build our wheels with the Python.org python installs. 15 | # 16 | # cibuildwheel does this for us. It will install the Python.org binaries and do the wheel build 17 | # and then test. The tool is pretty simple and I think it is safe to rely on it. 18 | # 19 | # cibuildwheel also supports windows builds. It downloads nuget windows python binaries 20 | # and runs the wheel building in vitrualenvs. This is nicer that listing an Appveyor 21 | # job for every windows wheel, so I've decided to use cibuildwheel for windows too. 22 | # 23 | # We aren't using cibuildwheel for Linux because we only do a source distribution for linux 24 | # and cibuildwheel doesn't support NOT building wheels. It also needs a manylinux VM to run. 25 | # 26 | # Hacking the options 27 | # ------------------- 28 | # One issue with cibuildwheel is that it insists on installing the wheels it builds locally 29 | # and running the tests. There is no explicit option to specify different install behavior 30 | # (like, e.g., uploading to testpypi and installing from there). So I have abused the 31 | # CIBW_REPAIR_WHEEL_COMMAND, CIBW_BEFORE_TEST and CIBW_TEST_COMMAND options to (when desired) 32 | # upload to testpypi, uninstall the local install and install from pypi all before running 33 | # the tests. 34 | 35 | matrix: 36 | fast_finish: true 37 | 38 | environment: 39 | testpypi_pw: 40 | secure: /Wa2jI4VqSUhZRCL2vxfb9y36bGjzg+oN5Jm5g+/8jxcekWfv3YkEdF51D2+X/gT5m6rDXIYCLJixDBP4sIHsyULuhyGS9MATmakFhPT2fXvaaXiR2Rjpkt36w1m/Hv2Gz7t6xMljCbIREDJGYtNF1GCMm3a5HrtWiUl7zVI+198cLMEjfw0ZjcUBccHYaPf77boJwaxJRzwR3VBFNjbFiLOq5WyxJR8ava2yVsnMAFJUKo8w3BKVr1AMD/0bCKE 41 | 42 | matrix: 43 | 44 | 45 | # ============================================================================================ 46 | # Windows builds. 47 | # We use cibuildwheel. 48 | # 49 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 50 | CIBW_BUILD: cp311-win_amd64 cp310-win_amd64 51 | CIBW_BUILD_VERBOSITY: 2 52 | CIBW_VERSION: ">2.11" 53 | WIN_LATEST_PYTHON: "C:\\Python38-x64" 54 | PYPI_DELAY_CMD: "TIMEOUT 420" 55 | 56 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 57 | CIBW_BUILD: cp39-win32 cp39-win_amd64 cp38-win32 cp38-win_amd64 58 | CIBW_BUILD_VERBOSITY: 2 59 | CIBW_VERSION: ">2.11" 60 | WIN_LATEST_PYTHON: "C:\\Python38-x64" 61 | PYPI_DELAY_CMD: "TIMEOUT 420" 62 | 63 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 64 | CIBW_BUILD: cp37-win32 cp37-win_amd64 cp36-win32 cp36-win_amd64 65 | CIBW_BUILD_VERBOSITY: 2 66 | CIBW_VERSION: ">2.11" 67 | WIN_LATEST_PYTHON: "C:\\Python38-x64" 68 | PYPI_DELAY_CMD: "TIMEOUT 420" 69 | 70 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 71 | CIBW_BUILD: cp35-win32 cp35-win_amd64 72 | CIBW_BUILD_VERBOSITY: 2 73 | CIBW_VERSION: "==1.12.0" 74 | WIN_LATEST_PYTHON: "C:\\Python38-x64" 75 | PYPI_DELAY_CMD: "TIMEOUT 420" 76 | 77 | # ============================================================================================ 78 | # MacOS builds. 79 | # 80 | # We use cibuildwheel. 81 | # 82 | - APPVEYOR_BUILD_WORKER_IMAGE: macos-catalina 83 | CIBW_BUILD: cp311-macosx_x86_64 cp310-macosx_x86_64 84 | CIBW_BUILD_VERBOSITY: 2 85 | CIBW_VERSION: ">2.11" 86 | PYPI_DELAY_CMD: "SLEEP 420" 87 | 88 | - APPVEYOR_BUILD_WORKER_IMAGE: macos-catalina 89 | CIBW_BUILD: cp39-macosx_x86_64 cp38-macosx_x86_64 cp37-macosx_x86_64 cp36-macosx_x86_64 90 | CIBW_BUILD_VERBOSITY: 2 91 | CIBW_VERSION: ">2.11" 92 | PYPI_DELAY_CMD: "SLEEP 420" 93 | 94 | - APPVEYOR_BUILD_WORKER_IMAGE: macos-catalina 95 | CIBW_BUILD: cp27-macosx_x86_64 cp35-macosx_x86_64 96 | CIBW_BUILD_VERBOSITY: 2 97 | CIBW_VERSION: "==1.12.0" 98 | PYPI_DELAY_CMD: "SLEEP 420" 99 | 100 | 101 | # ============================================================================================ 102 | # Linux builds. 103 | # 104 | # We use the pre-installed python virtual environments to do all our Linux building/testing 105 | # in one job. 106 | - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu 107 | LINUX_VENVS: venv2.7,venv3.5,venv3.6,venv3.7,venv3.8,venv3.9,venv3.10,venv3.11 108 | 109 | 110 | # Need to make sure initial setup is done with latest python (especially for cibuildwheel) 111 | stack: python 3.9 112 | 113 | 114 | install: 115 | 116 | # Set the build name and alpha release number 117 | - ps: | 118 | 119 | $base_version = Get-Content .\VERSION 120 | $base_version | Select-Object -First 1 121 | 122 | if (Test-Path env:is_release_build) 123 | { 124 | Update-AppveyorBuild -Version "$env:APPVEYOR_REPO_BRANCH-$($base_version)rc$env:APPVEYOR_BUILD_NUMBER" 125 | } 126 | else 127 | { 128 | Update-AppveyorBuild -Version "$env:APPVEYOR_REPO_BRANCH-$($base_version)a$env:APPVEYOR_BUILD_NUMBER" 129 | 130 | <# 131 | If the package files need extra information in the version field, 132 | a file called ALPHA with the string to append must be written to 133 | the root of the project. This will occur unless the environment 134 | variable is_release_build is set. 135 | #> 136 | Set-Content -Path .\ALPHA -Value "a$env:APPVEYOR_BUILD_NUMBER" 137 | } 138 | 139 | 140 | # Need to make sure initial setup is done with latest python (especially for cibuildwheel) 141 | # This cmd is windows-specific. 142 | - cmd: "SET PATH=%WIN_LATEST_PYTHON%;%WIN_LATEST_PYTHON%\\Scripts;%PATH%" 143 | 144 | # Check that we have the expected version for Python 145 | - "python --version" 146 | 147 | # Upgrade to the latest version of pip to avoid it displaying warnings 148 | # about it being out of date on windows. 149 | - cmd: "python -m pip install --upgrade pip" 150 | 151 | - ps: | 152 | 153 | <# 154 | 155 | Set up cibuildwheel environment variables. 156 | If we are installing locally, this just requires setting the test script. 157 | If we want to install from testpypi, we need to store the built wheel in our own dir, 158 | make sure the wheel is present in the "repaired wheel" dir (because when we set our 159 | own CIBW_REPAIR_WHEEL_COMMAND, cibuildwheel still expects this file to exist but doesn't 160 | do it itself), upload to testpypi, then uninstall the local version that cibuildwheel 161 | installs just before the test command is run, install from testpypi and then finally 162 | run our actual tests. 163 | 164 | Note that the {wheel} file path placeholder isn't accessible at all parts of the flow, 165 | so that is why CIBW_REPAIR_WHEEL_COMMAND is being used. 166 | 167 | #> 168 | if (-Not $isLinux) 169 | { 170 | if (Test-Path env:install_from_pypi) 171 | { 172 | $install_version = python setup.py --version 173 | Set-Item -Path Env:CIBW_REPAIR_WHEEL_COMMAND -Value ("rm -rf upload_dir && mkdir upload_dir && cp {wheel} upload_dir && cp {wheel} {dest_dir}") 174 | Set-Item -Path Env:CIBW_BEFORE_TEST -Value ("pip install --upgrade twine && twine upload --repository testpypi -u __token__ -p " + $env:testpypi_pw + " upload_dir/* && " + $env:PYPI_DELAY_CMD) 175 | Set-Item -Path Env:CIBW_TEST_COMMAND -Value ("pip uninstall -y fpbinary && pip install -I --pre --index-url https://test.pypi.org/simple/ --no-deps fpbinary==" + $install_version + " && cd {project} && pip install numpy && pip install --prefer-binary scipy && python -m unittest discover -s tests -p testFpBinary*") 176 | } 177 | else 178 | { 179 | Set-Item -Path Env:CIBW_TEST_COMMAND -Value ("cd {project} && pip install numpy && pip install --prefer-binary scipy && python -m unittest discover -s tests -p testFpBinary*") 180 | } 181 | 182 | python -m pip install cibuildwheel$env:CIBW_VERSION 183 | } 184 | 185 | 186 | build_script: 187 | 188 | # ============================================================================================ 189 | # Linux builds. 190 | # 191 | # We only do a source distribution for Linux, so we use the latest python version virtual env 192 | # to build the source dist. 193 | # 194 | # macOS builds. 195 | # Just run cibuildwheel - it gets everything from the env variables previously set. 196 | # 197 | - ps: | 198 | if ($isLinux) 199 | { 200 | python -m pip install --upgrade wheel 201 | python setup.py sdist --formats=zip 202 | 203 | <# Linux doesn't use cibuildwheel, so need to do the wheel upload here. #> 204 | if (Test-Path env:install_from_pypi) 205 | { 206 | python -m pip install --upgrade twine 207 | python -m twine upload --repository testpypi -u __token__ -p "$env:testpypi_pw" dist/* 208 | Start-Sleep -s 420 209 | } 210 | } 211 | elseif (-Not $isWindows) 212 | { 213 | python -m cibuildwheel --output-dir dist 214 | } 215 | 216 | # ============================================================================================ 217 | # Windoes builds. 218 | # Also uses cibuildwheel to do everything, but we can't run the wheel build in PowerShell 219 | # because it errors out on compiler flag warnings (https://github.com/pypa/pip/issues/3383). 220 | # Windows cmd doesn't error out on the warnings, so using cmd for windows. 221 | - cmd: python -m cibuildwheel --output-dir dist 222 | 223 | 224 | # ============================================================================================ 225 | # All builds. 226 | # 227 | # Upload files to test pypi if requested. 228 | - ps: | 229 | 230 | 231 | 232 | test_script: 233 | 234 | # ============================================================================================ 235 | # Windows and macOS builds. 236 | # 237 | # Tests are specified in env variables for cibuildwheel to do the tests as part of the build 238 | # process. 239 | 240 | 241 | # ============================================================================================ 242 | # Linux builds. 243 | # 244 | # For each python version, we use pip to install the source distribution in the dist directory. 245 | - ps: | 246 | if ($isLinux) 247 | { 248 | $ar = $env:LINUX_VENVS.Split(",") 249 | Foreach ($e in $ar) 250 | { 251 | . $HOME/$e/bin/activate.ps1 252 | 253 | pip install numpy 254 | pip install --prefer-binary scipy 255 | 256 | if (Test-Path env:install_from_pypi) 257 | { 258 | $install_version = python setup.py --version 259 | pip install -I --pre --index-url https://test.pypi.org/simple/ --no-deps fpbinary=="$install_version" 260 | } 261 | else 262 | { 263 | pip install fpbinary --no-index --find-links dist/ 264 | } 265 | 266 | python -m unittest discover -s tests -p testFpBinary* 267 | } 268 | } 269 | 270 | 271 | artifacts: 272 | - path: dist\* 273 | 274 | -------------------------------------------------------------------------------- /demos/basic_FpBinarySwitchable_use.py: -------------------------------------------------------------------------------- 1 | import random 2 | from fpbinary import FpBinary, FpBinarySwitchable 3 | 4 | fp_mode = True 5 | 6 | 7 | # An instance can be created with fixed point and float values 8 | FpBinarySwitchable(fp_mode=fp_mode, fp_value=FpBinary(8, 8, value=6.7), float_value=6.7) 9 | 10 | 11 | # FpBinarySwitchable looks like FpBinary 12 | num1 = FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(8, 8, value=6.7), float_value=6.7) 13 | num2 = FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(8, 8, value=7.3), float_value=6.7) 14 | # {} + {} == {} {} * {} = {}'.format(num1, num2, num1 + num2, num1, num2, num1 * num2)) 15 | 16 | 17 | # FpBinarySwitchable plays well with FpBinary 18 | num1 = FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(8, 8, value=6.7), float_value=6.7) 19 | num2 = FpBinary(8, 8, value=7.3) 20 | num1, num2, num1 + num2, num1, num2, num1 * num2, type(num1 * num2) 21 | 22 | 23 | # FpBinarySwitchable is so called because you can switch operation between fixed and floating point 24 | # simply by flicking a constructor variable. Operations that aren\'t supported by normal numerical 25 | # objects (like resize) can still be called in floating point mode without an exception. 26 | 27 | for i in range(0, 2): 28 | num1 = FpBinarySwitchable(fp_mode=fp_mode, fp_value=FpBinary(8, 8, value=6.7), float_value=6.7) 29 | # fp_mode: {} Resized: {}'.format(fp_mode, num1.resize((2, 7)))) 30 | fp_mode = not fp_mode 31 | 32 | 33 | # FpBinarySwitchable can also be used to track min and max values while in floating point mode by 34 | # using the value property 35 | 36 | num1 = FpBinarySwitchable(fp_mode=False, fp_value=FpBinary(8, 8, value=0.0), float_value=0.0) 37 | for i in range(0, 5): 38 | num1.value = FpBinary(8, 8, value=random.uniform(-1.0, 1.0)) 39 | num1 40 | 41 | num1.min_value, num1.max_value 42 | 43 | 44 | # The value property can be set to other FpBinarySwitchable instances too 45 | num1 = FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(8, 8, value=0.0), float_value=0.0) 46 | num2 = FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(8, 8, value=5.0), float_value=5.0) 47 | num3 = FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(8, 8, value=0.0), float_value=0.0) 48 | num3.value = num1 + num2 49 | type(num1), type(num2), type(num3) 50 | -------------------------------------------------------------------------------- /demos/basic_FpBinary_use.py: -------------------------------------------------------------------------------- 1 | from fpbinary import FpBinary, OverflowEnum, RoundingEnum 2 | 3 | 4 | # New fixed point number from float value 5 | FpBinary(int_bits=4, frac_bits=4, signed=True, value=2.5) 6 | 7 | 8 | # New fixed point number from float value, format set by another instance 9 | FpBinary(format_inst=FpBinary(int_bits=4, frac_bits=4, signed=True, value=2.5), 10 | signed=True, value=2.5) 11 | 12 | 13 | # The (int_bits, frac_bits) tuple can be accessed via the format property 14 | FpBinary(4, 6).format 15 | 16 | 17 | # If you are dealing with massive numbers such that the float value doesn't have 18 | # enough precision, you can define your value as a bit field (type int) 19 | fp_massive = FpBinary(int_bits=128, frac_bits=128, signed=True, bit_field=(1 << 255) + 1) 20 | 21 | # The default string rep uses floats, but the str_ex() method outputs a string that 22 | # preserves precision 23 | fp_massive 24 | fp_massive.str_ex() 25 | 26 | # The format can have negative int_bits or negative frac_bits so long as the total 27 | # bits is greater than 0. The meaning of a negative format value is that number of 28 | # bits is removed from the other format part, but the extreme bit position remains 29 | # the same. E.g.: 30 | # 31 | # a format of (-2, 10) gives 8 fractional bits, 0 integer bits and the fractional 32 | # bit positions are 3 to 10 (inclusive). 33 | FpBinary(int_bits=-2, frac_bits=10, signed=False, value=2.0**-10) 34 | 35 | # This should saturate to the maximum representable value for an 8 bit unsigned 36 | # with fractional bit positions 3 to 10: 37 | FpBinary(int_bits=-2, frac_bits=10, signed=False, value=2.0**-1) 38 | 39 | # Similarly, negative frac_bits produces an instance with only integer bit positions: 40 | FpBinary(int_bits=10, frac_bits=-2, signed=False, value=2.0**9) 41 | 42 | # Basic math ops are supported, and overflow is guaranteed to NOT happen 43 | FpBinary(4, 4, value=2.5) + FpBinary(4, 4, value=2.5) 44 | FpBinary(4, 4, value=2.5) - FpBinary(4, 4, value=2.5) 45 | FpBinary(4, 4, value=2.5) * FpBinary(4, 4, value=2.5) 46 | FpBinary(4, 4, value=2.5) / FpBinary(4, 4, value=2.5) 47 | 48 | # Negative int/frac bits instances use the same rules of arithmetic, overflow, rounding 49 | # and resultant format as ordinary formats. 50 | add_res = FpBinary(-3, 8, value=0.03125) + FpBinary(9, -2, value=12.0) 51 | add_res 52 | add_res.format 53 | 54 | mul_res = FpBinary(-3, 8, value=0.03125) * FpBinary(9, -2, value=12.0) 55 | mul_res 56 | mul_res.format 57 | 58 | 59 | # Resizing numbers after operations can be done either by format tuple 60 | # or taking the format from another FpBinary instance 61 | mul_res = FpBinary(4, 4, value=2.5) * FpBinary(4, 4, value=2.5) 62 | mul_res.resize((7, 8), overflow_mode=OverflowEnum.sat, round_mode=RoundingEnum.near_pos_inf) 63 | mul_res.resize(format=FpBinary(2, 4), overflow_mode=OverflowEnum.sat, round_mode=RoundingEnum.near_pos_inf) 64 | 65 | 66 | # FpBinary objects play well with other types of numbers - formats will be inferred from the number's value 67 | FpBinary(4, 4, value=2.5) * 3.5 68 | FpBinary(4, 4, value=2.5) * 3 69 | 70 | 71 | # Unsigned data is also supported 72 | num1 = FpBinary(4, 2, signed=False, value=3.75) 73 | num2 = FpBinary(4, 2, signed=False, value=4.0) 74 | num1 - num2 75 | 76 | 77 | # FpBinary instances can be sliced and indexed (useful for things like NCOs.') 78 | # The result of slicing is another FpBinary number whose value is the bits interpreted as an int. 79 | # bin() is also useful. 80 | num1 = FpBinary(4, 4, value=5.5) 81 | num1, bin(num1), num1, num1[3:1], num1, bin(num1[3:1]), num1, bin(num1[:]), num1, num1[3] 82 | 83 | 84 | # The bits_to_signed() method takes all the bits in a FpBinary and interprets them as a signed integer 85 | num1 = FpBinary(4, 4, value=-3.5) 86 | num1, bin(num1), num1.bits_to_signed() 87 | 88 | 89 | # The __index__ method takes all the bits in a FpBinary and interprets them as an unsigned integer 90 | num1, bin(num1), num1.__index__() 91 | -------------------------------------------------------------------------------- /demos/basic_fir.py: -------------------------------------------------------------------------------- 1 | import math 2 | from collections import deque 3 | from fpbinary import FpBinary, OverflowEnum, RoundingEnum 4 | 5 | # ====================================== 6 | # Basic FIR Demo 7 | data_path_len = 32 8 | 9 | # Just a delay filter 10 | filter_len = 11 11 | filter_coeffs = int(filter_len / 2) * [FpBinary(2, 30, value=0.0)] + \ 12 | [FpBinary(2, 30, value=1.0)] + \ 13 | int(filter_len / 2) * [FpBinary(2, 30, value=0.0)] 14 | 15 | # 32 bit data 16 | delay_line = deque(filter_len * [FpBinary(2, 30, value=0.0)], filter_len) 17 | 18 | def fir_next_sample(sample): 19 | # Allow for hardware specs 20 | guard_bits = int(math.log(filter_len, 2)) + 1 21 | adder_bits = 48 22 | adder_in_format = (2 + guard_bits, adder_bits - 2 - guard_bits) 23 | output_format = (2 + guard_bits, 32 - 2 - guard_bits) 24 | 25 | # Ensure data is correct format 26 | delay_line.appendleft(sample.resize(format=delay_line[0], overflow_mode=OverflowEnum.wrap)) 27 | 28 | accum = 0.0 29 | for tap, coeff in zip(delay_line, filter_coeffs): 30 | accum += (tap * coeff).resize(adder_in_format, round_mode=RoundingEnum.direct_neg_inf) 31 | 32 | return accum.resize(output_format) 33 | 34 | 35 | if __name__ == '__main__': 36 | for i in range(-10, 10): 37 | next_sample = fir_next_sample(FpBinary(int_bits=2, frac_bits=30, value=i / 10.0)) 38 | print(next_sample, next_sample.format) 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /doc/changelog.rst: -------------------------------------------------------------------------------- 1 | 2 | .. include:: ../CHANGELOG.rst -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'fpbinary' 21 | copyright = '2022, smlgit' 22 | author = 'smlgit' 23 | 24 | master_doc = 'index' 25 | 26 | 27 | # -- General configuration --------------------------------------------------- 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.napoleon' 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # List of patterns, relative to source directory, that match files and 41 | # directories to ignore when looking for source files. 42 | # This pattern also affects html_static_path and html_extra_path. 43 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 44 | 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | html_theme = 'sphinx_rtd_theme' 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | html_static_path = ['_static'] 57 | 58 | 59 | # -- Extension configuration ------------------------------------------------- 60 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | fpbinary 2 | ======== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents 7 | 8 | intro 9 | objects 10 | changelog -------------------------------------------------------------------------------- /doc/intro.rst: -------------------------------------------------------------------------------- 1 | 2 | Introduction 3 | ================ 4 | 5 | fpbinary is a binary fixed point package for Python. It is written as an extension module for the CPython implementation of Python. 6 | 7 | fpbinary was created with **fast** simulation of math-intensive systems destined for digital hardware (e.g. signal processing) in mind. While Python has great support for signal processing functions, there is no offical fixed point package. Implementaions written completely in Python tend to be frustratingly slow, so fpbinary is an attempt to make fixed point simulation of large, complex hardware systems in Python viable. 8 | 9 | 10 | Features 11 | -------- 12 | 13 | 14 | * Arbitrary precision representation of real numbers (including a ``str_ex()`` method for string display of high precision numbers) 15 | * Complex number object 16 | * Definable integer and fractional bit formats 17 | * Fixed point basic math operations 18 | * Bitwise/index/slice operations 19 | * Gracefully plays with int and float Python types 20 | * Switch between fixed and floating point math without changing code 21 | * Tracking of min/max values for prototyping 22 | * Follows the VHDL fixed point library conventions (relatively) closely 23 | * Objects are picklable (only pickle protocols >= 2 are supported) 24 | * The fpbinary objects **ARE NOT** subclassable at present 25 | 26 | 27 | Installation 28 | ------------ 29 | 30 | The easiest way is to install using the pip utility. If you don't already have pip installed, first follow the instructions at `pip Installation `_ . Then follow the instructions for your operating system below. 31 | 32 | 33 | Linux 34 | ^^^^^ 35 | 36 | pip will install fpbinary on Linux using a source distribution. This does require a C99 compliant compiler on your system, which your distribution is likely to already have. If not, install something like gcc. Then, to install fpbinary: 37 | 38 | .. code-block:: bash 39 | 40 | pip install fpbinary 41 | 42 | If you come across a missing Python.h error, you may need to install the python3-dev package: 43 | 44 | .. code-block:: bash 45 | 46 | sudo apt-get update 47 | sudo apt-get install python3-dev 48 | 49 | 50 | Windows 51 | ^^^^^^^ 52 | 53 | fpbinary is tested on Windows 10 and binaries are produced on PyPi. Only python versions >= 3.5 are supported for Windows. Install via: 54 | 55 | .. code-block:: bash 56 | 57 | pip install fpbinary 58 | 59 | macOS 60 | ^^^^^ 61 | 62 | fpbinary is currently tested on macOS 10.14 (Mojave) but binaries are produced on PyPi that *should* install on macOS 10.9 and newer. Install via: 63 | 64 | .. code-block:: bash 65 | 66 | pip install fpbinary 67 | 68 | These binaries only support 64 bit systems. If you are running 32 bit or an older OS, the above command will still probably work as long as you have a C99 compliant compiler installed. 69 | 70 | Going Bush 71 | ^^^^^^^^^^ 72 | 73 | If you want to test the very latest source code or for some other reason you want to install directly from the repository, you can install via: 74 | 75 | .. code-block:: bash 76 | 77 | pip install git+https://github.com/smlgit/fpbinary.git 78 | 79 | or: 80 | 81 | .. code-block:: bash 82 | 83 | git clone https://github.com/smlgit/fpbinary.git 84 | 85 | cd fpbinary 86 | python setup install 87 | 88 | 89 | Use 90 | --- 91 | 92 | fpbinary provides three main objects - ``FpBinary``, ``FpBinaryComplex`` and ``FpBinarySwitchable``. The best way to learn how they work is to read the help documentation: 93 | 94 | .. code-block:: python 95 | 96 | from fpbinary import FpBinary, FpBinarySwitchable 97 | help(FpBinary) 98 | help(FpBinaryComplex) 99 | help(FpBinarySwitchable) 100 | 101 | This documentation is also avaliable at `Read the Docs `_. There are also some useful `demos `_. 102 | 103 | Below is a very brief introduction to the objects. 104 | 105 | Objects 106 | ^^^^^^^ 107 | 108 | ``FpBinary`` 109 | ~~~~~~~~~~~~~~~~ 110 | 111 | This object represents a real number with a specified number of integer and fractional bits. 112 | 113 | Some basic usage: 114 | 115 | .. code-block:: python 116 | 117 | >>> fp_num = FpBinary(int_bits=4, frac_bits=4, signed=True, value=2.5) 118 | >>> fp_num 119 | 2.5 120 | >>> fp_num.format 121 | (4, 4) 122 | >>> fp_num * 2.0 123 | 5.0 124 | >>> fp_num.resize((1,4)) 125 | 0.5 126 | 127 | ``FpBinarySwitchable`` 128 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 129 | 130 | This object is intended to be used in simulation code where the user wants to switch between fixed and floating point math operation. It allows a simulation to be coded with fixed point method calls (like resize()) but to be run in floating point mode at the flick of a constructor switch: 131 | 132 | .. code-block:: python 133 | 134 | def dsp_sim(fp_mode): 135 | num1 = FpBinarySwitchable(fp_mode=fp_mode, fp_value=FpBinary(8, 8, value=6.7), float_value=6.7) 136 | num2 = FpBinary(16, 16, value=0.005) 137 | 138 | num3 = (num1 * num2).resize((8, 8), overflow_mode=OverflowEnum.wrap, 139 | rounding_mode=RoundingEnum.direct_neg_inf) 140 | 141 | # Do other stuff... 142 | 143 | return num3 144 | 145 | ``FpBinarySwitchable`` also provides the ``value`` property. This can be set to fixed or floating point objects (depending on the mode) and the min and max values over the lifetime of the object are tracked. This gives the designer an indication of the required fixed point format of the various data points in their design: 146 | 147 | .. code-block:: python 148 | 149 | 150 | inp = FpBinarySwitchable(fp_mode=fp_mode, fp_value=FpBinary(8, 8, value=0.0), float_value=0.0) 151 | scaled = FpBinarySwitchable(fp_mode=fp_mode, fp_value=FpBinary(16, 16, value=0.0), float_value=0.0) 152 | 153 | def some_dsp_next_sample(sample): 154 | inp.value = sample.resize(format_inst=inp) 155 | scaled.value = inp * scale_factor 156 | 157 | # .... 158 | return val 159 | 160 | def run(fp_mode): 161 | # call some_dsp_next_sample a whole heap 162 | 163 | return inp.min_value, inp.max_value, scaled.min_value, scaled.max_value 164 | 165 | 166 | Development 167 | ----------- 168 | 169 | fpbinary was designed from the point of view of a frustrated FPGA designer. Speed and useability for FPGA/hardware engineers drove the implementation decisions. 170 | 171 | Architecture 172 | ^^^^^^^^^^^^ 173 | 174 | The main objects are ``FpBinary``, ``FpBinaryComplex`` and ``FpBinarySwitchable``. 175 | 176 | ``FpBinary`` 177 | ~~~~~~~~~~~~~~~~ 178 | 179 | Is a wrapper that is composed of an instance of one of two "base" types: 180 | 181 | 182 | * ``_FpBinarySmall``\ : this object uses native c types for the underlying value representation. This makes operations as fast as possible. However, use of this object is limited by the machine bit width. 183 | * ``_FpBinaryLarge``\ : this object uses Python integer objects (\ ``PyLong``\ ) for the value representation. This allows arbitrary length data at the expense of slower operation (and messier c code...). 184 | 185 | The purpose of ``FpBinary`` is to work out whether the faster object can be used for a representation or operation result and select between the two base types accordingly. It also must make sure the operands of binary/ternary operations are cast to the base type before forwarding them on. 186 | 187 | This architecture does make the code and maintenance more complicated and it is questionable whether it is worth having the small object at all. Basic profiling does suggest that ``FpBinary`` is faster than ``_FpBinaryLarge`` on its own (for numbers < 64 bits), but the difference isn't that big (and is mostly in the creation of objects rather than the math ops). 188 | 189 | ``FpBinaryComplex`` 190 | ~~~~~~~~~~~~~~~~~~~~~ 191 | 192 | This object holds two FpBinary objects, one for the real part and one for the imaginary part of a fixed point complex number. 193 | 194 | Only signed numbers are supported. 195 | 196 | ``FpBinarySwitchable`` 197 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 198 | 199 | The point of this object is to allow a designer to write their simulation code assuming fixed point operation (i.e. with fixed point operations like the ``resize()``\ ) method, but to be able to force floating point math with the flick of a switch. Not only is the normal workflow to try out a design using floating point math first, it is also incredibly handy to be able to switch back and forth through the entire project lifecycle. 200 | 201 | ``FpBinarySwitchable`` is composed of a ``FpBinary`` instance and a native c ``double`` variable. Which variable is actually used when an operation is invoked on the instance is dictated by the ``fp_mode``\ , which is defined at constructor time. The ``FpBinarySwitchable`` code is essentially tasked with casting the other operand to the right type (fixed or float) and then forwarding on the underlying operation to the right object. 202 | 203 | ``FpBinarySwitchable`` also implements a ``value`` property that can be used to set the composition instances. This makes it easy for the designer to write simulation code with apparently mutable data points. The advantage of this is that minimum and maximum values can be tracked during the lifetime of the object - Matlab implements a similar feature for its fixed point variables and it allows the user to get an idea for the required format of each data point. ``FpBinarySwitchable`` implements this functionality with simple logic in the property setter method. Note that this is only done when in floating point mode. 204 | 205 | ``FpBinarySwitchable`` is designed to "look" like an ``FpBinary`` object, at least when it makes sense to flick the operation to float mode. So I have implemented ``resize()`` operations (no change in float mode) and shifting operations (mult/div by powers of 2) as well as the math operations. But index/slice and bitwise operations have **not** been implemented. 206 | 207 | Coding Notes 208 | ^^^^^^^^^^^^ 209 | 210 | 211 | * Direct calls to object methods (like the tp_as_number methods) was favoured over the c api PyNumber abstract methods where possible. This was done for speed. 212 | * Similarly, a private interface was created for ``_FpBinarySmall`` and ``_FpBinaryLarge`` to implement so ``FpBinary`` could access them without going through the abstract call functions (that use string methods for lookup). This provided some type of polymorphism via the ``fpbinary_base_t`` type placed at the top of the base's object definitions. 213 | 214 | 215 | Enhancements 216 | ------------ 217 | 218 | 219 | * [ ] Possibly jettison the base class architecure and use ``_FpBinaryLarge`` as the main object. 220 | * [ ] Add global contexts that allows the user to define "hardware" specifications so inputs and outputs to math operations can be resized automatically (i.e. without the need for explicit resizing code). 221 | * [ ] Add more advanced operations like log, exp, sin/cos/tan. I have stopped short of doing these thus far because a user may rather simulate the actual hardware implementation (e.g. a lookup table would likely be used for sin). Having said that, a convienient fpbinary method should give the same result. 222 | * [ ] Allow ``FpBinary`` and ``FpBinarySwitchable`` to be subclassable. Would require some basic changes to (mostly) ``FpBinarySwitchable`` to use the abstract methods from the Python Numeric/Sequence interfaces rather than direct accessing via the type memory. Might reduce speed slightly. 223 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /doc/objects.rst: -------------------------------------------------------------------------------- 1 | fpbinary Objects 2 | ================ 3 | 4 | FpBinary 5 | -------- 6 | .. autoclass:: fpbinary.FpBinary 7 | :members: 8 | 9 | FpBinaryComplex 10 | --------------- 11 | .. autoclass:: fpbinary.FpBinaryComplex 12 | :members: 13 | 14 | FpBinarySwitchable 15 | ------------------ 16 | .. autoclass:: fpbinary.FpBinarySwitchable 17 | :members: 18 | 19 | OverflowEnum 20 | ------------ 21 | .. autoclass:: fpbinary.OverflowEnum 22 | 23 | RoundingEnum 24 | ------------ 25 | .. autoclass:: fpbinary.RoundingEnum 26 | 27 | FpBinaryOverflowException 28 | ------------------------- 29 | .. autoclass:: fpbinary.FpBinaryOverflowException 30 | 31 | 32 | -------------------------------------------------------------------------------- /release/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/release/__init__.py -------------------------------------------------------------------------------- /release/building_and_releasing.rst: -------------------------------------------------------------------------------- 1 | Building 2 | ======== 3 | 4 | fpbinary is simple to build locally using: 5 | 6 | .. code-block:: bash 7 | 8 | python setup.py install 9 | 10 | setuptools picks up the right compiler/linker tools for the local machine and runs them and places the object file in the right place. 11 | 12 | Because Linux systems usually have a compiler pre installed, I only do a source distribution for Linux. It should just work. 13 | 14 | Building for platforms like Windows and macOS is more complicated because a user ideally wouldn't need to find and install a compiler. 15 | This is particularly important for Windows because Visual Studio is the recommeded compiler to use but it is 16 | pretty bloaty and not installed by default. Also, you need the correct version of the VS C++ compiler. 17 | 18 | The chosen solution is to use an online service to build and test the fpbinary library. We are currently using Appveyor. 19 | `This `_ page is very helpful re the compiler versions required for 20 | windows builds. I currently only build for >= Python 3.5 because the required older versions of the VS C++ compiler don't 21 | fully support C99. Specifically, it craps out on use of `` and the dot notation in struct definitions. We 22 | could use the MinGW compiler for older versions, but that is a low priority job. 23 | 24 | Appveyor 25 | -------- 26 | 27 | I have an account at ``_ with username *smlgit*. Appveyor builds Linux, Windows and macOS. 28 | 29 | Appveyor has a decent REST API that the python scripts in /release use to start builds and download archive files. 30 | 31 | A Github authorization has been granted to Appveyor for fpbinary for web hook access. This can be revoked at either 32 | Github or Appveyor. 33 | 34 | An Appveyor API token has been generated on the *smlgit->My Profile->API Keys* page for access via the REST API. A new 35 | token can be generated at any time. 36 | 37 | The Appveyor build is controlled via the appveyor.yml file in the repo root. Note that web hooks **are** required for this 38 | to work. The only setting that is really used *outside* the .yml file or REST API is the 'Next build number' setting in 39 | fpbinary settings on the Appveyor website. This number is untouched and is incremented every build automatically. 40 | 41 | Powershell scripting is used in the .yml file whenever flow needs to be controlled (Powershell is the only 42 | scripting available in the .yml for **every** OS. 'cmd:' lines run Windows-only commands. 43 | 44 | The cibuildwheel tool is used for building windows and macOS wheels because it installs the Python runtimes needed for 45 | supported of older macOS versions and it was cleaner to also use it for Windows. Because we only do a source dist for 46 | Linux, it has its own code. 47 | 48 | The run_build.py makes it easy to start a build for a branch and download the resultant files. It can also upload the 49 | files to test.pypi for testing the release. A build can also be started in the web interface, but you'll need to set the 50 | branch in the project settings page. An environment variable also needs to be set if it is a release build, so best to 51 | stick with the script. 52 | 53 | Build names and release numbers 54 | ------------------------------- 55 | 56 | It seems that semantic versioning has become big and most python packages seem to use it. So I decided to use 57 | a MAJOR.MINOR.PATCH format for released packages. The patch number is incremented after each release (in the 58 | VERSION file manually). So packages that are released to the public have no build number information in them. 59 | 60 | I want development builds to have build number info in them for easy distinguishing. But note that PEP 440 61 | is rather strict on the formats of public packages, which test.pypi adheres to. I could have used 62 | MAJOR.MINOR.PATCH.BUILD_NUMBER but that makes the final release the oldest version among same patch builds. 63 | The only possible alternative was to use the MAJOR.MINOR.PATCHaBUILD_NUMER 'alpha' format. 64 | 65 | A package version number is set in setup.py in the setup function: 66 | 67 | .. code-block:: python 68 | 69 | setup(name='fpbinary', version=version, ...) 70 | 71 | The build number comes from Appveyor. In order to get this information into setup.py, the build process can 72 | write to a file called 'ALPHA' in the root of fpbinary. The text written will be appended to the MAJOR.MINOR.PATCH 73 | obtained from the 'VERSION' file. See setup.py. Note that this is only done on a development (non-release) 74 | build. On a release build, the `is_release_build` env variable must be created (and set to anything) in Appveyor. 75 | This is easy to do via the REST API. This variable prevents the Appveyor yaml from creating the 'ALPHA' file. 76 | 77 | There is also code in appveyor.yml that sets the build name (or version as Appveyor calls it). I've made all 78 | build names have the build number in it. See the comment in release.lib.common.get_version_from_appveyor_build_name() 79 | for the build name format I use. 80 | 81 | The run_build.py script has an option to upload binaries/source to test.pypi and install from there during 82 | the Appveyor build. Note that while test.pypi.org is useful for testing a new package, it (ridiculously) 83 | prevents you from uploading the same file (by name) twice for a given version (forever - you can't even remove a 84 | release and redo it). But it does recognize the alpha release format, so it isn't a problem unless a 85 | *release* build is uploaded and you need to re-do it. In that case, you would have to increment the PATCH number 86 | if you wanted to upload to the test server with another release build. This applies also to the online pypi.org server 87 | too. So best to do a non-release build with upload to the test server, make sure it works and then do the same with the 88 | release build (without any code changes). 89 | 90 | In order to upload to test.pypi, you need a password token. The Appveyor yaml file has an *encrypted* version of the 91 | test.pypi API token. The mechanism used is called a 'secure variable'. A secure variable can be created in the 92 | *Account->Encrypt YAML* page. 93 | 94 | Test PYPI and PYPI 95 | ------------------ 96 | 97 | test.pypi.org is a clone of pypi.org and allows you to test your release file uploads before uploading to the real 98 | pypi.org server. I have an account with username *smlgit*. Access isn't via an API. We just use `twine` to upload to the 99 | server as we would to pypi.org, but with a url option. Both test.pypi.org and pypi.org require an API token that must be 100 | passed in via the `twine -p` option. The tokens are generated on the *Settings* page of the fpbinary project. Note that 101 | test.pypi.org and pypi.org have their own distinct settings, they don't share these credentials. 102 | 103 | Github 104 | ------ 105 | 106 | Aside from the usual operations on Github, the only thing we do on Github for release is to generate a tag for a public 107 | release. This is done in the release.py script via the Github API. This needs an access token. I've used a *Personal 108 | Access Token*. These are lightweight tokens that are used for authorization over the https REST API. They are generated 109 | on the *Profile Picture->Settings->Developer Settings->Personal Access Tokens* page. 110 | 111 | Security File 112 | ------------- 113 | 114 | For the release/build scripts to get access to the various online services, a file named *security.json* file must be 115 | placed in the release directory with the following structure: 116 | 117 | .. code-block:: python 118 | 119 | { 120 | "APPVEYOR": {"token": , "account": "smlgit"}, 121 | "TESTPYPI": {"token": }, 122 | "PYPI": {"token": }, 123 | "GITHUB": {"token": } 124 | } 125 | 126 | Releasing 127 | ========= 128 | 129 | A release comprises the following steps: 130 | 131 | #. Make sure the MAJOR.MINOR.PATCH version is set correctly in the VERSION file 132 | #. Make sure CHANGELOG.rst is updated with the new release enhancements and fixes 133 | #. Do a non-release build, preferably with installation from test.pypi.org, so that tests are run on all possible platforms: 134 | 135 | .. code-block:: bash 136 | 137 | python release/run_build.py --install-from-testpypi 138 | 139 | #. If everything passes, do the same as a release build: 140 | 141 | .. code-block:: bash 142 | 143 | python release/run_build.py --install-from-testpypi --release 144 | 145 | The only difference here is that the version in the packaging info won't have an `a` appendage. 146 | 147 | #. If everything is ok, run the release script: 148 | 149 | .. code-block:: bash 150 | 151 | python release/release.py 152 | 153 | This should download the package files from Appveyor, upload them to pypi.org, run the `test_all_pypi.sh` script 154 | (which just tests that you can install the package in virtualenvs of each pyenv version on the local PC) and finally 155 | creates a release tag on the commit that Appveyor reports the build was done on. 156 | 157 | .. note:: 158 | 159 | * This process can be done on any branch but we should be releasing off of the master branch 160 | 161 | Documentation 162 | ============= 163 | 164 | readthedocs 165 | ----------- 166 | 167 | We have a readthedocs account under the username *smlgit*. 168 | 169 | .rst files are used to add documentation for the library that readthedocs can build to produce 170 | ``_. The .rst files are in the `doc` directory. The html files that will be 171 | produced by readthedocs can be generated locally (after `sphinx` and its `napoleon` and `autodoc` extensions are 172 | installed) by running: 173 | 174 | .. code-block:: bash 175 | 176 | make html 177 | 178 | in the `doc` directory. The resultant html will located in the `_build/html` directory. 179 | 180 | Currently, readthedocs will automatically re-build the docs whenever there is a commit on the master branch. This 181 | requires Github web hook access to readthedocs. 182 | 183 | help() docstrings 184 | ----------------- 185 | 186 | The main documentation for fpbinary is written in the source code itself via docstrings. The format follows the numpy 187 | documentation standard (as far as possible) (see ``_ ). 188 | 189 | Not only does this give the user access to the documentation in the interpreter shell, but rst/html files are 190 | generated from the interpreter help via the `sphinx` tool and the `autodoc` extension. This is done by readthedocs to 191 | produce the page ``_. 192 | 193 | 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /release/download_build.py: -------------------------------------------------------------------------------- 1 | import argparse, logging, os 2 | from lib.appveyor import get_build_from_name, get_last_build, download_build_artifacts 3 | from lib.pypi import upload_to_pypi_server 4 | from lib.common import get_appveyor_security, get_testpypi_security 5 | 6 | 7 | def main(): 8 | logging.basicConfig() 9 | logging.root.setLevel(logging.INFO) 10 | 11 | parser = argparse.ArgumentParser(description='Downloads the artifacts of a build on Appveyor.') 12 | parser.add_argument('buildname', type=str, 13 | help='The build name (or \'version\' as Appveyor calls it) to download. ' 14 | 'Enter a branch name if you want to download the latest build from that branch.') 15 | parser.add_argument('outputdir', type=str, 16 | help='The artifacts of the build will be downloaded to this directory.') 17 | parser.add_argument('--upload-dest', type=str, default=None, choices=['pypi', 'testpypi'], 18 | help='Specify to upload the artifacts to the respective package server.') 19 | args = parser.parse_args() 20 | 21 | security_dict = get_appveyor_security() 22 | 23 | build_name = None 24 | 25 | # First see if the build name exists as a build 26 | try: 27 | logging.info('Looking for build {}'.format(args.buildname)) 28 | get_build_from_name(security_dict['token'], security_dict['account'], 29 | 'fpbinary', args.buildname) 30 | build_name = args.buildname 31 | except: 32 | # Then try a branch 33 | try: 34 | logging.info(('Looking for branch {}'.format(args.buildname))) 35 | build = get_last_build(security_dict['token'], security_dict['account'], 36 | 'fpbinary', branch=args.buildname) 37 | build_name = build['version'] 38 | logging.info('Found build {} on branch {}'.format(build['version'], args.buildname)) 39 | except: 40 | pass 41 | 42 | 43 | if build_name is None: 44 | logging.error('Failed to find a build or branch called {}'.format(args.buildname)) 45 | exit(1) 46 | 47 | logging.info('Downloading build {} ...'.format(build_name)) 48 | download_build_artifacts(security_dict['token'], security_dict['account'], 49 | 'fpbinary', os.path.abspath(args.outputdir), build_name=build_name) 50 | 51 | if args.upload_dest is not None: 52 | upload_to_pypi_server(get_testpypi_security()['token'], args.outputdir, server=args.upload_dest) 53 | 54 | 55 | if __name__ == '__main__': 56 | main() -------------------------------------------------------------------------------- /release/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/release/lib/__init__.py -------------------------------------------------------------------------------- /release/lib/appveyor.py: -------------------------------------------------------------------------------- 1 | import logging, ntpath, os 2 | import requests 3 | from . import common 4 | import time 5 | 6 | 7 | appveyor_api_url_prefix = 'https://ci.appveyor.com/api/' 8 | 9 | 10 | # Need to allow for uploading/installing from test pypi which seems to require 11 | # a 10 minute delay PER artifact produced. Allow for about 10 artifacts, round 12 | # to 2 hours. 13 | max_time_to_wait_for_build_secs = 7200 14 | 15 | 16 | def _appveyor_rest_api_build_headers(auth_token, customer_headers={}, 17 | content_type='json'): 18 | customer_headers['Authorization'] = 'Bearer {}'.format(auth_token) 19 | 20 | if content_type == 'json': 21 | customer_headers['Content-type'] = 'application/json' 22 | 23 | return customer_headers 24 | 25 | 26 | def _appveyor_get_full_url(relative_url): 27 | return common.concat_urls([appveyor_api_url_prefix, relative_url]) 28 | 29 | 30 | def _get_projects(auth_token): 31 | r = requests.get(_appveyor_get_full_url('projects'), 32 | headers=_appveyor_rest_api_build_headers(auth_token)) 33 | r.raise_for_status() 34 | return r.json() 35 | 36 | 37 | def get_project_id(auth_token, project_name): 38 | js = _get_projects(auth_token) 39 | for project in js: 40 | if project['slug'] == project_name: 41 | return project['projectId'] 42 | 43 | return None 44 | 45 | 46 | def get_last_build(auth_token, account_name, project_name, branch=None): 47 | """ 48 | Returns the 'build' dict of the last build on branch. If branch is None, 49 | will return the 'build' dict of the last build on the project. 50 | """ 51 | 52 | if branch is None: 53 | request_url = 'projects/{}/{}'.format(account_name, project_name) 54 | else: 55 | request_url = 'projects/{}/{}/branch/{}'.format( 56 | account_name, project_name, branch) 57 | 58 | r = requests.get( 59 | _appveyor_get_full_url(request_url), 60 | headers=_appveyor_rest_api_build_headers(auth_token)) 61 | 62 | if r.status_code == 404: 63 | # Not found error 64 | # Assume there has been no build for this branch and return None. 65 | return None 66 | 67 | r.raise_for_status() 68 | return r.json()['build'] 69 | 70 | 71 | def get_build_from_id(auth_token, account_name, project_name, build_id): 72 | """ 73 | Returns the 'build' dict of the build with id build_id. 74 | """ 75 | 76 | # Note that this url doesn't seem to be documented. I found it at 77 | # https://help.appveyor.com/discussions/problems/17648-build-api-seems-to-have-changed-to-buildsbuildid 78 | r = requests.get( 79 | _appveyor_get_full_url('projects/{}/{}/builds/{}'.format( 80 | account_name, project_name, build_id)), 81 | headers=_appveyor_rest_api_build_headers(auth_token)) 82 | r.raise_for_status() 83 | return r.json()['build'] 84 | 85 | 86 | def get_build_from_name(auth_token, account_name, project_name, build_name): 87 | """ 88 | Returns the 'build' dict of the build with build_name. 89 | """ 90 | r = requests.get( 91 | _appveyor_get_full_url('projects/{}/{}/build/{}'.format( 92 | account_name, project_name, build_name)), 93 | headers=_appveyor_rest_api_build_headers(auth_token)) 94 | r.raise_for_status() 95 | return r.json()['build'] 96 | 97 | 98 | def set_build_number(auth_token, account_name, project_name, build_number): 99 | """ 100 | Sets the next build number to build_number. 101 | """ 102 | r = requests.put( 103 | _appveyor_get_full_url('projects/{}/{}/settings/build-number'.format( 104 | account_name, project_name)), 105 | headers=_appveyor_rest_api_build_headers(auth_token), 106 | json={'nextBuildNumber': build_number}) 107 | r.raise_for_status() 108 | 109 | 110 | def get_artifact_list(auth_token, job_id): 111 | """ 112 | Returns a list of artifacts dicts for job_id. 113 | """ 114 | r = requests.get( 115 | _appveyor_get_full_url('buildjobs/{}/artifacts'.format(job_id)), 116 | headers=_appveyor_rest_api_build_headers(auth_token)) 117 | r.raise_for_status() 118 | return r.json() 119 | 120 | 121 | def download_job_artifacts(auth_token, job_id, output_dir_path): 122 | artifacts = get_artifact_list(auth_token, job_id) 123 | 124 | for artifact in artifacts: 125 | # Artifact file names can have directory paths in them, including 126 | # windows format, so need to use ntpath instead of os.path 127 | filename = ntpath.basename(artifact['fileName']) 128 | 129 | logging.info('Downloading file {} to {}'.format( 130 | filename, output_dir_path 131 | )) 132 | 133 | r = requests.get( 134 | _appveyor_get_full_url('buildjobs/{}/artifacts/{}'.format( 135 | job_id, artifact['fileName'])), 136 | headers=_appveyor_rest_api_build_headers(auth_token, content_type='content')) 137 | r.raise_for_status() 138 | 139 | with open(os.path.join(output_dir_path, filename), mode='wb') as f: 140 | f.write(r.content) 141 | 142 | 143 | def download_build_artifacts(auth_token, account_name, project_name, output_dir_path, 144 | build_name=None, build_id=None): 145 | """ 146 | If neither build_name or build_id is specified, the latest build will be downloaded. 147 | """ 148 | 149 | if build_name is not None: 150 | build = get_build_from_name(auth_token, account_name, project_name, build_name) 151 | elif build_id is not None: 152 | build = get_build_from_id(auth_token, account_name, project_name, build_id) 153 | else: 154 | build = get_last_build(auth_token, account_name, project_name) 155 | 156 | if not os.path.exists(output_dir_path): 157 | os.mkdir(output_dir_path) 158 | 159 | # Clear download directory 160 | [os.remove(os.path.join(output_dir_path, p)) for p in os.listdir(output_dir_path)] 161 | 162 | for job in build['jobs']: 163 | download_job_artifacts(auth_token, job['jobId'], output_dir_path) 164 | 165 | 166 | def start_build(auth_token, account_name, project_name, branch, 167 | install_from_testpypi=False, 168 | is_release_build=False, wait_for_finish=False): 169 | """ 170 | If successfully started, returns a tuple with (success, build id). 171 | Else, returns None. 172 | """ 173 | 174 | logging.info('Starting build...') 175 | 176 | data = {'accountName': account_name, 'projectSlug': project_name, 'branch': branch, 177 | 'environmentVariables': {}} 178 | 179 | if is_release_build is True: 180 | data['environmentVariables']['is_release_build'] = '1' 181 | 182 | if install_from_testpypi is True: 183 | data['environmentVariables']['install_from_pypi'] = '1' 184 | 185 | r = requests.post( 186 | _appveyor_get_full_url('builds'), 187 | headers=_appveyor_rest_api_build_headers(auth_token), json=data) 188 | r.raise_for_status() 189 | build_id = r.json()['buildId'] 190 | 191 | accum_secs = 0 192 | sleep_secs = 30 193 | 194 | logging.info('Started build: {}'.format(build_id)) 195 | 196 | if wait_for_finish: 197 | logging.info('Waiting for build to finish...') 198 | while accum_secs < max_time_to_wait_for_build_secs: 199 | 200 | time.sleep(sleep_secs) 201 | accum_secs += sleep_secs 202 | 203 | build = get_build_from_id(auth_token, account_name, project_name, build_id) 204 | 205 | if 'finished' in build: 206 | logging.info('Build {} completed with status {}'.format( 207 | build['version'], build['status'])) 208 | return build_is_successful(build), build_id 209 | 210 | logging.error('Build completion timed out') 211 | 212 | return build_id 213 | 214 | 215 | def build_is_successful(appveyor_build_dict): 216 | return appveyor_build_dict['status'] == 'success' 217 | 218 | 219 | def get_build_summary(appveyor_build_dict): 220 | return 'Name: {}\nBranch: {}\nCommit: {}\nFinished: {}\nStatus: {}'.format( 221 | appveyor_build_dict['version'], appveyor_build_dict['branch'], 222 | appveyor_build_dict['commitId'], appveyor_build_dict['finished'], 223 | appveyor_build_dict['status'] 224 | ) 225 | -------------------------------------------------------------------------------- /release/lib/common.py: -------------------------------------------------------------------------------- 1 | import os, json, re 2 | 3 | 4 | def get_version_from_appveyor_build_name(build_name): 5 | # For release builds, build name is: 6 | # branch_name-rc 7 | # and need to return: 8 | # 9 | # 10 | # For non-release builds, build name is: 11 | # branch_name-a 12 | # and need to return: 13 | # a 14 | 15 | if re.match('^.+-[0-9].[0-9].[0-9]rc[0-9]+$', build_name): 16 | # Release build 17 | return re.sub('rc[0-9]+$', '', re.sub('^.+-', '', build_name)) 18 | elif re.match('^.+-[0-9].[0-9].[0-9]a[0-9]+$', build_name): 19 | # Non-release build 20 | return re.sub('^.+-', '', build_name) 21 | 22 | return None 23 | 24 | 25 | def get_security_config_file_path(): 26 | if (os.path.exists('security.json')): 27 | return os.path.abspath('security.json') 28 | 29 | if (os.path.exists('release/security.json')): 30 | return os.path.abspath('release/security.json') 31 | 32 | return None 33 | 34 | 35 | def get_appveyor_security(): 36 | security_file = get_security_config_file_path() 37 | 38 | if security_file is not None: 39 | with open(security_file, 'r') as f: 40 | config = json.load(f) 41 | 42 | return config['APPVEYOR'] 43 | 44 | return None 45 | 46 | 47 | def get_testpypi_security(): 48 | security_file = get_security_config_file_path() 49 | 50 | if security_file is not None: 51 | with open(security_file, 'r') as f: 52 | config = json.load(f) 53 | 54 | return config['TESTPYPI'] 55 | 56 | return None 57 | 58 | 59 | def get_pypi_security(): 60 | security_file = get_security_config_file_path() 61 | 62 | if security_file is not None: 63 | with open(security_file, 'r') as f: 64 | config = json.load(f) 65 | 66 | return config['PYPI'] 67 | 68 | return None 69 | 70 | 71 | def get_github_security(): 72 | security_file = get_security_config_file_path() 73 | 74 | if security_file is not None: 75 | with open(security_file, 'r') as f: 76 | config = json.load(f) 77 | 78 | return config['GITHUB'] 79 | 80 | return None 81 | 82 | 83 | def concat_urls(urls): 84 | 85 | result = '' 86 | 87 | for url in urls: 88 | result += url.strip('/') + '/' 89 | 90 | return result.strip('/') 91 | -------------------------------------------------------------------------------- /release/lib/github.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from . import common 3 | 4 | 5 | def _github_rest_api_build_headers(auth_token, customer_headers={}): 6 | customer_headers['Authorization'] = 'token {}'.format(auth_token) 7 | customer_headers['Content-type'] = 'application/json' 8 | return customer_headers 9 | 10 | 11 | def _github_get_full_url(relative_url): 12 | return common.concat_urls(['https://api.github.com', relative_url]) 13 | 14 | 15 | def create_light_tag(auth_token, owner, repository, sha, tagname): 16 | r = requests.post( 17 | _github_get_full_url('/repos/{}/{}/git/refs'.format(owner, repository)), 18 | headers=_github_rest_api_build_headers(auth_token), 19 | json={'ref': 'refs/tags/{}'.format(tagname),'sha': sha}) 20 | 21 | # 201 == "created" 22 | if r.status_code != 201: 23 | r.raise_for_status() 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /release/lib/pypi.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | 4 | def upload_to_pypi_server(password, files_dir, server='testpypi'): 5 | """ 6 | :param server: 'testpypi' or 'pypi' 7 | :return: True if successful 8 | """ 9 | result = subprocess.run(['python', '-m', 'twine', 'upload', '--repository', 10 | '{}'.format(server), '-u', '__token__', '-p', 11 | '{}'.format(password), '--verbose', 12 | '{}/*'.format(files_dir.rstrip('/'))], 13 | check=True) 14 | 15 | return result == 0 16 | 17 | -------------------------------------------------------------------------------- /release/release.py: -------------------------------------------------------------------------------- 1 | import argparse, logging, os, time, subprocess 2 | from lib.appveyor import download_build_artifacts, get_build_from_name, build_is_successful, get_build_summary 3 | from lib.pypi import upload_to_pypi_server 4 | from lib.github import create_light_tag 5 | from lib.common import get_appveyor_security, get_pypi_security, get_github_security, get_version_from_appveyor_build_name 6 | 7 | 8 | default_output_dir = os.path.abspath('download_dir') 9 | pypi_upload_delay_minutes = 15 10 | 11 | 12 | def main(): 13 | logging.basicConfig() 14 | logging.root.setLevel(logging.INFO) 15 | 16 | parser = argparse.ArgumentParser(description='Will download build artifacts from Appveyor, ' 17 | 'upload to PyPi, run install tests and add tag to GitHub.') 18 | parser.add_argument('buildname', type=str, 19 | help='The build name/version of the Appveyor build to release.') 20 | 21 | args = parser.parse_args() 22 | 23 | version = get_version_from_appveyor_build_name(args.buildname) 24 | if version is None: 25 | raise ValueError('Couldn\'t parse build name {} to a valid version.'.format(args.buildname)) 26 | 27 | appveyor_security_dict = get_appveyor_security() 28 | pypi_security_dict = get_pypi_security() 29 | github_security_dict = get_github_security() 30 | 31 | build_dict = get_build_from_name(appveyor_security_dict['token'], appveyor_security_dict['account'], 32 | 'fpbinary', args.buildname) 33 | 34 | if build_is_successful(build_dict) is False: 35 | raise ValueError('The build for version {} was not successful. Let\'s not release it...'.format( 36 | args.buildname 37 | )) 38 | 39 | logging.info('Releasing build:') 40 | logging.info(get_build_summary(build_dict)) 41 | logging.info('Version: {}'.format(version)) 42 | ans = input('Are you sure you want to continue? (y/n)') 43 | 44 | if ans != 'y': 45 | return 46 | 47 | 48 | # ==================================================== 49 | # Download from Appveyor 50 | # ==================================================== 51 | 52 | download_dir_abs = os.path.abspath(default_output_dir) 53 | download_build_artifacts(appveyor_security_dict['token'], appveyor_security_dict['account'], 54 | 'fpbinary', download_dir_abs, build_name=args.buildname) 55 | 56 | 57 | # ==================================================== 58 | # Upload to PyPi 59 | # ==================================================== 60 | 61 | logging.info('Uploading to PyPi...') 62 | upload_to_pypi_server(pypi_security_dict['token'], download_dir_abs, server='pypi') 63 | 64 | logging.info('Sleeping to give PyPi time to get sorted...') 65 | time.sleep(pypi_upload_delay_minutes * 60) 66 | 67 | 68 | # ==================================================== 69 | # Run test scripts 70 | # ==================================================== 71 | 72 | test_script_abs = os.path.abspath('release/test_all_pypi.sh') 73 | subprocess.run([str(test_script_abs), 'pypi', version], check=True) 74 | 75 | 76 | # ==================================================== 77 | # Create tag at Github 78 | # ==================================================== 79 | 80 | tag_str = 'v' + version 81 | logging.info('Creating tag {} on commit {}...'.format(tag_str, build_dict['commitId'])) 82 | create_light_tag(github_security_dict['token'], 'smlgit', 'fpbinary', 83 | build_dict['commitId'], tag_str) 84 | 85 | 86 | logging.info('Release of version {} is complete.'.format(version)) 87 | 88 | 89 | if __name__ == '__main__': 90 | main() -------------------------------------------------------------------------------- /release/run_build.py: -------------------------------------------------------------------------------- 1 | import argparse, logging, os 2 | from lib.appveyor import start_build, download_build_artifacts 3 | from lib.pypi import upload_to_pypi_server 4 | from lib.common import get_appveyor_security, get_testpypi_security 5 | 6 | 7 | default_output_dir = os.path.abspath('download_dir') 8 | 9 | 10 | def main(): 11 | logging.basicConfig() 12 | logging.root.setLevel(logging.INFO) 13 | 14 | parser = argparse.ArgumentParser(description='Run an fpbinary build on Appveyor.') 15 | parser.add_argument('branch', type=str, 16 | help='The fpbinary Github branch to run a build on.') 17 | parser.add_argument('--output-dir', type=str, default=None, 18 | help='If specified, the artifacts of the build will be downloaded to this directory.') 19 | parser.add_argument('--upload-dest', type=str, default=None, choices=['pypi', 'testpypi'], 20 | help='Specify to upload the artifacts to the respective package server.') 21 | parser.add_argument('--wait-complete', action='store_true', 22 | help='If specified, will wait until the build is finished. Automatically set if' 23 | '--upload-dest or --output-dir are set.') 24 | parser.add_argument('--release', action='store_true', 25 | help='If specified, do the build without the \'a\' pre release specifier in the output files.') 26 | parser.add_argument('--install-from-testpypi', action='store_true', 27 | help='If specified, the build will upload to test pypi and install from it.') 28 | 29 | args = parser.parse_args() 30 | 31 | if args.upload_dest is not None and args.output_dir is None: 32 | args.output_dir = default_output_dir 33 | 34 | if args.output_dir is not None: 35 | args.wait_complete = True 36 | 37 | appveyor_security_dict = get_appveyor_security() 38 | testpypi_security_dict = get_testpypi_security() 39 | 40 | result_tuple = start_build(appveyor_security_dict['token'], appveyor_security_dict['account'], 41 | 'fpbinary', args.branch, 42 | install_from_testpypi=args.install_from_testpypi, 43 | is_release_build=args.release, wait_for_finish=args.wait_complete) 44 | 45 | if not args.wait_complete: 46 | return 47 | 48 | if result_tuple is None: 49 | logging.error('Build didn\'t start') 50 | exit(1) 51 | 52 | build_success = result_tuple[0] 53 | build_id = result_tuple[1] 54 | 55 | if build_success is True and args.output_dir is not None: 56 | download_build_artifacts(appveyor_security_dict['token'], appveyor_security_dict['account'], 57 | 'fpbinary', os.path.abspath(args.output_dir), build_id=build_id) 58 | 59 | if args.upload_dest is not None: 60 | upload_to_pypi_server(testpypi_security_dict['token'], args.output_dir, server='testpypi') 61 | 62 | 63 | 64 | if __name__ == '__main__': 65 | main() -------------------------------------------------------------------------------- /release/test_all_local_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ================================================================== 4 | # 5 | # Will run the library tests on all pyenv versions by doing local 6 | # builds. 7 | # 8 | # The following need to be installed for this script to run: 9 | # - pyenv 10 | # - for each pyenv python version installed, virtualenv 11 | 12 | 13 | # Exit on error 14 | set -e 15 | 16 | # Read pyenv versions installed 17 | declare -a pyenv_versions 18 | readarray -t pyenv_versions < <(pyenv versions --bare --skip-aliases) 19 | 20 | # Remove build and dist dirs to force a clear install if compiling from source 21 | rm -rf build dist 22 | 23 | 24 | # For each pyenv version, create a virtualenv and run the install 25 | for v in ${pyenv_versions[@]}; do 26 | 27 | echo "======================================================" 28 | echo $v 29 | echo "======================================================" 30 | 31 | eval "$(pyenv init -)" 32 | pyenv shell $v 33 | 34 | virtualenv --clear venv$v 35 | source venv$v/bin/activate 36 | 37 | # Need numpy for tests 38 | pip install numpy 39 | pip install scipy 40 | 41 | python setup.py install 42 | python -m unittest discover -s tests -p testFpBinary* 43 | 44 | # Deactivate virtualenv 45 | deactivate 46 | rm -rf venv$v 47 | done 48 | 49 | 50 | -------------------------------------------------------------------------------- /release/test_all_pypi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ================================================================== 4 | # The following need to be installed for this script to run: 5 | # - pyenv 6 | # - for each pyenv python version installed, virtualenv 7 | # ================================================================== 8 | # 9 | # Will run the library tests on all pyenv versions by installing 10 | # from either test pypi or pypi proper. 11 | # 12 | # Command syntax: 13 | # ./test_all_pypi.sh server_name [version] 14 | # where server_name is either test or pypi and 15 | # version is the version as shown on the server. If 16 | # not specified, will install the latest. 17 | # 18 | # The following need to be installed for this script to run: 19 | # - pyenv 20 | # - for each pyenv python version installed, virtualenv 21 | 22 | 23 | # Exit on error 24 | set -e 25 | 26 | # Commandline args 27 | args=("$@") 28 | 29 | if [ ${#args[@]} -lt 1 ]; then 30 | echo "You must specify either 'pypi' or 'test' as the pypi install location." 31 | (exit 1); 32 | fi 33 | 34 | location=${args[0]} 35 | if [ "$location" != "pypi" ] && [ "$location" != "test" ]; then 36 | echo "You must specify either 'pypi' or 'test' as the pypi install location." 37 | (exit 1); 38 | fi 39 | 40 | version="" 41 | if [ ${#args[@]} -gt 1 ]; then 42 | version=${args[1]} 43 | fi 44 | 45 | if [ "$version" == "" ]; then 46 | package="fpbinary" 47 | else 48 | package="fpbinary==$version" 49 | fi 50 | 51 | 52 | # Read pyenv versions installed 53 | declare -a pyenv_versions 54 | readarray -t pyenv_versions < <(pyenv versions --bare --skip-aliases) 55 | 56 | # Remove build and dist dirs to force a clear install if compiling from source 57 | rm -rf build dist 58 | 59 | # For each pyenv version, create a virtualenv and run the install 60 | for v in ${pyenv_versions[@]}; do 61 | 62 | echo "======================================================" 63 | echo $v 64 | echo "======================================================" 65 | 66 | eval "$(pyenv init -)" 67 | pyenv shell $v 68 | 69 | virtualenv --clear venv$v 70 | source venv$v/bin/activate 71 | 72 | # Need numpy for tests 73 | pip install numpy 74 | pip install scipy 75 | 76 | echo "======================================================" 77 | echo "Installing $package via pypi site $location" 78 | echo "======================================================" 79 | 80 | if [ "$location" == "test" ]; then 81 | pip install -I --pre --no-cache-dir --index-url https://test.pypi.org/simple/ --no-deps $package 82 | else 83 | pip install -I --pre --no-cache-dir --no-deps $package 84 | fi 85 | 86 | # Run fpbinary tests 87 | python -m unittest discover -s tests -p testFpBinary* 88 | 89 | # Deactivate virtualenv 90 | deactivate 91 | rm -rf venv$v 92 | done 93 | 94 | 95 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | from setuptools import setup, Extension 3 | 4 | 5 | # Version information 6 | def get_version_number(): 7 | with open('VERSION', 'r') as f: 8 | for line in f: 9 | return line.split('.') 10 | 11 | return () 12 | 13 | # Alpha build information 14 | # Builder must create a file in the fpbinary root directory called "ALPHA" 15 | # with the alpha build string in it if they want the build to have a version 16 | # with extra information appended to it. 17 | def get_alpha_str(): 18 | if os.path.exists('ALPHA'): 19 | with open('ALPHA', 'r') as f: 20 | for line in f: 21 | return line.strip() 22 | 23 | return None 24 | 25 | version_tuple = get_version_number() 26 | 27 | if len(version_tuple) < 3: 28 | raise SystemError("Couldn't find the module version number!") 29 | 30 | version = '{}.{}.{}'.format(version_tuple[0], version_tuple[1], version_tuple[2]) 31 | alpha_str = get_alpha_str() 32 | 33 | if alpha_str is not None: 34 | version += alpha_str 35 | 36 | with open("README.rst", "r") as fh: 37 | long_description = fh.read() 38 | 39 | extra_compile_args = [] 40 | 41 | fpbinary_module = Extension('fpbinary', 42 | define_macros=[('MAJOR_VERSION', version_tuple[0]), 43 | ('MINOR_VERSION', version_tuple[1]), 44 | ('MICRO_VERSION', version_tuple[2]), 45 | ('VERSION_STRING', version)], 46 | sources=['src/fpbinarymodule.c', 47 | 'src/fpbinaryglobaldoc.c', 48 | 'src/fpbinarycommon.c', 49 | 'src/fpbinarysmall.c', 50 | 'src/fpbinarylarge.c', 51 | 'src/fpbinaryobject.c', 52 | 'src/fpbinarycomplexobject.c', 53 | 'src/fpbinaryswitchable.c', 54 | 'src/fpbinaryarrayfuncs.c', 55 | 'src/fpbinaryenums.c'], 56 | extra_compile_args=extra_compile_args) 57 | 58 | 59 | setup(name='fpbinary', 60 | version=version, 61 | description='Provides binary fixed point functionality.', 62 | long_description=long_description, 63 | python_requires='>=2.7', 64 | 65 | classifiers=[ 66 | 'Operating System :: Microsoft :: Windows', 67 | 'Operating System :: POSIX', 68 | 'Operating System :: MacOS :: MacOS X', 69 | 'Programming Language :: Python :: 2.7', 70 | 'Programming Language :: Python :: 3'], 71 | 72 | url="https://github.com/smlgit/fpbinary", 73 | author_email='smlgit@protonmail.com', 74 | keywords='fixed-point, binary, bit-accurate, dsp, fpga', 75 | license='GPL-2.0 License', 76 | 77 | project_urls={ 78 | 'Source': 'https://github.com/smlgit/fpbinary', 79 | 'Documentation': 'https://fpbinary.readthedocs.io/en/latest/', 80 | }, 81 | 82 | ext_modules=[fpbinary_module]) 83 | -------------------------------------------------------------------------------- /src/fpbinaryarrayfuncs.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | /****************************************************************************** 6 | * 7 | * Functions to create or modify lists or arrays of fixed point objects. 8 | * 9 | *****************************************************************************/ 10 | 11 | #include "fpbinaryarrayfuncs.h" 12 | #include "fpbinarycomplexobject.h" 13 | #include "fpbinaryobject.h" 14 | 15 | /* 16 | * Assumes kwds is NOT NULL. 17 | */ 18 | static PyObject * 19 | fpbinary_from_array_nested(PyObject *array, PyObject *fpbinary_args, 20 | PyObject *kwds) 21 | { 22 | Py_ssize_t len = PySequence_Length(array); 23 | PyObject *result_list = NULL; 24 | 25 | if (len < 0) 26 | { 27 | PyErr_SetString(PyExc_TypeError, "Could not determine array length " 28 | "when creating FpBinary from array."); 29 | return NULL; 30 | } 31 | 32 | result_list = PyList_New(len); 33 | 34 | for (int i = 0; i < len; i++) 35 | { 36 | PyObject *cur_item = PySequence_GetItem(array, i); 37 | 38 | if (PySequence_Check(cur_item)) 39 | { 40 | PyObject *nested_list = 41 | fpbinary_from_array_nested(cur_item, fpbinary_args, kwds); 42 | if (nested_list) 43 | { 44 | PyList_SET_ITEM(result_list, i, nested_list); 45 | } 46 | else 47 | { 48 | return NULL; 49 | } 50 | } 51 | else 52 | { 53 | PyObject *new_fp_object = NULL; 54 | 55 | /* Set the fpbinary keyword argument "value" to the current array 56 | * item */ 57 | PyDict_SetItemString(kwds, "value", cur_item); 58 | 59 | new_fp_object = 60 | PyObject_Call((PyObject *)&FpBinary_Type, fpbinary_args, kwds); 61 | 62 | if (new_fp_object) 63 | { 64 | PyList_SET_ITEM(result_list, i, new_fp_object); 65 | } 66 | else 67 | { 68 | return NULL; 69 | } 70 | } 71 | 72 | Py_DECREF(cur_item); 73 | } 74 | 75 | return result_list; 76 | } 77 | 78 | /* 79 | * Assumes kwds is NOT NULL. 80 | */ 81 | static PyObject * 82 | fpbinarycomplex_from_array_nested(PyObject *array, 83 | PyObject *fpbinarycomplex_args, 84 | PyObject *kwds) 85 | { 86 | Py_ssize_t len = PySequence_Length(array); 87 | PyObject *result_list = NULL; 88 | 89 | if (len < 0) 90 | { 91 | PyErr_SetString(PyExc_TypeError, "Could not determine array length " 92 | "when creating FpBinary from array."); 93 | return NULL; 94 | } 95 | 96 | result_list = PyList_New(len); 97 | 98 | for (int i = 0; i < len; i++) 99 | { 100 | PyObject *cur_item = PySequence_GetItem(array, i); 101 | 102 | if (PySequence_Check(cur_item)) 103 | { 104 | PyObject *nested_list = fpbinarycomplex_from_array_nested( 105 | cur_item, fpbinarycomplex_args, kwds); 106 | if (nested_list) 107 | { 108 | PyList_SET_ITEM(result_list, i, nested_list); 109 | } 110 | else 111 | { 112 | return NULL; 113 | } 114 | } 115 | else 116 | { 117 | PyObject *new_fp_object = NULL; 118 | 119 | /* Set the fpbinary keyword argument "value" to the current array 120 | * item */ 121 | PyDict_SetItemString(kwds, "value", cur_item); 122 | 123 | new_fp_object = PyObject_Call((PyObject *)&FpBinaryComplex_Type, 124 | fpbinarycomplex_args, kwds); 125 | 126 | if (new_fp_object) 127 | { 128 | PyList_SET_ITEM(result_list, i, new_fp_object); 129 | } 130 | else 131 | { 132 | return NULL; 133 | } 134 | } 135 | 136 | Py_DECREF(cur_item); 137 | } 138 | 139 | return result_list; 140 | } 141 | 142 | static bool 143 | fpbinary_array_resize_nested(PyObject *array, PyObject *fpbinary_args, 144 | PyObject *kwds) 145 | { 146 | Py_ssize_t len = PySequence_Length(array); 147 | bool result = true; 148 | 149 | if (len < 0) 150 | { 151 | PyErr_SetString(PyExc_TypeError, "Could not determine array length " 152 | "when resizing FpBinary in array."); 153 | return false; 154 | } 155 | 156 | for (int i = 0; i < len; i++) 157 | { 158 | PyObject *cur_item = PySequence_GetItem(array, i); 159 | 160 | if (PySequence_Check(cur_item)) 161 | { 162 | result = 163 | fpbinary_array_resize_nested(cur_item, fpbinary_args, kwds); 164 | } 165 | else 166 | { 167 | PyObject *resized_obj = forward_call_with_args( 168 | cur_item, resize_method_name_str, fpbinary_args, kwds); 169 | ; 170 | result = (resized_obj != NULL); 171 | 172 | if (result) 173 | { 174 | Py_DECREF(resized_obj); 175 | } 176 | } 177 | 178 | Py_DECREF(cur_item); 179 | } 180 | 181 | return result; 182 | } 183 | 184 | /* 185 | * First argument must be an object that implements the __getitem__ method. 186 | * The rest of the arguments: 187 | * int_bits, frac_bits, signed, format_inst. 188 | * 189 | * We use the underlying FpBinary constructor format, so if format_inst is used, 190 | * it needs to be used as a keyword arg. 191 | * 192 | * Returns a list of FpBinary objects. 193 | */ 194 | 195 | FP_GLOBAL_Doc_STRVAR( 196 | FpBinary_FromArray_doc, 197 | "fpbinary_list_from_array(array, int_bits=1, frac_bits=0, signed=True, " 198 | "format_inst=None)\n" 199 | "--\n" 200 | "\n" 201 | "Converts the elements of array to a list of FpBinary objects using the " 202 | "format " 203 | "specified by int_bits/frac_bits or format_inst.\n" 204 | "If format_inst is used, it must be specified by keyword.\n" 205 | "\n" 206 | "Parameters\n" 207 | "----------\n" 208 | "array : Any object that implements __getitem__.\n" 209 | "\n" 210 | "int_bits, frac_bits, signed, format_inst : As per FpBinary constructor\n" 211 | "\n" 212 | "Returns\n" 213 | "----------\n" 214 | "list\n" 215 | " Elements are FpBinary objects. Dimension of input array is " 216 | "maintained.\n"); 217 | 218 | PyObject * 219 | FpBinary_FromArray(PyObject *self, PyObject *args, PyObject *kwds) 220 | { 221 | Py_ssize_t args_len; 222 | PyObject *fpbinary_args = NULL; 223 | PyObject *temp_kwds = NULL; 224 | PyObject *result = NULL; 225 | 226 | if (!PyTuple_Check(args)) 227 | { 228 | PyErr_SetString( 229 | PyExc_ValueError, 230 | "Unexpected parameter list when creating FpBinary from array."); 231 | return NULL; 232 | } 233 | 234 | args_len = PyTuple_Size(args); 235 | if (args_len < 1) 236 | { 237 | PyErr_SetString(PyExc_TypeError, "An array or list must be specified " 238 | "when creating FpBinary from array."); 239 | return NULL; 240 | } 241 | 242 | if (!PySequence_Check(PyTuple_GET_ITEM(args, 0))) 243 | { 244 | PyErr_SetString(PyExc_TypeError, "First argument must be an array or " 245 | "list when creating FpBinary from " 246 | "array."); 247 | return NULL; 248 | } 249 | 250 | if (args_len > 4) 251 | { 252 | PyErr_SetString(PyExc_ValueError, 253 | "The only positional arguments allowed when when " 254 | "creating FpBinary from array" 255 | " are array, int_bits, frac_bits and signed."); 256 | return NULL; 257 | } 258 | 259 | fpbinary_args = PyTuple_GetSlice(args, 1, args_len); 260 | 261 | if (!kwds) 262 | { 263 | temp_kwds = PyDict_New(); 264 | result = fpbinary_from_array_nested(PyTuple_GET_ITEM(args, 0), 265 | fpbinary_args, temp_kwds); 266 | Py_DECREF(temp_kwds); 267 | } 268 | else 269 | { 270 | result = fpbinary_from_array_nested(PyTuple_GET_ITEM(args, 0), 271 | fpbinary_args, kwds); 272 | } 273 | 274 | Py_DECREF(fpbinary_args); 275 | 276 | return result; 277 | } 278 | 279 | /* 280 | * First argument must be an object that implements the __getitem__ method. 281 | * The rest of the arguments: 282 | * int_bits, frac_bits, format_inst. 283 | * 284 | * We use the underlying FpBinaryComplex constructor format, so if format_inst 285 | * is used, 286 | * it needs to be used as a keyword arg. 287 | * 288 | * Returns a list of FpBinaryComplex objects. 289 | */ 290 | 291 | FP_GLOBAL_Doc_STRVAR( 292 | FpBinaryComplex_FromArray_doc, 293 | "fpbinarycomplex_list_from_array(array, int_bits=1, frac_bits=0, " 294 | "format_inst=None)\n" 295 | "--\n" 296 | "\n" 297 | "Converts the elements of array to a list of FpBinaryComplex objects using " 298 | "the format " 299 | "specified by int_bits/frac_bits or format_inst.\n" 300 | "If format_inst is used, it must be specified by keyword.\n" 301 | "\n" 302 | "Parameters\n" 303 | "----------\n" 304 | "array : Any object that implements __getitem__.\n" 305 | "\n" 306 | "int_bits, frac_bits, format_inst : As per FpBinaryComplex constructor\n" 307 | "\n" 308 | "Returns\n" 309 | "----------\n" 310 | "list\n" 311 | " Elements are FpBinaryComplex objects. Dimension of input array is " 312 | "maintained.\n"); 313 | 314 | PyObject * 315 | FpBinaryComplex_FromArray(PyObject *self, PyObject *args, PyObject *kwds) 316 | { 317 | Py_ssize_t args_len; 318 | PyObject *fpbinarycomplex_args = NULL; 319 | PyObject *temp_kwds = NULL; 320 | PyObject *result = NULL; 321 | 322 | if (!PyTuple_Check(args)) 323 | { 324 | PyErr_SetString(PyExc_ValueError, "Unexpected parameter list when " 325 | "creating FpBinaryComplex from " 326 | "array."); 327 | return NULL; 328 | } 329 | 330 | args_len = PyTuple_Size(args); 331 | if (args_len < 1) 332 | { 333 | PyErr_SetString(PyExc_TypeError, "An array or list must be specified " 334 | "when creating FpBinaryComplex from " 335 | "array."); 336 | return NULL; 337 | } 338 | 339 | if (!PySequence_Check(PyTuple_GET_ITEM(args, 0))) 340 | { 341 | PyErr_SetString(PyExc_TypeError, "First argument must be an array or " 342 | "list when creating FpBinaryComplex " 343 | "from array."); 344 | return NULL; 345 | } 346 | 347 | if (args_len > 3) 348 | { 349 | PyErr_SetString(PyExc_ValueError, 350 | "The only positional arguments allowed when when " 351 | "creating FpBinaryComplex from array" 352 | " are array, int_bits and frac_bits."); 353 | return NULL; 354 | } 355 | 356 | fpbinarycomplex_args = PyTuple_GetSlice(args, 1, args_len); 357 | 358 | if (!kwds) 359 | { 360 | temp_kwds = PyDict_New(); 361 | result = fpbinarycomplex_from_array_nested( 362 | PyTuple_GET_ITEM(args, 0), fpbinarycomplex_args, temp_kwds); 363 | Py_DECREF(temp_kwds); 364 | } 365 | else 366 | { 367 | result = fpbinarycomplex_from_array_nested(PyTuple_GET_ITEM(args, 0), 368 | fpbinarycomplex_args, kwds); 369 | } 370 | 371 | Py_DECREF(fpbinarycomplex_args); 372 | 373 | return result; 374 | } 375 | 376 | /* 377 | * First argument must be an object that implements the __getitem__ method. 378 | * The rest of the arguments: 379 | * format, overflow_mode, round_mode. 380 | */ 381 | 382 | FP_GLOBAL_Doc_STRVAR( 383 | FpBinary_ArrayResize_doc, 384 | "array_resize(array, format, overflow_mode=0, round_mode=2)\n" 385 | "--\n" 386 | "\n" 387 | "Resizes the fixed point objects in array IN PLACE to the format described " 388 | "by format.\n" 389 | "See the documentation for FpBinary.resize for more information.\n" 390 | "\n" 391 | "Parameters\n" 392 | "----------\n" 393 | "array : Any object that implements __getitem__. Elements must be nested " 394 | "arrays or FpBinary or FpBinaryComplex objects.\n" 395 | "\n" 396 | "array, format, overflow_mode=0, round_mode : As per FpBinary.resize " 397 | "method.\n" 398 | "\n" 399 | "Returns\n" 400 | "----------\n" 401 | "None\n"); 402 | 403 | PyObject * 404 | FpBinary_ArrayResize(PyObject *self, PyObject *args, PyObject *kwds) 405 | { 406 | Py_ssize_t args_len; 407 | PyObject *fpbinary_args = NULL; 408 | 409 | if (!PyTuple_Check(args)) 410 | { 411 | PyErr_SetString( 412 | PyExc_ValueError, 413 | "Unexpected parameter list when resizing array elements."); 414 | return NULL; 415 | } 416 | 417 | args_len = PyTuple_Size(args); 418 | if (args_len < 1) 419 | { 420 | PyErr_SetString( 421 | PyExc_TypeError, 422 | "An array or list must be specified when resizing array elements."); 423 | return NULL; 424 | } 425 | 426 | if (!PySequence_Check(PyTuple_GET_ITEM(args, 0))) 427 | { 428 | PyErr_SetString(PyExc_TypeError, "First argument must be an array or " 429 | "list when resizing array elements."); 430 | return NULL; 431 | } 432 | 433 | fpbinary_args = PyTuple_GetSlice(args, 1, args_len); 434 | if (fpbinary_array_resize_nested(PyTuple_GET_ITEM(args, 0), fpbinary_args, 435 | kwds)) 436 | { 437 | Py_DECREF(fpbinary_args); 438 | Py_RETURN_NONE; 439 | } 440 | else 441 | { 442 | return NULL; 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /src/fpbinaryarrayfuncs.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | #ifndef FPBINARYARRAYFUNCS_H_ 6 | #define FPBINARYARRAYFUNCS_H_ 7 | 8 | #include "fpbinarycommon.h" 9 | 10 | extern FP_GLOBAL_Doc_VAR(FpBinary_FromArray_doc); 11 | extern FP_GLOBAL_Doc_VAR(FpBinaryComplex_FromArray_doc); 12 | extern FP_GLOBAL_Doc_VAR(FpBinary_ArrayResize_doc); 13 | 14 | PyObject* FpBinary_FromArray(PyObject* self, PyObject* args, PyObject* kwds); 15 | PyObject* FpBinaryComplex_FromArray(PyObject* self, PyObject* args, PyObject* kwds); 16 | PyObject* FpBinary_ArrayResize(PyObject* self, PyObject* args, PyObject* kwds); 17 | 18 | #endif /* FPBINARYARRAYFUNCS_H_ */ 19 | -------------------------------------------------------------------------------- /src/fpbinarycommon.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | /****************************************************************************** 6 | * 7 | * Useful functions available to all fpbinary source. 8 | * 9 | *****************************************************************************/ 10 | 11 | #include "fpbinarycommon.h" 12 | 13 | #include 14 | #include 15 | 16 | PyObject *py_zero; 17 | PyObject *py_one; 18 | PyObject *py_two; 19 | PyObject *py_minus_one; 20 | PyObject *py_ten; 21 | PyObject *py_five; 22 | 23 | /* For pickling base objects */ 24 | PyObject *fp_small_type_id; 25 | PyObject *fp_large_type_id; 26 | 27 | /* Non-standard method/property names */ 28 | PyObject *copy_method_name_str = NULL; 29 | PyObject *resize_method_name_str = NULL; 30 | PyObject *get_is_signed_method_name_str = NULL; 31 | PyObject *get_format_method_name_str = NULL; 32 | PyObject *str_ex_method_name_str = NULL; 33 | PyObject *complex_real_property_name_str = NULL; 34 | PyObject *complex_imag_property_name_str = NULL; 35 | PyObject *py_default_format_tuple = NULL; 36 | 37 | /* Useful, reusable strings. 38 | * Careful using these with concat methods - check if a reference is stolen... 39 | */ 40 | PyObject *decimal_point_str = NULL; 41 | PyObject *add_sign_str = NULL; 42 | PyObject *j_str = NULL; 43 | PyObject *open_bracket_str = NULL; 44 | PyObject *close_bracket_str = NULL; 45 | 46 | /* 47 | * Does a left shift SAFELY (shifting by more than the length of the 48 | * type is undefined). 49 | */ 50 | FP_UINT_TYPE 51 | fp_uint_lshift(FP_UINT_TYPE value, FP_UINT_TYPE num_shifts) 52 | { 53 | if (num_shifts == 0) 54 | { 55 | return value; 56 | } 57 | 58 | if (num_shifts >= FP_UINT_NUM_BITS) 59 | { 60 | return 0; 61 | } 62 | 63 | return value << num_shifts; 64 | } 65 | 66 | /* 67 | * Does a right shift SAFELY (shifting by more than the length of the 68 | * type is undefined). 69 | */ 70 | FP_UINT_TYPE 71 | fp_uint_rshift(FP_UINT_TYPE value, FP_UINT_TYPE num_shifts) 72 | { 73 | if (num_shifts == 0) 74 | { 75 | return value; 76 | } 77 | 78 | if (num_shifts >= FP_UINT_NUM_BITS) 79 | { 80 | return 0; 81 | } 82 | 83 | return value >> num_shifts; 84 | } 85 | 86 | /* 87 | * FFS, this does what the 2.7 PyString concat does... 88 | * 89 | * Probably could change all code to use unicode. 90 | * 91 | * The reference in left is stolen and reassigned to the result of the 92 | * concatenation. 93 | */ 94 | void 95 | unicode_concat(PyObject **left, PyObject *right) 96 | { 97 | PyObject *tmp = *left; 98 | *left = PyUnicode_Concat(tmp, right); 99 | Py_DECREF(tmp); 100 | } 101 | 102 | /* 103 | * To minimise impact of v2->v3 ... 104 | */ 105 | int 106 | FpBinary_TpCompare(PyObject *op1, PyObject *op2) 107 | { 108 | #if PY_MAJOR_VERSION >= 3 109 | int result = -1; 110 | PyObject *gt = PyObject_RichCompare(op1, op2, Py_GT); 111 | 112 | if (gt == Py_True) 113 | { 114 | result = 1; 115 | } 116 | else 117 | { 118 | PyObject *eq = PyObject_RichCompare(op1, op2, Py_EQ); 119 | 120 | if (eq == Py_True) 121 | { 122 | result = 0; 123 | } 124 | 125 | Py_DECREF(eq); 126 | } 127 | 128 | Py_DECREF(gt); 129 | return result; 130 | 131 | #else 132 | 133 | return FP_METHOD(op1, tp_compare)(op1, op2); 134 | 135 | #endif 136 | } 137 | 138 | bool 139 | fp_binary_new_params_parse(PyObject *args, PyObject *kwds, long *int_bits, 140 | long *frac_bits, bool *is_signed, double *value, 141 | PyObject **bit_field, PyObject **format_instance) 142 | { 143 | static char *kwlist[] = {"int_bits", "frac_bits", "signed", "value", 144 | "bit_field", "format_inst", NULL}; 145 | 146 | PyObject *py_int_bits = NULL, *py_frac_bits = NULL; 147 | PyObject *py_is_signed = NULL; 148 | *bit_field = NULL; 149 | *format_instance = NULL; 150 | 151 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOdOO", kwlist, 152 | &py_int_bits, &py_frac_bits, &py_is_signed, 153 | value, bit_field, format_instance)) 154 | return false; 155 | 156 | /* Ensure there is information for the format */ 157 | if (py_int_bits) 158 | { 159 | if (!check_supported_builtin_int(py_int_bits)) 160 | { 161 | PyErr_SetString(PyExc_TypeError, "int_bits must be an integer."); 162 | return false; 163 | } 164 | 165 | *int_bits = PyLong_AsLong(py_int_bits); 166 | } 167 | 168 | if (py_frac_bits) 169 | { 170 | if (!check_supported_builtin_int(py_frac_bits)) 171 | { 172 | PyErr_SetString(PyExc_TypeError, "frac_bits must be an integer."); 173 | return false; 174 | } 175 | 176 | *frac_bits = PyLong_AsLong(py_frac_bits); 177 | } 178 | 179 | if (!(*format_instance) && (!py_int_bits || !py_frac_bits)) 180 | { 181 | double scaled_value; 182 | FP_INT_TYPE int_bits_uint, frac_bits_uint; 183 | calc_double_to_fp_params(*value, &scaled_value, &int_bits_uint, 184 | &frac_bits_uint); 185 | 186 | if (!py_int_bits) 187 | { 188 | *int_bits = (long)int_bits_uint; 189 | } 190 | 191 | if (!py_frac_bits) 192 | { 193 | *frac_bits = (long)frac_bits_uint; 194 | } 195 | } 196 | 197 | if (py_is_signed) 198 | { 199 | if (!PyBool_Check(py_is_signed)) 200 | { 201 | PyErr_SetString(PyExc_TypeError, "signed must be True or False."); 202 | return false; 203 | } 204 | 205 | if (py_is_signed == Py_True) 206 | { 207 | *is_signed = true; 208 | } 209 | else 210 | { 211 | *is_signed = false; 212 | } 213 | } 214 | 215 | if (*bit_field) 216 | { 217 | if (!PyLong_Check(*bit_field)) 218 | { 219 | PyErr_SetString(PyExc_TypeError, 220 | "bit_field must be a long integer."); 221 | return false; 222 | } 223 | } 224 | 225 | return true; 226 | } 227 | 228 | bool 229 | fp_binary_subscript_get_item_index(PyObject *item, Py_ssize_t *index) 230 | { 231 | if (PyIndex_Check(item)) 232 | { 233 | *index = PyNumber_AsSsize_t(item, NULL); 234 | return true; 235 | } 236 | 237 | return false; 238 | } 239 | 240 | bool 241 | fp_binary_subscript_get_item_start_stop(PyObject *item, Py_ssize_t *start, 242 | Py_ssize_t *stop, 243 | Py_ssize_t assumed_length) 244 | { 245 | if (PySlice_Check(item)) 246 | { 247 | Py_ssize_t step; 248 | 249 | /* I ain't giving up a clean record now... */ 250 | #if PY_MAJOR_VERSION >= 3 251 | PyObject *cast_item = item; 252 | #else 253 | PySliceObject *cast_item = (PySliceObject *)item; 254 | #endif 255 | 256 | /* 257 | * In Python 2, the slice unpacking methods are pretty bad. To make this 258 | * as clean as possible (i.e. only upper functions care about length, we 259 | * have a bit of a hack here. 260 | */ 261 | Py_ssize_t length; 262 | if (PySlice_GetIndicesEx(cast_item, PY_SSIZE_T_MAX, start, stop, &step, 263 | &length) < 0) 264 | { 265 | return false; 266 | } 267 | 268 | if (step > 0) 269 | { 270 | return true; 271 | } 272 | else 273 | { 274 | PyErr_SetString(PyExc_TypeError, 275 | "Steps in subscripts are not supported."); 276 | } 277 | } 278 | 279 | return false; 280 | } 281 | 282 | /* 283 | * This function basically converts a double to a fpbinary without creating 284 | * the actual fpbinary object. This is so a user can decide which type to 285 | * use based on the magnitude of scaled_value. If scaled value is too large 286 | * to be represented by a 64 bit fpbinary object, the values can be applied 287 | * to a fpbinarylarge object (this is why the output parameter scaled_value is 288 | * a double). 289 | * 290 | * We could have decided to just use the exp value and DBL_MANT_DIG but in 291 | * most cases, I wouldn't expect raw doubles/floats to be used with fpbinary 292 | * objects unless they were quite small and of limited precision, and if we 293 | * minimise the number of bits used, we reduce the need to use the slower 294 | * fpbinarylarge object after math operations. 295 | */ 296 | void 297 | calc_double_to_fp_params(double input_value, double *scaled_value, 298 | FP_INT_TYPE *int_bits, FP_INT_TYPE *frac_bits) 299 | { 300 | int exp; 301 | double mantissa = frexp(input_value, &exp); 302 | 303 | if (mantissa == 0) 304 | { 305 | *int_bits = 1; 306 | *frac_bits = 0; 307 | *scaled_value = 0.0; 308 | } 309 | else 310 | { 311 | FP_INT_TYPE i; 312 | double shifted_mant = mantissa; 313 | 314 | /* Multiply the mantissa by two and subtract the new integer part. 315 | * Continue until 316 | * the remaining value is zero. I'm doing this instead of (say) 317 | * converting to an 318 | * integer and left shifting because we can't guarantee a double will 319 | * have less bits 320 | * than a long long int. 321 | * 322 | * I'm using a for loop that WILL exit if it gets to 323 | * the max number of bits possible in the mantissa. This is so the code 324 | * doesn't 325 | * lock up if I've made a mistake... 326 | */ 327 | for (i = 1; i <= DBL_MANT_DIG; i++) 328 | { 329 | shifted_mant *= 2; 330 | shifted_mant -= (int)shifted_mant; 331 | 332 | if (shifted_mant == 0.0) 333 | { 334 | break; 335 | } 336 | } 337 | 338 | /* i should now be the total number of PRECISION bits required. */ 339 | if (exp > 0) 340 | { 341 | *int_bits = exp; 342 | } 343 | else 344 | *int_bits = 0; 345 | 346 | *frac_bits = 0; 347 | 348 | /* Negative exponent means its magnitude is the inital number of 349 | * FRACTIONAL bits. 350 | */ 351 | if (exp < 0) 352 | { 353 | *frac_bits = abs(exp); 354 | } 355 | 356 | if (i > *int_bits) 357 | { 358 | *frac_bits += i - *int_bits; 359 | } 360 | 361 | /* And calculate the scaled_value for fixed point representation. */ 362 | *scaled_value = ldexp(mantissa, exp + *frac_bits); 363 | 364 | /* We always assume a signed type, so add an extra bit for the sign. */ 365 | *int_bits += 1; 366 | } 367 | } 368 | 369 | /* 370 | * This function basically converts a PyInt or PyLong to a fpbinary without 371 | * creating 372 | * the actual fpbinary object. This is so a user can decide which type to 373 | * use based on the magnitude of scaled_value. If scaled value is too large 374 | * to be represented by a 64 bit fpbinary object, the values can be applied 375 | * to a fpbinarylarge object. 376 | * 377 | * input_value must be a pointer to a PyInt or PyLong. 378 | */ 379 | void 380 | calc_pyint_to_fp_params(PyObject *input_value, PyObject **scaled_value, 381 | FP_INT_TYPE *int_bits) 382 | { 383 | *scaled_value = NULL; 384 | *int_bits = 0; 385 | 386 | if (FpBinary_IntCheck(input_value) || PyLong_Check(input_value)) 387 | { 388 | *scaled_value = FpBinary_EnsureIsPyLong(input_value); 389 | } 390 | 391 | if (*scaled_value) 392 | { 393 | size_t num_bits = _PyLong_NumBits(*scaled_value); 394 | /* Assume signed - need extra bit (_PyLong_NumBits returns number 395 | * of bits for magnitude). 396 | */ 397 | num_bits++; 398 | 399 | *int_bits = num_bits; 400 | } 401 | } 402 | 403 | /* 404 | * Attempts to work out the smallest int and frac bit format for the passed 405 | * numberic object. 406 | * If the object type isn't supported (see check_supported_builtin) returns 407 | * false. 408 | */ 409 | bool 410 | get_best_int_frac_bits(PyObject *obj, FP_INT_TYPE *int_bits, 411 | FP_INT_TYPE *frac_bits) 412 | { 413 | if (check_supported_builtin_int(obj)) 414 | { 415 | PyObject *scaled_bits; 416 | calc_pyint_to_fp_params(obj, &scaled_bits, int_bits); 417 | *frac_bits = 0; 418 | 419 | if (scaled_bits) 420 | { 421 | Py_DECREF(scaled_bits); 422 | } 423 | } 424 | else if (check_supported_builtin_float(obj)) 425 | { 426 | double scaled_value; 427 | calc_double_to_fp_params(PyFloat_AsDouble(obj), &scaled_value, int_bits, 428 | frac_bits); 429 | } 430 | else 431 | { 432 | return false; 433 | } 434 | 435 | return true; 436 | } 437 | 438 | PyObject * 439 | fp_uint_as_pylong(FP_UINT_TYPE value) 440 | { 441 | return PyLong_FromUnsignedLongLong(value); 442 | } 443 | 444 | PyObject * 445 | fp_int_as_pylong(FP_INT_TYPE value) 446 | { 447 | return PyLong_FromLongLong(value); 448 | } 449 | 450 | FP_INT_TYPE 451 | pylong_as_fp_int(PyObject *val) { return (FP_INT_TYPE)PyLong_AsLongLong(val); } 452 | 453 | FP_UINT_TYPE 454 | pylong_as_fp_uint(PyObject *val) 455 | { 456 | return (FP_UINT_TYPE)PyLong_AsUnsignedLongLong(val); 457 | } 458 | 459 | /* 460 | * Will build a PyLong object whose bits are the scaled value as defined 461 | * by the float value and frac_bits. Rounding will be taken care of on 462 | * the basis of round_mode. 463 | * 464 | * NOTE: Overflow is not checked for (that is why there is no int_bits param). 465 | */ 466 | void 467 | build_scaled_bits_from_pyfloat(PyObject *value, PyObject *frac_bits, 468 | fp_round_mode_t round_mode, 469 | PyObject **output_obj) 470 | { 471 | PyObject *py_scale_factor = 472 | FP_NUM_METHOD(py_one, nb_lshift)(py_one, frac_bits); 473 | PyObject *py_scaled_value = 474 | FP_NUM_METHOD(value, nb_multiply)(value, py_scale_factor); 475 | double dbl_scaled_value = PyFloat_AsDouble(py_scaled_value); 476 | 477 | if (round_mode == ROUNDING_NEAR_POS_INF) 478 | { 479 | dbl_scaled_value += 0.5; 480 | } 481 | 482 | dbl_scaled_value = floor(dbl_scaled_value); 483 | *output_obj = PyLong_FromDouble(dbl_scaled_value); 484 | 485 | Py_DECREF(py_scale_factor); 486 | Py_DECREF(py_scaled_value); 487 | } 488 | 489 | /* Will attempt to convert the format_tuple_param int_bits and frac_bits PyLong 490 | * objects. 491 | * 492 | * It is assumed the calling function will decrement the reference to 493 | * *int_bits and *frac_bits. 494 | * 495 | * Returns false if the parameter could not be converted. 496 | */ 497 | bool 498 | extract_fp_format_from_tuple(PyObject *format_tuple_param, PyObject **int_bits, 499 | PyObject **frac_bits) 500 | { 501 | *int_bits = NULL; 502 | *frac_bits = NULL; 503 | 504 | /* FP format is defined by a python tuple: (int_bits, frac_bits) */ 505 | if (PyTuple_Check(format_tuple_param)) 506 | { 507 | PyObject *new_int_bits_borrowed = NULL, *new_frac_bits_borrowed = NULL; 508 | 509 | if (PyTuple_Size(format_tuple_param) != 2) 510 | { 511 | PyErr_SetString(PyExc_TypeError, "Format tuple must be length 2."); 512 | return false; 513 | } 514 | 515 | new_int_bits_borrowed = PyTuple_GetItem(format_tuple_param, 0); 516 | if (new_int_bits_borrowed) 517 | { 518 | if (FpBinary_IntCheck(new_int_bits_borrowed) || 519 | PyLong_Check(new_int_bits_borrowed)) 520 | { 521 | /* Need to convert to long. */ 522 | *int_bits = FpBinary_EnsureIsPyLong(new_int_bits_borrowed); 523 | } 524 | } 525 | 526 | new_frac_bits_borrowed = PyTuple_GetItem(format_tuple_param, 1); 527 | if (new_frac_bits_borrowed) 528 | { 529 | if (FpBinary_IntCheck(new_frac_bits_borrowed) || 530 | PyLong_Check(new_frac_bits_borrowed)) 531 | { 532 | /* Need to convert to long. */ 533 | *frac_bits = FpBinary_EnsureIsPyLong(new_frac_bits_borrowed); 534 | } 535 | } 536 | 537 | if (!*int_bits || !*frac_bits) 538 | { 539 | PyErr_SetString(PyExc_TypeError, 540 | "The values in the format tuple must be integers."); 541 | } 542 | } 543 | 544 | return (*int_bits && *frac_bits); 545 | } 546 | 547 | /* Will attempt to convert the format_tuple_param to int_bits and frac_bits c 548 | * long values. 549 | * 550 | * Returns false if the parameter could not be converted. 551 | */ 552 | bool 553 | extract_fp_format_ints_from_tuple(PyObject *format_tuple_param, 554 | FP_INT_TYPE *int_bits, FP_INT_TYPE *frac_bits) 555 | { 556 | PyObject *int_bits_py = NULL, *frac_bits_py = NULL; 557 | 558 | if (extract_fp_format_from_tuple(format_tuple_param, &int_bits_py, 559 | &frac_bits_py)) 560 | { 561 | /* Convert the py objects to the c type */ 562 | *int_bits = PyLong_AsLong(int_bits_py); 563 | *frac_bits = PyLong_AsLong(frac_bits_py); 564 | 565 | Py_DECREF(int_bits_py); 566 | Py_DECREF(frac_bits_py); 567 | 568 | return true; 569 | } 570 | 571 | return false; 572 | } 573 | 574 | /* 575 | * Checks the parameters to an FpBinary new method are the correct types. 576 | * Returns false if one or more params are the wrong type. 577 | */ 578 | bool 579 | check_new_method_input_types(PyObject *py_is_signed, PyObject *bit_field) 580 | { 581 | if (py_is_signed) 582 | { 583 | if (!PyBool_Check(py_is_signed)) 584 | { 585 | PyErr_SetString(PyExc_TypeError, "signed must be True or False."); 586 | return false; 587 | } 588 | } 589 | 590 | if (bit_field) 591 | { 592 | if (!PyLong_Check(bit_field)) 593 | { 594 | PyErr_SetString(PyExc_TypeError, 595 | "bit_field must be a long integer."); 596 | return false; 597 | } 598 | } 599 | 600 | return true; 601 | } 602 | 603 | /* 604 | * Produces a string representation of the arbitrary length fixed point number 605 | * as defined by scaled_value, int_bits, and frac_bits. Scientific notation is 606 | * NOT used. 607 | * 608 | * scaled_value must be a 2's compliment representation of the fixed point 609 | * number 610 | * multiplied by 2**frac bits. 611 | */ 612 | PyObject * 613 | scaled_long_to_float_str(PyObject *scaled_value, PyObject *int_bits, 614 | PyObject *frac_bits) 615 | { 616 | /* 617 | * scaled_value: this is the number multiplied by 2**frac_bits. So 618 | * to be converted to the correct value while staying as an integer, we 619 | * multiply 620 | * by 10**frac bits and then divide by 2**frac_bits (note that each negative 621 | * power of 2 can only produce, at most, a single decimal equivalent because 622 | * 1 >> 2 produces 0.5). So this is (10/2)**frac_bits = 5**frac_bits. This 623 | * will 624 | * give us an integer that can be assessed using the standard % 10 logic. 625 | */ 626 | PyObject *int_bits_is_negative, *frac_bits_is_negative; 627 | PyObject *int_string, *frac_string, *final_string; 628 | PyObject *frac_format_string, *frac_value_tuple; 629 | PyObject *scaled_value_padded; 630 | PyObject *is_negative, *scaled_value_mag, *frac_mask1, *frac_mask; 631 | PyObject *frac_part, *int_part, *frac_scale, *frac_part_corrected; 632 | 633 | long frac_bits_long, frac_dec_places, count = 0; 634 | PyObject *modulus, *modulus_is_zero; 635 | 636 | /* If we have negative int_bits, pad out the extra fractional spaces */ 637 | int_bits_is_negative = PyObject_RichCompare(int_bits, py_zero, Py_LT); 638 | 639 | if (int_bits_is_negative == Py_True) 640 | { 641 | int_bits = py_zero; // No need to inc/dec this 642 | } 643 | 644 | /* If we have negative frac_bits, pad out the extra int spaces */ 645 | frac_bits_is_negative = PyObject_RichCompare(frac_bits, py_zero, Py_LT); 646 | 647 | if (frac_bits_is_negative == Py_True) 648 | { 649 | PyObject *left_shift = PyNumber_Absolute(frac_bits); 650 | scaled_value_padded = PyNumber_Lshift(scaled_value, left_shift); 651 | Py_DECREF(left_shift); 652 | frac_bits = py_zero; // No need to inc/dec this 653 | } 654 | else 655 | { 656 | Py_INCREF(scaled_value); 657 | scaled_value_padded = scaled_value; 658 | } 659 | 660 | is_negative = PyObject_RichCompare(scaled_value_padded, py_zero, Py_LT); 661 | scaled_value_mag = PyNumber_Absolute(scaled_value_padded); 662 | frac_mask1 = PyNumber_Lshift(py_one, frac_bits); 663 | frac_mask = PyNumber_Subtract(frac_mask1, py_one); 664 | frac_part = PyNumber_And(scaled_value_mag, frac_mask); 665 | int_part = PyNumber_Rshift(scaled_value_mag, frac_bits); 666 | 667 | frac_scale = PyNumber_Power(py_five, frac_bits, Py_None); 668 | frac_part_corrected = PyNumber_Multiply(frac_part, frac_scale); 669 | 670 | /* Need to get rid of any trailing zeros before creating string */ 671 | frac_bits_long = PyLong_AsLong(frac_bits), count = 0; 672 | frac_dec_places = frac_bits_long; 673 | modulus = PyNumber_Remainder(frac_part_corrected, py_ten); 674 | modulus_is_zero = PyObject_RichCompare(modulus, py_zero, Py_EQ); 675 | 676 | while (count < frac_bits_long && modulus_is_zero == Py_True) 677 | { 678 | PyObject *tmp = frac_part_corrected; 679 | frac_part_corrected = PyNumber_FloorDivide(tmp, py_ten); 680 | Py_DECREF(tmp); 681 | 682 | Py_DECREF(modulus); 683 | Py_DECREF(modulus_is_zero); 684 | modulus = PyNumber_Remainder(frac_part_corrected, py_ten); 685 | modulus_is_zero = PyObject_RichCompare(modulus, py_zero, Py_EQ); 686 | 687 | frac_dec_places--; 688 | count++; 689 | } 690 | 691 | Py_DECREF(modulus); 692 | Py_DECREF(modulus_is_zero); 693 | 694 | int_string = FP_METHOD(int_part, tp_str)(int_part); 695 | 696 | frac_format_string = PyUnicode_FromFormat("%%0%ldd", frac_dec_places); 697 | frac_value_tuple = PyTuple_Pack(1, frac_part_corrected); 698 | frac_string = PyUnicode_Format(frac_format_string, frac_value_tuple); 699 | 700 | if (is_negative == Py_True) 701 | { 702 | final_string = PyUnicode_FromString("-"); 703 | unicode_concat(&final_string, int_string); 704 | Py_DECREF(int_string); 705 | } 706 | else 707 | { 708 | final_string = int_string; 709 | } 710 | 711 | unicode_concat(&final_string, decimal_point_str); 712 | unicode_concat(&final_string, frac_string); 713 | 714 | Py_DECREF(scaled_value_padded); 715 | Py_DECREF(frac_string); 716 | Py_DECREF(is_negative); 717 | Py_DECREF(int_bits_is_negative); 718 | Py_DECREF(frac_bits_is_negative); 719 | Py_DECREF(scaled_value_mag); 720 | Py_DECREF(frac_mask1); 721 | Py_DECREF(frac_mask); 722 | Py_DECREF(frac_part); 723 | Py_DECREF(int_part); 724 | Py_DECREF(frac_scale); 725 | Py_DECREF(frac_part_corrected); 726 | Py_DECREF(frac_format_string); 727 | Py_DECREF(frac_value_tuple); 728 | 729 | return final_string; 730 | } 731 | 732 | /* 733 | * Convenience function to forward a function call on to a PyObject with 734 | * arguments. 735 | */ 736 | PyObject * 737 | forward_call_with_args(PyObject *obj, PyObject *method_name, PyObject *args, 738 | PyObject *kwds) 739 | { 740 | PyObject *callable = PyObject_GetAttr(obj, method_name); 741 | if (callable) 742 | { 743 | PyObject* result = NULL; 744 | 745 | if (!args) 746 | { 747 | PyObject *dummy_tup = PyTuple_New(0); 748 | result = PyObject_Call(callable, dummy_tup, kwds); 749 | Py_DECREF(dummy_tup); 750 | } 751 | else 752 | { 753 | result = PyObject_Call(callable, args, kwds); 754 | } 755 | 756 | Py_DECREF(callable); 757 | return result; 758 | } 759 | 760 | return NULL; 761 | } 762 | 763 | bool 764 | FpBinary_IntCheck(PyObject *ob) 765 | { 766 | #if PY_MAJOR_VERSION >= 3 767 | 768 | return false; 769 | 770 | #else 771 | 772 | return PyInt_Check(ob); 773 | 774 | #endif 775 | } 776 | 777 | /* 778 | * Will take the input and: 779 | * - if it is NOT a PyLong, will attempt to convert to a PyLong 780 | * - if it IS a PyLong, will increment the ref count and return it 781 | * 782 | * Note that this function should only be called if the input ob is either 783 | * a PyLong or a PyInt. 784 | */ 785 | PyObject * 786 | FpBinary_EnsureIsPyLong(PyObject *ob) 787 | { 788 | #if PY_MAJOR_VERSION >= 3 789 | 790 | Py_INCREF(ob); 791 | return ob; 792 | 793 | #else 794 | 795 | if (PyLong_Check(ob)) 796 | { 797 | Py_INCREF(ob); 798 | return ob; 799 | } 800 | else 801 | { 802 | return PyLong_FromLong(PyInt_AsLong(ob)); 803 | } 804 | 805 | #endif 806 | } 807 | 808 | /* 809 | * Will take the input and: 810 | * - if it is NOT a PyInt AND platform supports distinction between Int and 811 | * Long, 812 | * will convert to a PyInt 813 | * - if it IS a PyLong, will increment the ref count and return it 814 | * 815 | * Note that this function should only be called if the input ob is either 816 | * a PyLong or a PyInt. 817 | */ 818 | PyObject * 819 | FpBinary_TryConvertToPyInt(PyObject *ob) 820 | { 821 | #if PY_MAJOR_VERSION >= 3 822 | 823 | Py_INCREF(ob); 824 | return ob; 825 | 826 | #else 827 | 828 | if (PyInt_Check(ob)) 829 | { 830 | Py_INCREF(ob); 831 | return ob; 832 | } 833 | else 834 | { 835 | return PyInt_FromLong(PyLong_AsLong(ob)); 836 | } 837 | 838 | #endif 839 | } 840 | 841 | void 842 | FpBinaryCommon_InitModule(void) 843 | { 844 | py_zero = PyLong_FromLong(0); 845 | py_one = PyLong_FromLong(1); 846 | py_two = PyLong_FromLong(2); 847 | py_minus_one = PyLong_FromLong(-1); 848 | py_five = PyLong_FromLong(5); 849 | py_ten = PyLong_FromLong(10); 850 | 851 | /* Tells us what type of base object was pickled */ 852 | fp_small_type_id = PyLong_FromLong(1); 853 | fp_large_type_id = PyLong_FromLong(2); 854 | 855 | copy_method_name_str = PyUnicode_FromString("__copy__"); 856 | resize_method_name_str = PyUnicode_FromString("resize"); 857 | get_is_signed_method_name_str = PyUnicode_FromString("is_signed"); 858 | get_format_method_name_str = PyUnicode_FromString("format"); 859 | str_ex_method_name_str = PyUnicode_FromString("str_ex"); 860 | complex_real_property_name_str = PyUnicode_FromString("real"); 861 | complex_imag_property_name_str = PyUnicode_FromString("imag"); 862 | py_default_format_tuple = PyTuple_Pack(2, py_one, py_zero); 863 | 864 | decimal_point_str = PyUnicode_FromString("."); 865 | add_sign_str = PyUnicode_FromString("+"); 866 | open_bracket_str = PyUnicode_FromString("("); 867 | close_bracket_str = PyUnicode_FromString(")"); 868 | j_str = PyUnicode_FromString("j"); 869 | } 870 | -------------------------------------------------------------------------------- /src/fpbinarycommon.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | #ifndef FPBINARYCOMMON_H_ 6 | #define FPBINARYCOMMON_H_ 7 | 8 | #include "Python.h" 9 | #include 10 | 11 | /* defines for compatability between v2 and v3 */ 12 | #if PY_MAJOR_VERSION >= 3 13 | 14 | #define nb_nonzero nb_bool 15 | #define nb_long nb_int 16 | 17 | #endif 18 | 19 | #ifndef Py_TPFLAGS_CHECKTYPES 20 | #define Py_TPFLAGS_CHECKTYPES 0 21 | #endif 22 | 23 | #define FP_INT_TYPE long long 24 | #define FP_INT_NUM_BITS (sizeof(FP_INT_TYPE) * 8) 25 | #define FP_INT_ALL_BITS_MASK (~((FP_INT_TYPE)0)) 26 | 27 | #define FP_UINT_TYPE unsigned long long 28 | #define FP_UINT_NUM_BITS (sizeof(FP_INT_TYPE) * 8) 29 | #define FP_UINT_MAX_SIGN_BIT (((FP_UINT_TYPE)1) << (FP_UINT_NUM_BITS - 1)) 30 | #define FP_UINT_ALL_BITS_MASK (~((FP_UINT_TYPE)0)) 31 | #define FP_UINT_MAX_VAL FP_UINT_ALL_BITS_MASK 32 | 33 | /* Not sure if this is already done somewhere - couldn't find it... 34 | * I've chosen to call "abstract" methods directly (rather than using the 35 | * PyObject_CallMethodObjArgs function) for speed. */ 36 | #define FP_METHOD(ob, method_name) Py_TYPE(ob)->method_name 37 | #define FP_NUM_METHOD(ob, method_name) Py_TYPE(ob)->tp_as_number->method_name 38 | #define FP_SQ_METHOD(ob, method_name) Py_TYPE(ob)->tp_as_sequence->method_name 39 | #define FP_MP_METHOD(ob, method_name) Py_TYPE(ob)->tp_as_mapping->method_name 40 | #define FP_NUM_METHOD_PRESENT(ob, method_name) \ 41 | (ob && (Py_TYPE(ob)->tp_as_number && FP_NUM_METHOD(ob, method_name))) 42 | 43 | #define xstr(s) str(s) 44 | #define str(s) #s 45 | 46 | /* 47 | * Will carry out method on op1 and steal the original reference to op1. 48 | */ 49 | #define FP_NUM_UNI_OP_INPLACE(op1, method) \ 50 | do \ 51 | { \ 52 | PyObject *tmp = op1; \ 53 | op1 = FP_NUM_METHOD(tmp, method)(tmp); \ 54 | Py_XDECREF(tmp); \ 55 | } while (0) 56 | 57 | /* 58 | * Will carry out method on op1 and op2 and steal the original reference 59 | * to op1. 60 | */ 61 | #define FP_NUM_BIN_OP_INPLACE(op1, op2, method) \ 62 | do \ 63 | { \ 64 | PyObject *tmp = op1; \ 65 | op1 = FP_NUM_METHOD(tmp, method)(tmp, op2); \ 66 | Py_XDECREF(tmp); \ 67 | } while (0) 68 | 69 | #define FP_GLOBAL_Doc_VAR(name) char name[] 70 | #define FP_GLOBAL_Doc_STRVAR(name, str) FP_GLOBAL_Doc_VAR(name) = PyDoc_STR(str) 71 | 72 | /* Packaging up code that changes the point of a PyObject field */ 73 | #define FP_ASSIGN_PY_FIELD(obj, value, field) \ 74 | do \ 75 | { \ 76 | PyObject *tmp = obj->field; \ 77 | Py_XINCREF(value); \ 78 | obj->field = value; \ 79 | Py_XDECREF(tmp); \ 80 | } while (0) 81 | 82 | #define FP_BASE_PYOBJ(ob) ((PyObject *)ob) 83 | #define PYOBJ_FP_BASE(ob) ((fpbinary_base_t *)ob) 84 | #define FP_BASE_METHOD(ob, method_name) \ 85 | PYOBJ_FP_BASE(ob)->private_iface->method_name 86 | 87 | /* Similarly, not sure if this is defined in python 2, so adding my own to make 88 | * porting to 3 easier. 89 | */ 90 | 91 | #define FPBINARY_RETURN_NOT_IMPLEMENTED \ 92 | return Py_INCREF(Py_NotImplemented), Py_NotImplemented 93 | 94 | typedef enum { 95 | ROUNDING_NEAR_POS_INF = 1, 96 | ROUNDING_DIRECT_NEG_INF = 2, 97 | ROUNDING_NEAR_ZERO = 3, 98 | ROUNDING_DIRECT_ZERO = 4, 99 | ROUNDING_NEAR_EVEN = 5, 100 | } fp_round_mode_t; 101 | 102 | typedef enum { 103 | OVERFLOW_WRAP = 0, 104 | OVERFLOW_SAT = 1, 105 | OVERFLOW_EXCEP = 2, 106 | } fp_overflow_mode_t; 107 | 108 | /* Pseudo polymorphism to speed up calling functions that are in the "methods" 109 | * struct or not exposed to python users at all. 110 | */ 111 | typedef struct 112 | { 113 | FP_INT_TYPE (*get_int_bits)(PyObject *); 114 | FP_INT_TYPE (*get_frac_bits)(PyObject *); 115 | FP_UINT_TYPE (*get_total_bits)(PyObject *); 116 | bool (*is_signed)(PyObject *); 117 | PyObject *(*resize)(PyObject *self, PyObject *, PyObject *); 118 | PyObject *(*str_ex)(PyObject *self); 119 | PyObject *(*to_signed)(PyObject *obj, PyObject *args); 120 | PyObject *(*bits_to_signed)(PyObject *, PyObject *); 121 | PyObject *(*copy)(PyObject *, PyObject *); 122 | PyObject *(*fp_getformat)(PyObject *, void *); 123 | PyObject *(*fp_from_double)(double, FP_INT_TYPE, FP_INT_TYPE, bool, 124 | fp_overflow_mode_t, fp_round_mode_t); 125 | PyObject *(*fp_from_bits_pylong)(PyObject *, FP_INT_TYPE, FP_INT_TYPE, 126 | bool); 127 | 128 | PyObject *(*getitem)(PyObject *, PyObject *); 129 | 130 | bool (*build_pickle_dict)(PyObject *self, PyObject *dict); 131 | 132 | } fpbinary_private_iface_t; 133 | 134 | typedef struct 135 | { 136 | PyObject_HEAD fpbinary_private_iface_t *private_iface; 137 | } fpbinary_base_t; 138 | 139 | extern PyObject *FpBinaryOverflowException; 140 | extern PyObject *py_zero; 141 | extern PyObject *py_one; 142 | extern PyObject *py_two; 143 | extern PyObject *py_minus_one; 144 | 145 | /* For pickling base objects */ 146 | extern PyObject *fp_small_type_id; 147 | extern PyObject *fp_large_type_id; 148 | 149 | extern PyObject *copy_method_name_str; 150 | extern PyObject *resize_method_name_str; 151 | extern PyObject *get_format_method_name_str; 152 | extern PyObject *get_is_signed_method_name_str; 153 | extern PyObject *str_ex_method_name_str; 154 | extern PyObject *complex_real_property_name_str; 155 | extern PyObject *complex_imag_property_name_str; 156 | extern PyObject *py_default_format_tuple; 157 | 158 | /* Useful, reusable strings. 159 | * Careful using these with concat methods - check if a reference is stolen... 160 | */ 161 | extern PyObject *decimal_point_str; 162 | extern PyObject *add_sign_str; 163 | extern PyObject *j_str; 164 | extern PyObject *open_bracket_str; 165 | extern PyObject *close_bracket_str; 166 | 167 | FP_UINT_TYPE fp_uint_lshift(FP_UINT_TYPE value, FP_UINT_TYPE num_shifts); 168 | FP_UINT_TYPE fp_uint_rshift(FP_UINT_TYPE value, FP_UINT_TYPE num_shifts); 169 | 170 | void unicode_concat(PyObject **left, PyObject *right); 171 | 172 | /* Required for compatibility between v2 and v3 */ 173 | bool FpBinary_IntCheck(PyObject *ob); 174 | PyObject *FpBinary_EnsureIsPyLong(PyObject *ob); 175 | PyObject *FpBinary_TryConvertToPyInt(PyObject *ob); 176 | int FpBinary_TpCompare(PyObject *op1, PyObject *op2); 177 | 178 | void FpBinaryCommon_InitModule(void); 179 | 180 | bool fp_binary_new_params_parse(PyObject *args, PyObject *kwds, long *int_bits, 181 | long *frac_bits, bool *is_signed, double *value, 182 | PyObject **bit_field, 183 | PyObject **format_instance); 184 | bool fp_binary_subscript_get_item_index(PyObject *item, Py_ssize_t *index); 185 | bool fp_binary_subscript_get_item_start_stop(PyObject *item, Py_ssize_t *start, 186 | Py_ssize_t *stop, 187 | Py_ssize_t assumed_length); 188 | PyObject *calc_scaled_val_bits(PyObject *obj, FP_UINT_TYPE frac_bits); 189 | void calc_double_to_fp_params(double input_value, double *scaled_value, 190 | FP_INT_TYPE *int_bits, FP_INT_TYPE *frac_bits); 191 | void calc_pyint_to_fp_params(PyObject *input_value, PyObject **scaled_value, 192 | FP_INT_TYPE *int_bits); 193 | bool 194 | get_best_int_frac_bits(PyObject *obj, FP_INT_TYPE *int_bits, FP_INT_TYPE *frac_bits); 195 | PyObject *fp_uint_as_pylong(FP_UINT_TYPE value); 196 | PyObject *fp_int_as_pylong(FP_INT_TYPE value); 197 | FP_UINT_TYPE pylong_as_fp_uint(PyObject *val); 198 | FP_INT_TYPE pylong_as_fp_int(PyObject *val); 199 | void build_scaled_bits_from_pyfloat(PyObject *value, PyObject *frac_bits, 200 | fp_round_mode_t round_mode, 201 | PyObject **output_obj); 202 | bool extract_fp_format_from_tuple(PyObject *format_tuple_param, 203 | PyObject **int_bits, PyObject **frac_bits); 204 | bool extract_fp_format_ints_from_tuple(PyObject *format_tuple_param, FP_INT_TYPE *int_bits, 205 | FP_INT_TYPE *frac_bits); 206 | bool check_new_method_input_types(PyObject *py_is_signed, PyObject *bit_field); 207 | PyObject *scaled_long_to_float_str(PyObject *scaled_value, PyObject *int_bits, 208 | PyObject *frac_bits); 209 | PyObject * 210 | forward_call_with_args(PyObject *obj, PyObject *method_name, PyObject *args, 211 | PyObject *kwds); 212 | 213 | /* 214 | * Macro to check if the PyObject obj is of a type that FpBinary should be able 215 | * to do arithmetic operations with. 216 | */ 217 | #define check_supported_builtin_int(obj) (PyLong_Check(obj) || FpBinary_IntCheck(obj)) 218 | #define check_supported_builtin_float(obj) (PyFloat_Check(obj)) 219 | #define check_supported_builtin(obj) (check_supported_builtin_int(obj) || check_supported_builtin_float(obj)) 220 | 221 | #endif /* FPBINARYCOMMON_H_ */ 222 | -------------------------------------------------------------------------------- /src/fpbinarycomplexobject.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | #ifndef FPBINARYCOMPLEXOBJECT_H 6 | #define FPBINARYCOMPLEXOBJECT_H 7 | 8 | #include "fpbinarycommon.h" 9 | 10 | typedef struct 11 | { 12 | PyObject_HEAD 13 | PyObject *real; 14 | PyObject *imag; 15 | } FpBinaryComplexObject; 16 | 17 | extern PyTypeObject FpBinaryComplex_Type; 18 | 19 | #define PYOBJ_TO_REAL_FP(ob) (((FpBinaryComplexObject *)ob)->real) 20 | #define PYOBJ_TO_IMAG_FP(ob) (((FpBinaryComplexObject *)ob)->imag) 21 | #define PYOBJ_TO_REAL_FP_PYOBJ(ob) ((PyObject *) PYOBJ_TO_REAL_FP(ob)) 22 | #define PYOBJ_TO_IMAG_FP_PYOBJ(ob) ((PyObject *) PYOBJ_TO_IMAG_FP(ob)) 23 | 24 | #define FpBinaryComplex_CheckExact(op) (Py_TYPE(op) == &FpBinaryComplex_Type) 25 | #define FpBinaryComplex_Check(op) PyObject_TypeCheck(op, &FpBinaryComplex_Type) 26 | 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/fpbinaryenums.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | /****************************************************************************** 6 | * 7 | * Basic objects to make the use of enumerated values easier pre python3. 8 | * For each enum type, a PyTypeObject is defined which has an int field for 9 | * each possible value. The fields are exposed to python users as get 10 | * properties. 11 | * 12 | *****************************************************************************/ 13 | 14 | #include "fpbinaryenums.h" 15 | #include "structmember.h" 16 | 17 | /* 18 | * 19 | * Overflow enumeration class. 20 | * 21 | */ 22 | 23 | PyDoc_STRVAR( 24 | fpbinaryoverflow_enum_doc, 25 | "Provides static fields for overflow modes.\n" 26 | "\n" 27 | "Attributes\n" 28 | "----------\n" 29 | "wrap : int\n" 30 | " This is essentially the truncation of any int bits that are being " 31 | "removed (usually via a resize() call). \n" 32 | " For signed types, this mayresult in a positive number becoming " 33 | "negative and vice versa.\n" 34 | "\n" 35 | "sat : int\n" 36 | " If an overflow occurs, the value is railed to the min or max value of " 37 | "the new bit format.\n" 38 | "\n" 39 | "excep : int\n" 40 | " If an overflow occurs, an FpBinaryOverflowException is raised.\n"); 41 | PyTypeObject OverflowEnumType = { 42 | PyVarObject_HEAD_INIT(NULL, 0).tp_name = "fpbinary.OverflowEnum", 43 | .tp_doc = fpbinaryoverflow_enum_doc, .tp_itemsize = 0, 44 | .tp_flags = Py_TPFLAGS_DEFAULT, 45 | }; 46 | 47 | /* 48 | * 49 | * Rounding enumeration class. 50 | * 51 | */ 52 | 53 | PyDoc_STRVAR( 54 | fpbinaryrounding_enum_doc, 55 | "Provides static fields for rounding modes.\n" 56 | "The enums will generally be of the 'direct' or 'near' types. \n" 57 | "'near' implies that a rule is applied if the value is exactly " 58 | "halfway between the representable value. \n" 59 | "'direct' implies that no consideration is given to these halfway " 60 | "situations. \n" 61 | "\n" 62 | "Attributes\n" 63 | "----------\n" 64 | "near_pos_inf : int\n" 65 | " The value is rounded towards the nearest value representable by the " 66 | "new format. \n" 67 | " Ties (i.e. X.5) are rounded towards positive infinity.\n" 68 | " The IEEE 754 standard does not have an equivalent, but this is common " 69 | "in " 70 | "general arithmetic that many call 'rounding up'.\n" 71 | " Examples: *5.5 and 5.6 both go to 6.0 (assuming resizing to zero " 72 | "fract_bits). \n" 73 | " -5.25 goes to -5.0, -5.375 goes to -5.5 (assuming resizing to one " 74 | "fract_bit).* \n" 75 | "\n" 76 | "direct_neg_inf : int\n" 77 | " The value is rounded in the negative direction to the nearest " 78 | " value representable by the new format. \n" 79 | " This is a clean truncate of bits without any other processing. It is " 80 | " often called 'flooring'. \n" 81 | " This is the mode the VHDL fixed point library applies when using the " 82 | "'truncate' mode. \n" 83 | " The IEEE 754 standard calls this 'Round toward -infinity'. \n" 84 | " Examples: *5.5 and 5.6 both go to 5.0 (assuming resizing to zero " 85 | "fract_bits). \n" 86 | " -5.25 and -5.375 both go to -5.5 (assuming resizing to one " 87 | "fract_bit).* \n" 88 | "\n" 89 | "near_zero : int\n" 90 | " The value is rounded towards the nearest value representable by the " 91 | "new format. \n" 92 | " Ties (i.e. X.5) are rounded towards zero. \n" 93 | " The IEEE 754 standard does not have an equivalent.\n" 94 | " Examples: *5.5 goes to 5.0, 5.6 goes to 6.0 (assuming resizing to " 95 | "zero fract_bits).\n" 96 | " -5.25 goes to -5.0, -5.375 goes to -5.5 (assuming resizing to one " 97 | "fract_bit).* \n" 98 | "\n" 99 | "direct_zero : int\n" 100 | " The value is rounded in the direction towards zero to the nearest " 101 | "value representable by the new format. \n" 102 | " The IEEE 754 standard calls this 'Round toward 0' or 'truncation'. \n" 103 | " Examples: *5.5 and 5.6 both go to 5.0 (assuming resizing to zero " 104 | "fract_bits). \n" 105 | " -5.25 and -5.375 both go to -5.0 (assuming resizing to one " 106 | "fract_bit).* \n" 107 | "\n" 108 | "near_even : int\n" 109 | " The value is rounded towards the nearest value representable by the " 110 | "new format. \n" 111 | " Ties (i.e. X.5) are rounded towards the 'even' representation. This " 112 | "means that, after rounding a tie, the lsb is zero. \n" 113 | " The IEEE 754 standard calls this 'Round to nearest, ties to even'. \n" 114 | " This is also the mode the Python 3 round() function applies. \n" 115 | " This is also the mode the VHDL fixed point library applies when using " 116 | "the 'round' mode. \n" 117 | " Examples: *5.5 and 6.5 both go to 6.0 (assuming resizing to zero " 118 | "fract_bits). \n" 119 | " -5.5 and -6.5 both go to -6.0 (assuming resizing to zero fract_bits). " 120 | "\n" 121 | " 5.75 goes to 6.0, 5.25 goes to 5.0 (assuming resizing to one " 122 | "fract_bit).* \n"); 123 | PyTypeObject RoundingEnumType = { 124 | PyVarObject_HEAD_INIT(NULL, 0).tp_name = "fpbinary.RoundingEnum", 125 | .tp_doc = fpbinaryrounding_enum_doc, .tp_itemsize = 0, 126 | .tp_flags = Py_TPFLAGS_DEFAULT, 127 | }; 128 | 129 | void 130 | fpbinaryenums_InitModule(void) 131 | { 132 | /* Set type object attribute dicts with our fields for both enums */ 133 | 134 | PyDict_SetItemString(OverflowEnumType.tp_dict, "wrap", 135 | PyLong_FromLong((long)OVERFLOW_WRAP)); 136 | PyDict_SetItemString(OverflowEnumType.tp_dict, "sat", 137 | PyLong_FromLong((long)OVERFLOW_SAT)); 138 | PyDict_SetItemString(OverflowEnumType.tp_dict, "excep", 139 | PyLong_FromLong((long)OVERFLOW_EXCEP)); 140 | 141 | PyDict_SetItemString(RoundingEnumType.tp_dict, "near_pos_inf", 142 | PyLong_FromLong((long)ROUNDING_NEAR_POS_INF)); 143 | PyDict_SetItemString(RoundingEnumType.tp_dict, "direct_neg_inf", 144 | PyLong_FromLong((long)ROUNDING_DIRECT_NEG_INF)); 145 | PyDict_SetItemString(RoundingEnumType.tp_dict, "near_zero", 146 | PyLong_FromLong((long)ROUNDING_NEAR_ZERO)); 147 | PyDict_SetItemString(RoundingEnumType.tp_dict, "direct_zero", 148 | PyLong_FromLong((long)ROUNDING_DIRECT_ZERO)); 149 | PyDict_SetItemString(RoundingEnumType.tp_dict, "near_even", 150 | PyLong_FromLong((long)ROUNDING_NEAR_EVEN)); 151 | } 152 | -------------------------------------------------------------------------------- /src/fpbinaryenums.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | #ifndef FPBINARYENUMS_H_ 6 | #define FPBINARYENUMS_H_ 7 | 8 | #include "fpbinarycommon.h" 9 | 10 | extern PyTypeObject OverflowEnumType; 11 | extern PyTypeObject RoundingEnumType; 12 | 13 | void fpbinaryenums_InitModule(void); 14 | 15 | #endif /* FPBINARYENUMS_H_ */ 16 | -------------------------------------------------------------------------------- /src/fpbinaryglobaldoc.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | /****************************************************************************** 6 | * 7 | * Doc strings that can be used by more than one object are defined here. 8 | * 9 | *****************************************************************************/ 10 | 11 | #include "fpbinaryglobaldoc.h" 12 | 13 | FP_GLOBAL_Doc_STRVAR( 14 | resize_doc, 15 | "resize(format, overflow_mode=fpbinary.OverflowEnum.wrap, " 16 | "round_mode=fpbinary.RoundingEnum.direct_neg_inf)\n" 17 | "--\n" 18 | "\n" 19 | "Resizes the fixed point object IN PLACE to the format described by " 20 | "format.\n" 21 | "\n" 22 | "Parameters\n" 23 | "----------\n" 24 | "format : length 2 tuple or instance of this object.\n" 25 | " If tuple, will be resized to format[0] int bits and format[1] frac " 26 | "bits.\n" 27 | " If instance of this type, the format will be taken from format.\n" 28 | "\n" 29 | "overflow_mode : fpbinary.OverflowEnum, optional\n" 30 | " Specifies how to deal with overflows when int bits are reduced.\n" 31 | " Default is wrap.\n" 32 | "\n" 33 | "round_mode : fpbinary.RoundingEnum, optional\n" 34 | " Specifies how to deal with rounding when frac bits are reduced.\n" 35 | " Default is direct_neg_inf.\n" 36 | "\n" 37 | "Returns\n" 38 | "----------\n" 39 | "FpBinary/FpBinaryComplex\n" 40 | " `self`\n"); 41 | 42 | FP_GLOBAL_Doc_STRVAR( 43 | str_ex_doc, 44 | "str_ex()\n" 45 | "--\n" 46 | "\n" 47 | "Returns a str displaying the full precision value of the fixed point " 48 | "object.\n" 49 | "This is useful when the fixed point value has more bits than native " 50 | "floating \n" 51 | "point types can handle. Note that scientific notation is not used.\n" 52 | "\n" 53 | "Parameters\n" 54 | "----------\n" 55 | "None\n" 56 | "\n" 57 | "Returns\n" 58 | "----------\n" 59 | "str or unicode\n" 60 | " Non-scientific notation string representation of the instance " 61 | "value.\n"); 62 | 63 | FP_GLOBAL_Doc_STRVAR( 64 | bits_to_signed_doc, 65 | "bits_to_signed()\n" 66 | "--\n" 67 | "\n" 68 | "Interprets the bits of the fixed point binary object as a 2's complement " 69 | "signed\n" 70 | "integer and returns an int. If `self` is an unsigned object, the MSB, as " 71 | "defined by\n" 72 | "the `int_bits` and `frac_bits` values, will be considered a sign bit.\n" 73 | "\n" 74 | "Parameters\n" 75 | "----------\n" 76 | "None\n" 77 | "\n" 78 | "Returns\n" 79 | "----------\n" 80 | "int\n" 81 | " The object bits interpreted as a 2's complement signed integer.\n"); 82 | 83 | FP_GLOBAL_Doc_STRVAR(copy_doc, 84 | "__copy__(self)\n" 85 | "--\n" 86 | "\n" 87 | "Performs a copy of self and returns a new object. If " 88 | "self is a wrapper object, the\n" 89 | "embedded fixed point object will also be copied.\n" 90 | "Returns\n" 91 | "----------\n" 92 | "result : type(self)\n"); 93 | 94 | FP_GLOBAL_Doc_STRVAR(format_doc, "tuple : Read-only (int_bits, frac_bits) " 95 | "tuple representing the fixed point " 96 | "format.\n"); 97 | 98 | FP_GLOBAL_Doc_STRVAR( 99 | is_signed_doc, 100 | "bool : Read-only. True if the fixed point object is signed.\n"); 101 | -------------------------------------------------------------------------------- /src/fpbinaryglobaldoc.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | #include "fpbinarycommon.h" 6 | 7 | extern FP_GLOBAL_Doc_VAR(resize_doc); 8 | extern FP_GLOBAL_Doc_VAR(str_ex_doc); 9 | extern FP_GLOBAL_Doc_VAR(bits_to_signed_doc); 10 | extern FP_GLOBAL_Doc_VAR(copy_doc); 11 | extern FP_GLOBAL_Doc_VAR(format_doc); 12 | extern FP_GLOBAL_Doc_VAR(is_signed_doc); 13 | -------------------------------------------------------------------------------- /src/fpbinarylarge.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | #ifndef FPBINARYLARGE_H 6 | #define FPBINARYLARGE_H 7 | 8 | #include "fpbinarycommon.h" 9 | 10 | typedef struct 11 | { 12 | fpbinary_base_t fpbinary_base; 13 | PyObject *int_bits; 14 | PyObject *frac_bits; 15 | PyObject *scaled_value; 16 | bool is_signed; 17 | } FpBinaryLargeObject; 18 | 19 | extern PyTypeObject FpBinary_LargeType; 20 | extern fpbinary_private_iface_t FpBinary_LargePrvIface; 21 | 22 | #define FpBinaryLarge_CheckExact(op) (Py_TYPE(op) == &FpBinary_LargeType) 23 | /* TODO: */ 24 | #define FpBinaryLarge_Check(op) FpBinaryLarge_CheckExact(op) 25 | 26 | void FpBinaryLarge_InitModule(void); 27 | 28 | /* Helper functions for use of top client object. */ 29 | 30 | void FpBinaryLarge_FormatAsInts(PyObject *self, FP_INT_TYPE *out_int_bits, 31 | FP_INT_TYPE *out_frac_bits); 32 | PyObject *FpBinaryLarge_BitsAsPylong(PyObject *obj); 33 | bool FpBinaryLarge_IsSigned(PyObject *obj); 34 | 35 | PyObject *FpBinaryLarge_FromDouble(double value, FP_INT_TYPE int_bits, 36 | FP_INT_TYPE frac_bits, bool is_signed, 37 | fp_overflow_mode_t overflow_mode, 38 | fp_round_mode_t round_mode); 39 | PyObject *FpBinaryLarge_FromBitsPylong(PyObject *scaled_value, 40 | FP_INT_TYPE int_bits, 41 | FP_INT_TYPE frac_bits, bool is_signed); 42 | 43 | PyObject *FpBinaryLarge_FromPickleDict(PyObject *dict); 44 | bool FpBinaryLarge_UpdatePickleDict(PyObject *self, PyObject *dict); 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/fpbinarymodule.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | /****************************************************************************** 6 | * 7 | * Packaging up of publicly accessible objects into the fpbinary Python module. 8 | * 9 | *****************************************************************************/ 10 | 11 | #include "fpbinaryarrayfuncs.h" 12 | #include "fpbinarycomplexobject.h" 13 | #include "fpbinaryenums.h" 14 | #include "fpbinarylarge.h" 15 | #include "fpbinaryobject.h" 16 | #include "fpbinarysmall.h" 17 | #include "fpbinaryswitchable.h" 18 | #include "fpbinaryversion.h" 19 | 20 | #define FPBINARY_MOD_NAME "fpbinary" 21 | #define FPBINARY_MOD_DOC "Fixed point binary module." 22 | 23 | PyObject *FpBinaryOverflowException; 24 | static PyObject *FpBinaryVersionString; 25 | 26 | static PyMethodDef fpbinarymod_methods[] = { 27 | { 28 | .ml_name = "fpbinary_list_from_array", 29 | .ml_meth = (PyCFunction)FpBinary_FromArray, 30 | .ml_flags = METH_VARARGS | METH_KEYWORDS, 31 | .ml_doc = FpBinary_FromArray_doc, 32 | }, 33 | 34 | { 35 | .ml_name = "fpbinarycomplex_list_from_array", 36 | .ml_meth = (PyCFunction)FpBinaryComplex_FromArray, 37 | .ml_flags = METH_VARARGS | METH_KEYWORDS, 38 | .ml_doc = FpBinaryComplex_FromArray_doc, 39 | }, 40 | 41 | { 42 | .ml_name = "array_resize", 43 | .ml_meth = (PyCFunction)FpBinary_ArrayResize, 44 | .ml_flags = METH_VARARGS | METH_KEYWORDS, 45 | .ml_doc = FpBinary_ArrayResize_doc, 46 | }, 47 | 48 | {NULL}, /* Sentinel */ 49 | }; 50 | 51 | #if PY_MAJOR_VERSION >= 3 52 | static PyModuleDef fpbinarymoduledef = { 53 | PyModuleDef_HEAD_INIT, 54 | .m_name = FPBINARY_MOD_NAME, 55 | .m_doc = FPBINARY_MOD_DOC, 56 | .m_methods = fpbinarymod_methods, 57 | .m_size = -1, 58 | }; 59 | 60 | #define INITERROR return NULL 61 | PyMODINIT_FUNC 62 | PyInit_fpbinary(void) 63 | #else 64 | 65 | #define INITERROR return 66 | #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ 67 | #define PyMODINIT_FUNC void 68 | #endif 69 | PyMODINIT_FUNC 70 | initfpbinary(void) 71 | #endif 72 | { 73 | PyObject *m; 74 | 75 | if (PyType_Ready(&FpBinary_SmallType) < 0) 76 | INITERROR; 77 | 78 | if (PyType_Ready(&FpBinary_LargeType) < 0) 79 | INITERROR; 80 | 81 | if (PyType_Ready(&FpBinary_Type) < 0) 82 | INITERROR; 83 | 84 | if (PyType_Ready(&FpBinaryComplex_Type) < 0) 85 | INITERROR; 86 | 87 | if (PyType_Ready(&FpBinarySwitchable_Type) < 0) 88 | INITERROR; 89 | 90 | if (PyType_Ready(&OverflowEnumType) < 0) 91 | { 92 | INITERROR; 93 | } 94 | 95 | if (PyType_Ready(&RoundingEnumType) < 0) 96 | { 97 | INITERROR; 98 | } 99 | 100 | FpBinaryCommon_InitModule(); 101 | 102 | #if PY_MAJOR_VERSION >= 3 103 | m = PyModule_Create(&fpbinarymoduledef); 104 | #else 105 | m = Py_InitModule3("fpbinary", fpbinarymod_methods, 106 | "Fixed point binary module."); 107 | #endif 108 | 109 | Py_INCREF(&FpBinary_SmallType); 110 | PyModule_AddObject(m, "_FpBinarySmall", (PyObject *)&FpBinary_SmallType); 111 | 112 | Py_INCREF(&FpBinary_LargeType); 113 | PyModule_AddObject(m, "_FpBinaryLarge", (PyObject *)&FpBinary_LargeType); 114 | FpBinaryLarge_InitModule(); 115 | 116 | Py_INCREF(&FpBinary_Type); 117 | PyModule_AddObject(m, "FpBinary", (PyObject *)&FpBinary_Type); 118 | 119 | Py_INCREF(&FpBinarySwitchable_Type); 120 | PyModule_AddObject(m, "FpBinarySwitchable", 121 | (PyObject *)&FpBinarySwitchable_Type); 122 | 123 | Py_INCREF(&FpBinaryComplex_Type); 124 | PyModule_AddObject(m, "FpBinaryComplex", (PyObject *)&FpBinaryComplex_Type); 125 | 126 | /* Create enum instances */ 127 | Py_INCREF(&OverflowEnumType); 128 | PyModule_AddObject(m, "OverflowEnum", (PyObject *)&OverflowEnumType); 129 | 130 | Py_INCREF(&RoundingEnumType); 131 | PyModule_AddObject(m, "RoundingEnum", (PyObject *)&RoundingEnumType); 132 | 133 | fpbinaryenums_InitModule(); 134 | 135 | FpBinaryOverflowException = 136 | PyErr_NewException("fpbinary.FpBinaryOverflowException", NULL, NULL); 137 | PyModule_AddObject(m, "FpBinaryOverflowException", 138 | FpBinaryOverflowException); 139 | 140 | /* Doing this to ensure the version string is the default type on 141 | * each version of python. 142 | */ 143 | #if PY_MAJOR_VERSION >= 3 144 | FpBinaryVersionString = PyUnicode_FromString(FPBINARY_VERSION_STR); 145 | #else 146 | FpBinaryVersionString = PyString_FromString(FPBINARY_VERSION_STR); 147 | #endif 148 | PyModule_AddObject(m, "__version__", FpBinaryVersionString); 149 | 150 | #if PY_MAJOR_VERSION >= 3 151 | return m; 152 | #endif 153 | } 154 | -------------------------------------------------------------------------------- /src/fpbinaryobject.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | #ifndef FPBINARYOBJECT_H 6 | #define FPBINARYOBJECT_H 7 | 8 | #include "fpbinarycommon.h" 9 | 10 | typedef struct 11 | { 12 | PyObject_HEAD fpbinary_base_t *base_obj; 13 | } FpBinaryObject; 14 | 15 | #define PYOBJ_TO_BASE_FP(ob) (((FpBinaryObject *)ob)->base_obj) 16 | #define PYOBJ_TO_BASE_FP_PYOBJ(ob) ((PyObject *)PYOBJ_TO_BASE_FP(ob)) 17 | 18 | extern PyTypeObject FpBinary_Type; 19 | 20 | #define FpBinary_CheckExact(op) (Py_TYPE(op) == &FpBinary_Type) 21 | #define FpBinary_Check(op) PyObject_TypeCheck(op, &FpBinary_Type) 22 | 23 | FpBinaryObject *FpBinary_FromParams(long int_bits, long frac_bits, 24 | bool is_signed, double value, 25 | PyObject *bit_field, 26 | PyObject *format_instance); 27 | FpBinaryObject *FpBinary_FromValue(PyObject *value); 28 | void FpBinary_SetTwoInstToSameFormat(PyObject **op1, PyObject **op2); 29 | 30 | /* 31 | * Functions for client objects to easily call FpBinary user-specified methods. 32 | * These tend to use the Python-like call interfaces rather than using an insider's 33 | * knowledge of the underlying structures of FpBinary, so should be safe to use 34 | * on objects that quack like an FpBinary... 35 | */ 36 | PyObject *FpBinary_ResizeWithCInts(PyObject *value, long int_bits, long frac_bits, 37 | long round_mode, long overflow_mode); 38 | PyObject *FpBinary_ResizeWithFormatInstance(PyObject *value, PyObject *format_instance, 39 | long round_mode, long overflow_mode); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/fpbinarysmall.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | #ifndef FPBINARYSMALL_H 6 | #define FPBINARYSMALL_H 7 | 8 | #include "fpbinarycommon.h" 9 | 10 | typedef struct 11 | { 12 | fpbinary_base_t fpbinary_base; 13 | FP_INT_TYPE int_bits; 14 | FP_INT_TYPE frac_bits; 15 | FP_UINT_TYPE scaled_value; 16 | bool is_signed; 17 | } FpBinarySmallObject; 18 | 19 | extern PyTypeObject FpBinary_SmallType; 20 | extern fpbinary_private_iface_t FpBinary_SmallPrvIface; 21 | 22 | #define FpBinarySmall_CheckExact(op) (Py_TYPE(op) == &FpBinary_SmallType) 23 | /* TODO: */ 24 | #define FpBinarySmall_Check(op) FpBinarySmall_CheckExact(op) 25 | 26 | #define FP_SMALL_MAX_BITS FP_UINT_NUM_BITS 27 | 28 | /* Helper functions for use of top client object. */ 29 | 30 | void FpBinarySmall_FormatAsInts(PyObject *self, FP_INT_TYPE *out_int_bits, 31 | FP_INT_TYPE *out_frac_bits); 32 | PyObject *FpBinarySmall_BitsAsPylong(PyObject *obj); 33 | PyObject *FpBinarySmall_FromDouble(double value, FP_INT_TYPE int_bits, 34 | FP_INT_TYPE frac_bits, bool is_signed, 35 | fp_overflow_mode_t overflow_mode, 36 | fp_round_mode_t round_mode); 37 | PyObject *FpBinarySmall_FromBitsPylong(PyObject *scaled_value, 38 | FP_INT_TYPE int_bits, 39 | FP_INT_TYPE frac_bits, bool is_signed); 40 | 41 | PyObject *FpBinarySmall_FromPickleDict(PyObject *dict); 42 | bool FpBinarySmall_UpdatePickleDict(PyObject *self, PyObject *dict); 43 | 44 | bool FpBinarySmall_IsNegative(PyObject *obj); 45 | FP_UINT_TYPE fpbinarysmall_can_divide_ops(FP_UINT_TYPE op1_total_bits, 46 | FP_UINT_TYPE op2_total_bits); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/fpbinaryswitchable.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | #ifndef FPBINARYSWITCHABLE_H 6 | #define FPBINARYSWITCHABLE_H 7 | 8 | #include "fpbinaryobject.h" 9 | 10 | typedef struct 11 | { 12 | PyObject_HEAD bool fp_mode; 13 | PyObject *fp_mode_value; 14 | double dbl_mode_value; 15 | double dbl_mode_min_value; 16 | double dbl_mode_max_value; 17 | } FpBinarySwitchableObject; 18 | 19 | extern PyTypeObject FpBinarySwitchable_Type; 20 | 21 | #define FpBinarySwitchable_CheckExact(op) \ 22 | (Py_TYPE(op) == &FpBinarySwitchable_Type) 23 | #define FpBinarySwitchable_Check(op) \ 24 | PyObject_TypeCheck(op, &FpBinarySwitchable_Type) 25 | 26 | void FpBinarySwitchable_InitModule(void); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/fpbinaryversion.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under GNU General Public License 2.0 - see LICENSE 3 | *****************************************************************************/ 4 | 5 | /****************************************************************************** 6 | * 7 | * This is where the fpbinary package version is defined. 8 | * Don't change the format of the defines - they are assumed by setup.py . 9 | * 10 | *****************************************************************************/ 11 | 12 | #ifndef FPBINARYVERSION_H_ 13 | #define FPBINARYVERSION_H_ 14 | 15 | #include "fpbinarycommon.h" 16 | 17 | /* VERSION_STRING needs to be set when the compiler 18 | * is invoked (normally done by setup.py). 19 | */ 20 | #define FPBINARY_VERSION_STR xstr(VERSION_STRING) 21 | 22 | #endif /* FPBINARYVERSION_H_ */ 23 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/__init__.py -------------------------------------------------------------------------------- /tests/data/pickletest_v2_7_12_p2_linux2_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v2_7_12_p2_linux2_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_5_2_p2_linux_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_5_2_p2_linux_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_5_2_p3_linux_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_5_2_p3_linux_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_5_2_p4_linux_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_5_2_p4_linux_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_6_5_p2_linux_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_6_5_p2_linux_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_6_5_p2_win32_32.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_6_5_p2_win32_32.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_6_5_p2_win32_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_6_5_p2_win32_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_6_5_p3_linux_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_6_5_p3_linux_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_6_5_p3_win32_32.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_6_5_p3_win32_32.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_6_5_p3_win32_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_6_5_p3_win32_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_6_5_p4_linux_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_6_5_p4_linux_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_6_5_p4_win32_32.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_6_5_p4_win32_32.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_6_5_p4_win32_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_6_5_p4_win32_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_7_5_p2_win32_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_7_5_p2_win32_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_7_5_p3_win32_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_7_5_p3_win32_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_7_5_p4_win32_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_7_5_p4_win32_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_8_0_p2_win32_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_8_0_p2_win32_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_8_0_p3_win32_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_8_0_p3_win32_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_8_0_p4_win32_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_8_0_p4_win32_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_8_0_p5_win32_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_8_0_p5_win32_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_8_2_p2_linux_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_8_2_p2_linux_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_8_2_p3_linux_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_8_2_p3_linux_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_8_2_p4_linux_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_8_2_p4_linux_64.data -------------------------------------------------------------------------------- /tests/data/pickletest_v3_8_2_p5_linux_64.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smlgit/fpbinary/8dd1fc32a695a46db09d93c1ebda448d71c4548c/tests/data/pickletest_v3_8_2_p5_linux_64.data -------------------------------------------------------------------------------- /tests/data/readme: -------------------------------------------------------------------------------- 1 | The win32 labelled files were generated on a Windows 10 Home (version 1703) x64 processor 64-bit OS. Note that python's platform variable labels windows systems as "win32". -------------------------------------------------------------------------------- /tests/fpbinary_mem_leak_test.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | import time 3 | import gc 4 | import fpbinary 5 | import numpy as np 6 | 7 | 8 | def create_float_obj(quantity=1000000): 9 | l = [0.001 * j for j in range(quantity)] 10 | 11 | 12 | def create_fpbinarycomplex_value_param_obj(quantity=1000000): 13 | l = [fpbinary.FpBinaryComplex(8, 8, value=1.05 + 0.25j) for j in range(quantity)] 14 | 15 | 16 | def create_fpbinarycomplex_obj(quantity=1000000): 17 | l = [fpbinary.FpBinaryComplex(8, 8) for j in range(quantity)] 18 | 19 | 20 | def create_fpbinarycomplex_numpy_obj(quantity=1000000): 21 | ar = np.array([fpbinary.FpBinaryComplex(8, 8) for j in range(quantity)]) 22 | 23 | 24 | def create_fpbinaryswitchable_obj_float_mode(quantity=1000000): 25 | l = [fpbinary.FpBinarySwitchable(False, float_value=quantity / 0.3) for j in range(quantity)] 26 | 27 | 28 | def create_fpbinaryswitchable_obj_float_mode_npfloat(quantity=1000000): 29 | l = [fpbinary.FpBinarySwitchable(False, float_value=np.float64(quantity / 0.3)) for j in range(quantity)] 30 | 31 | 32 | def create_fpbinaryswitchable_obj_fp_mode(quantity=1000000): 33 | l = [fpbinary.FpBinarySwitchable(False, 34 | fp_value=fpbinary.FpBinary(8, 8, True, quantity / 0.3), 35 | float_value=quantity / 0.3) 36 | for j in range(quantity)] 37 | 38 | 39 | def resize_fpbinary_obj(quantity=1000000): 40 | l = [fpbinary.FpBinary(8, 8) for j in range(quantity)] 41 | for obj in l: 42 | obj.resize((4, 4)) 43 | 44 | 45 | def resize_fpbinarycomplex_obj(quantity=1000000): 46 | l = [fpbinary.FpBinaryComplex(8, 8) for j in range(quantity)] 47 | for obj in l: 48 | obj.resize((4, 4)) 49 | 50 | 51 | def basic_arith_fpbinary(quantity): 52 | ops_1 = [fpbinary.FpBinary(8, 8, value=0.1*j) for j in range(1, quantity + 1)] 53 | ops_2 = [fpbinary.FpBinary(8, 8, value=-0.5*j) for j in range(1, quantity + 1)] 54 | 55 | for i in range(len(ops_1)): 56 | add = ops_1[i] + ops_2[i] 57 | sub = ops_1[i] - ops_2[i] 58 | mult = ops_1[i] * ops_2[i] 59 | divide = ops_1[i] / ops_2[i] 60 | neg = -ops_1[i] 61 | ab = abs(ops_1[i]) 62 | po = ops_1[i] ** 2 63 | ls = ops_1[i] << 3 64 | rs = ops_1[i] >> 3 65 | 66 | 67 | def basic_arith_fpbinarycomplex(quantity): 68 | ops_1 = [fpbinary.FpBinaryComplex(8, 8, value=0.1*j - 0.1*j*1.0j) for j in range(1, quantity + 1)] 69 | ops_2 = [fpbinary.FpBinaryComplex(8, 8, value=-0.5*j + 0.2*j*1.0j) for j in range(1, quantity + 1)] 70 | 71 | for i in range(len(ops_1)): 72 | add = ops_1[i] + ops_2[i] 73 | sub = ops_1[i] - ops_2[i] 74 | mult = ops_1[i] * ops_2[i] 75 | divide = ops_1[i] / ops_2[i] 76 | neg = -ops_1[i] 77 | ab = abs(ops_1[i]) 78 | po = ops_1[i]**2 79 | ls = ops_1[i] << 3 80 | rs = ops_1[i] >> 3 81 | 82 | 83 | def fpbinarycomplex_conjugate(quantity): 84 | ops_1 = [fpbinary.FpBinaryComplex(8, 8, value=0.1*j - 0.1*j*1.0j) for j in range(1, quantity + 1)] 85 | 86 | for i in range(len(ops_1)): 87 | conj = ops_1[i].conjugate() 88 | 89 | 90 | def fpbinary_array_ops(quantity): 91 | float_l = [0.001 * j for j in range(quantity)] 92 | 93 | fpbinary_l = fpbinary.fpbinary_list_from_array(float_l, 8, 8) 94 | fpbinary.array_resize(fpbinary_l, (4, 6)) 95 | 96 | 97 | def fpbinarycomplex_array_ops(quantity): 98 | complex_l = [0.001 * j - 0.02j * j for j in range(quantity)] 99 | 100 | fpbinarycomplex_l = fpbinary.fpbinarycomplex_list_from_array(complex_l, 8, 8) 101 | fpbinary.array_resize(fpbinarycomplex_l, (4, 6)) 102 | 103 | 104 | funcs = { 105 | 'fpbinarycomplex': create_fpbinarycomplex_obj, 106 | 'fpbinarycomplex_float_param': create_fpbinarycomplex_value_param_obj, 107 | 'fpbinarycomplex_numpy': create_fpbinarycomplex_value_param_obj, 108 | 'fpbinaryswitchable_float_mode': create_fpbinaryswitchable_obj_float_mode, 109 | 'fpbinaryswitchable_float_mode_npfloat': create_fpbinaryswitchable_obj_float_mode_npfloat, 110 | 'fpbinaryswitchable_fp_mode': create_fpbinaryswitchable_obj_fp_mode, 111 | 'fpbinary_resize': resize_fpbinary_obj, 112 | 'fpbinarycomplex_resize': resize_fpbinarycomplex_obj, 113 | 'fpbinary_basic_arith': basic_arith_fpbinary, 114 | 'fpbinarycomplex_basic_arith': basic_arith_fpbinarycomplex, 115 | 'fpbinarycomplex_conjugate': fpbinarycomplex_conjugate, 116 | 'fpbinary_array_ops': fpbinary_array_ops, 117 | 'fpbinarycomplex_array_ops': fpbinarycomplex_array_ops, 118 | } 119 | 120 | 121 | def fpbinary_func_test(create_func_name, iterations=16, objects_per_iteration=1000000): 122 | 123 | proc = psutil.Process() 124 | 125 | start_mem = None 126 | 127 | for i in range(iterations): 128 | 129 | funcs[create_func_name](objects_per_iteration) 130 | 131 | if start_mem is None: 132 | start_mem = proc.memory_info().rss 133 | 134 | gc.collect() 135 | time.sleep(1) 136 | mem_increase = proc.memory_info().rss - start_mem 137 | 138 | if mem_increase > 0.5 * start_mem: 139 | raise MemoryError('Memory increased by {} \%'.format(mem_increase / start_mem * 100)) 140 | 141 | 142 | def run_all_leak_funcs(iterations=16, objects_per_iteration=100000): 143 | for func_name in funcs.keys(): 144 | print(func_name + '...') 145 | fpbinary_func_test(func_name, iterations, objects_per_iteration) 146 | 147 | print('No memory leaks detected.') 148 | 149 | 150 | def main(): 151 | run_all_leak_funcs() 152 | 153 | 154 | if __name__ == '__main__': 155 | main() 156 | -------------------------------------------------------------------------------- /tests/fpbinary_profiling_test.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import glob 3 | import cProfile 4 | import pstats 5 | from argparse import ArgumentParser 6 | from fpbinary import FpBinary, FpBinarySwitchable, _FpBinarySmall, _FpBinaryLarge, OverflowEnum, RoundingEnum 7 | 8 | NUM_OBJECT_CREATIONS = 100000 9 | NUM_MULTIPLIES = 100000 10 | NUM_ADDS = 100000 11 | NUM_RESIZES = 100000 12 | 13 | 14 | def build_create_params_float(int_bits, frac_bits, val): 15 | return (val,) 16 | 17 | 18 | def build_create_params_fpbinary(int_bits, frac_bits, val): 19 | return (int_bits, frac_bits, True, val) 20 | 21 | 22 | def build_create_params_fpswitchable_fpmode(int_bits, frac_bits, val): 23 | return (True, 24 | FpBinary(int_bits=int_bits, frac_bits=frac_bits, signed=True, value=val), 25 | 0.0) 26 | 27 | 28 | def build_create_params_fpswitchable_non_fpmode(int_bits, frac_bits, val): 29 | return (False, 30 | FpBinary(int_bits=int_bits, frac_bits=frac_bits, signed=True, value=val), 31 | val) 32 | 33 | 34 | def test_create(num_of_creates, class_type, build_param_func, int_bits, frac_bits, value): 35 | params = build_param_func(int_bits, frac_bits, value) 36 | for i in range(0, num_of_creates): 37 | res = class_type(*params) 38 | 39 | 40 | def test_multiply(num_of_multiplies, class_type, build_param_func, 41 | int_bits, frac_bits, val1, val2): 42 | num1 = class_type(*build_param_func(int_bits, frac_bits, val1)) 43 | num2 = class_type(*build_param_func(int_bits, frac_bits, val2)) 44 | 45 | for i in range(0, num_of_multiplies): 46 | res = num1 * num2 47 | 48 | 49 | def test_add(num_of_adds, class_type, build_param_func, 50 | int_bits, frac_bits, val1, val2): 51 | num1 = class_type(*build_param_func(int_bits, frac_bits, val1)) 52 | num2 = class_type(*build_param_func(int_bits, frac_bits, val2)) 53 | 54 | for i in range(0, num_of_adds): 55 | res = num1 + num2 56 | 57 | 58 | def test_resize(num_of_resizes, class_type, build_param_func, 59 | start_int_bits, start_frac_bits, resize_int_bits, 60 | resize_frac_bits, start_val, overflow_mode, round_mode): 61 | num1 = class_type(*build_param_func(start_int_bits, start_frac_bits, start_val)) 62 | 63 | if hasattr(num1, 'resize') == False: 64 | return 65 | 66 | resize_tup = (resize_int_bits, resize_frac_bits) 67 | 68 | for i in range(0, num_of_resizes): 69 | num1.resize(resize_tup, overflow_mode=overflow_mode, round_mode=round_mode) 70 | 71 | 72 | types_table = { 73 | 'fpbinary': {'class': FpBinary, 'build_params_func': build_create_params_fpbinary}, 74 | 'fpbinaryswitchable_fpmode': {'class': FpBinarySwitchable, 75 | 'build_params_func': build_create_params_fpswitchable_fpmode}, 76 | 'fpbinaryswitchable_non_fpmode': {'class': FpBinarySwitchable, 77 | 'build_params_func': build_create_params_fpswitchable_non_fpmode}, 78 | 'fpbinarysmall': {'class': _FpBinarySmall, 'build_params_func': build_create_params_fpbinary}, 79 | 'fpbinarylarge': {'class': _FpBinaryLarge, 'build_params_func': build_create_params_fpbinary}, 80 | 'float': {'class': float, 'build_params_func': build_create_params_float}, 81 | } 82 | 83 | 84 | def run_type_test(type_name): 85 | 86 | # Create test 87 | test_create(NUM_OBJECT_CREATIONS, types_table[type_name]['class'], 88 | types_table[type_name]['build_params_func'], 89 | int_bits=8, frac_bits=8, value=66.666) 90 | 91 | # Multiply test 92 | test_multiply(NUM_MULTIPLIES, types_table[type_name]['class'], 93 | types_table[type_name]['build_params_func'], 94 | int_bits = 3, frac_bits = 6, val1 = 1.23, val2 = 45.7) 95 | 96 | # Add test 97 | test_add(NUM_ADDS, types_table[type_name]['class'], 98 | types_table[type_name]['build_params_func'], 99 | int_bits=3, frac_bits=6, val1=-3.3445322, val2=0.035) 100 | 101 | # Resize test 102 | test_resize(NUM_RESIZES, types_table[type_name]['class'], 103 | types_table[type_name]['build_params_func'], 104 | start_int_bits=16, start_frac_bits=16, resize_int_bits=8, 105 | resize_frac_bits=8, start_val=-23343.3445322, overflow_mode=OverflowEnum.wrap, 106 | round_mode=RoundingEnum.near_pos_inf) 107 | 108 | 109 | def main(types_list, base_filename=''): 110 | for type_name in types_list: 111 | filename = '{}_{}'.format(base_filename, type_name) if base_filename else None 112 | 113 | cProfile.run('run_type_test(\'{}\')'.format(type_name), filename=filename, sort='tottime') 114 | 115 | if filename: 116 | p = pstats.Stats(filename) 117 | p.strip_dirs().sort_stats('tottime').print_stats() 118 | 119 | 120 | if __name__ == '__main__': 121 | parser = ArgumentParser() 122 | parser.add_argument('-t', '--type', type=str, help='One of {}, or \'all\'.'.format(types_table.keys())) 123 | parser.add_argument('--base-fname', type=str, default=None) 124 | parser.add_argument('--print-data', action='store_true') 125 | 126 | args = parser.parse_args() 127 | 128 | if args.print_data and args.base_fname: 129 | for filename in glob.glob('{}*'.format(args.base_fname)): 130 | p = pstats.Stats(filename) 131 | p.strip_dirs().sort_stats('tottime').print_stats() 132 | elif args.type != '': 133 | if args.type == 'all': 134 | types_list = types_table.keys() 135 | else: 136 | types_list = [args.type] 137 | 138 | main(types_list=types_list, base_filename=args.base_fname) 139 | -------------------------------------------------------------------------------- /tests/porting_v3_funcs.py: -------------------------------------------------------------------------------- 1 | 2 | def long(val): 3 | return int(val) -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import sys, math, pickle, os, struct 2 | from fpbinary import FpBinary, FpBinarySwitchable, FpBinaryComplex 3 | 4 | 5 | if sys.version_info[0] >= 3: 6 | from tests.porting_v3_funcs import * 7 | 8 | 9 | def get_small_type_size(): 10 | """ Returns the number of bits the FpBinarySmall object should be able to support. 11 | This is based on the assumption that FpBinary uses the long long type . """ 12 | return 8 * struct.calcsize("q") 13 | 14 | def get_interpreter_arch_size(): 15 | """ Returns the number of bits the python environment was compiled for. 16 | Note that this could be 32 on a 64 bit machine if running python in 32 bit. """ 17 | return 8 * struct.calcsize("P") 18 | 19 | def get_max_signed_value_bit_field(num_bits): 20 | return (long(1) << (num_bits - 1)) - 1 21 | 22 | def get_min_signed_value_bit_field(num_bits): 23 | return (long(1) << (num_bits - 1)) 24 | 25 | def get_max_unsigned_value_bit_field(num_bits): 26 | return (long(1) << num_bits) - 1 27 | 28 | def get_max_signed_value_bit_field_for_arch(): 29 | return get_max_signed_value_bit_field(get_small_type_size()) 30 | 31 | def get_min_signed_value_bit_field_for_arch(): 32 | return get_min_signed_value_bit_field(get_small_type_size()) 33 | 34 | def get_max_unsigned_value_bit_field_for_arch(): 35 | return get_max_unsigned_value_bit_field(get_small_type_size()) 36 | 37 | def convert_float_to_bit_field(value, int_bits, frac_bits): 38 | mant, exp = math.frexp(value) 39 | 40 | # Need to operate on magnitude bits and then do the 2's complement 41 | # to get the appropriate truncation behavior for negative numbers. 42 | value_mag = abs(mant) 43 | 44 | scaled_value = long(value_mag * 2.0 ** (exp + frac_bits)) 45 | 46 | # Now convert back to negative representation if needed 47 | if value < 0.0: 48 | scaled_value = (~scaled_value) + 1 49 | 50 | # Make sure only desired bits are used (note that this will convert 51 | # the number back to a positive long integer - its a bit field. 52 | return scaled_value & ((long(1) << (int_bits + frac_bits)) - 1) 53 | 54 | 55 | def set_float_bit_precision(value, int_bits, frac_bits, is_signed): 56 | """ 57 | Modifies value to the precision defined by int_bits and frac_bits. 58 | 59 | :param value: input float 60 | :param int_bits: number of integer bits to restrict to 61 | :param frac_bits: number of fractional bits to restrict to 62 | :param is_signed: Determines how many int bits can actually be used for magnitude 63 | :return: float - the input restricted to (int_bits + frac_bits) bits 64 | """ 65 | 66 | bit_field = convert_float_to_bit_field(value, int_bits, frac_bits) 67 | 68 | # Convert to an actual negative number if required 69 | if is_signed and value < 0.0: 70 | bit_field -= (long(1) << (int_bits + frac_bits)) 71 | 72 | # And convert back to float 73 | return bit_field / 2.0**frac_bits 74 | 75 | 76 | def fp_binary_fields_equal(op1, op2): 77 | """ 78 | Returns true if the FpBinary fields of op1 and op2 are equal (including value) 79 | """ 80 | return (op1.format == op2.format and op1.is_signed == op2.is_signed and op1 == op2) 81 | 82 | def fp_binary_complex_fields_equal(op1, op2): 83 | """ 84 | Returns true if the FpBinaryComplex fields of op1 and op2 are equal (including value) 85 | """ 86 | return (op1.format == op2.format and op1 == op2) 87 | 88 | def fp_binary_instances_are_totally_equal(op1, op2): 89 | """ 90 | The point of this is to check instances of FpBinary/Switchable/Complex are equal 91 | in value and other properties. If python numeric objects are passed, only value is checked. 92 | Returns True if the properties of the two objects are the same. 93 | """ 94 | 95 | if isinstance(op1, FpBinary) or isinstance(op2, FpBinary): 96 | return (isinstance(op1, FpBinary) and isinstance(op2, FpBinary) and op1.format == op2.format and 97 | op1.is_signed == op2.is_signed and op1 == op2) 98 | elif isinstance(op1, FpBinarySwitchable) or isinstance(op2, FpBinarySwitchable): 99 | return (isinstance(op1, FpBinarySwitchable) and isinstance(op2, FpBinarySwitchable) and 100 | op1.fp_mode == op2.fp_mode and op1.min_value == op2.min_value and op1.max_value == op2.max_value and 101 | fp_binary_instances_are_totally_equal(op1.value, op2.value)) 102 | elif isinstance(op1, FpBinaryComplex) or isinstance(op2, FpBinaryComplex): 103 | return (isinstance(op1, FpBinaryComplex) and isinstance(op2, FpBinaryComplex) and op1.format == op2.format and 104 | op1.is_signed == op2.is_signed and op1 == op2) 105 | 106 | 107 | return op1 == op2 108 | 109 | 110 | # ================================================================================ 111 | # Generating and getting back pickled data from multiple versions. 112 | # This includes FpBinary and FpBinarySwitchable instances 113 | # ================================================================================ 114 | 115 | pickle_static_file_prefix = 'pickletest' 116 | pickle_static_file_dir = 'data' 117 | pickle_static_data = [ 118 | FpBinary(8, 8, signed=True, value=0.01234), 119 | FpBinary(8, 8, signed=True, value=-3.01234), 120 | FpBinary(8, 8, signed=False, value=0.01234), 121 | 122 | FpBinary(64 - 2, 2, signed=True, value=56.789), 123 | FpBinary(64 - 2, 2, signed=False, value=56.789), 124 | 125 | FpBinarySwitchable(fp_mode=False, fp_value=FpBinary(16, 16, signed=True, value=5.875)), 126 | FpBinarySwitchable(fp_mode=False, float_value=-45.6), 127 | 128 | 129 | # All ones, small size 130 | FpBinary(64 - 2, 2, signed=True, bit_field=(1 << 64) - 1), 131 | FpBinary(64 - 2, 2, signed=False, bit_field=(1 << 64) - 1), 132 | 133 | FpBinary(64 - 2, 3, signed=True, value=56436.25), 134 | FpBinary(64 - 2, 3, signed=False, value=56436.25), 135 | 136 | FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(64 - 2, 2, signed=True)), 137 | 138 | # All ones, large size 139 | FpBinary(64 - 2, 3, signed=True, bit_field=(1 << (64 + 1)) - 1), 140 | FpBinary(64 - 2, 3, signed=False, bit_field=(1 << (64 + 1)) - 1), 141 | 142 | FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(64, 64, signed=False, 143 | bit_field=(1 << (64 * 2)) - 1)), 144 | 145 | 146 | FpBinary(64, 64, signed=True, bit_field=(1 << (64 + 5)) + 23), 147 | FpBinary(64, 64, signed=False, bit_field=(1 << (64 * 2)) - 1), 148 | 149 | FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(16, 16, signed=True, value=5.875)), 150 | FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(64 - 2, 3, signed=True)), 151 | FpBinarySwitchable(fp_mode=True, fp_value=FpBinary(64, 64, signed=True, 152 | bit_field=(1 << (64 + 5)) + 23)), 153 | 154 | ] 155 | 156 | def gen_static_pickle_files(): 157 | """ 158 | File name format: pickle_test_v[python_version]_p[pickle protocol]_[os_name]_[word_len].data 159 | """ 160 | 161 | this_dir = os.path.dirname(os.path.abspath(__file__)) 162 | data_dir = os.path.join(this_dir, pickle_static_file_dir) 163 | 164 | for protocol in range(2, pickle.HIGHEST_PROTOCOL + 1): 165 | fname = '{}_v{}_{}_{}_p{}_{}_{}.data'.format( 166 | pickle_static_file_prefix, 167 | sys.version_info.major, sys.version_info.minor, sys.version_info.micro, 168 | protocol, sys.platform, get_interpreter_arch_size() 169 | ) 170 | 171 | with open(os.path.join(data_dir, fname), 'wb') as f: 172 | pickle.dump(pickle_static_data, f, protocol) 173 | 174 | def get_static_pickle_file_paths(): 175 | result = [] 176 | 177 | this_dir = os.path.dirname(os.path.abspath(__file__)) 178 | data_dir = os.path.join(this_dir, pickle_static_file_dir) 179 | 180 | for f in os.listdir(data_dir): 181 | if pickle_static_file_prefix in f: 182 | file_protocol = int(f.split('_')[4].split('.')[0].strip('p')) 183 | if file_protocol <= pickle.HIGHEST_PROTOCOL: 184 | result.append(os.path.join(data_dir, f)) 185 | 186 | return result 187 | 188 | 189 | if __name__ == '__main__': 190 | # Generate pickling data 191 | gen_static_pickle_files() 192 | print(get_static_pickle_file_paths()) 193 | 194 | 195 | --------------------------------------------------------------------------------