[286] | 1 | // This file is a part of Framsticks SDK. http://www.framsticks.com/ |
---|
[1251] | 2 | // Copyright (C) 1999-2023 Maciej Komosinski and Szymon Ulatowski. |
---|
[286] | 3 | // See LICENSE.txt for details. |
---|
[122] | 4 | |
---|
[109] | 5 | #include "nonstd_math.h" |
---|
[970] | 6 | #include <PrintFloat/PrintFloat.h> |
---|
[980] | 7 | #include <string.h> // strncpy() |
---|
[970] | 8 | #include <sstream> |
---|
[980] | 9 | #include <algorithm> // std::min() |
---|
[109] | 10 | |
---|
[896] | 11 | RandomGenerator &rndGetInstance() |
---|
[109] | 12 | { |
---|
[867] | 13 | static RandomGenerator rnd(0); |
---|
| 14 | return rnd; |
---|
[109] | 15 | } |
---|
| 16 | |
---|
[1001] | 17 | |
---|
| 18 | std::string doubleToString(double x, int precision) //waiting for a proper, native C++ solution |
---|
[979] | 19 | { |
---|
[1001] | 20 | std::stringstream ss; //or for pre-allocated buffer, sprintf(s, "%.*f", precision, x); |
---|
| 21 | std::string str; |
---|
| 22 | if (fabs(x) < 1e8) //limiting the precision of huge fp values makes little sense - better use scientific notation, unless we want a looong number |
---|
| 23 | { |
---|
| 24 | ss << std::fixed; |
---|
| 25 | ss.precision(precision); //set the number of places after decimal |
---|
| 26 | ss << x; |
---|
| 27 | str = ss.str(); |
---|
[1026] | 28 | char *s = |
---|
| 29 | #ifdef __BORLANDC__ //embarcadero 10.3u3 compiler does not support char* str.data() even in C++17 mode? |
---|
| 30 | (char*) |
---|
| 31 | #endif |
---|
[1251] | 32 | str.data(); //now we will be operating directly on the internal std::string buffer |
---|
| 33 | for (int i = int(str.length()) - 1, end = int(str.length()); i >= 0; i--) //remove trailing zeros, and maybe also '.' |
---|
[1001] | 34 | { |
---|
| 35 | if (s[i] == '0') |
---|
| 36 | { |
---|
| 37 | if (end == i + 1) |
---|
| 38 | end = i; |
---|
| 39 | } |
---|
| 40 | else if (s[i] == '.') |
---|
| 41 | { |
---|
| 42 | if (end == i + 1) |
---|
| 43 | end = i; |
---|
| 44 | s[end] = '\0'; |
---|
| 45 | break; |
---|
| 46 | } |
---|
| 47 | } |
---|
| 48 | } |
---|
| 49 | else |
---|
| 50 | { |
---|
| 51 | ss << x; |
---|
| 52 | str = ss.str(); |
---|
| 53 | } |
---|
| 54 | //printf("%.17g and %d decimals: %s\n", x, precision, str.c_str()); |
---|
| 55 | return str; |
---|
[979] | 56 | } |
---|
| 57 | |
---|
[970] | 58 | int doubleToString(double x, int precision, char *buffer, int bufferlen) |
---|
| 59 | { |
---|
[979] | 60 | // C++ in 2020 and the impossible challenge https://stackoverflow.com/questions/277772/avoid-trailing-zeroes-in-printf |
---|
| 61 | if (precision < 0) |
---|
| 62 | { |
---|
| 63 | // The "g" format does not allow to use the number of decimal places after the decimal point. Dragon4 on the other hand fills in unnecessary trailinig zeros... so both are good only for "full precision". |
---|
[970] | 64 | #ifdef USE_PRINTFLOAT_DRAGON4 |
---|
[979] | 65 | return PrintFloat64(buffer, bufferlen, x, |
---|
| 66 | ((x < -1e17) || (x > 1e17) || ((x < 1e-4) && (x > -1e-4) && (x != 0.0))) |
---|
| 67 | ? PrintFloatFormat_Scientific : PrintFloatFormat_Positional, |
---|
| 68 | precision); //http://www.ryanjuckett.com/programming/printing-floating-point-numbers/ |
---|
[970] | 69 | #else |
---|
[979] | 70 | return sprintf(buffer, "%.17g", x); |
---|
[970] | 71 | #endif |
---|
[979] | 72 | } |
---|
| 73 | else |
---|
| 74 | { |
---|
| 75 | std::string s = doubleToString(x, precision); |
---|
| 76 | strncpy(buffer, s.c_str(), std::min(bufferlen, (int)s.length() + 1)); |
---|
| 77 | buffer[bufferlen - 1] = 0; //ensure the string is truncated |
---|
[1251] | 78 | return int(s.length()); |
---|
[979] | 79 | } |
---|
[970] | 80 | } |
---|
[109] | 81 | |
---|
| 82 | |
---|
[970] | 83 | double round(const double x, const int precision) |
---|
| 84 | { |
---|
[979] | 85 | double rounded = std::stod(doubleToString(x, precision)); |
---|
[970] | 86 | //printf("%d %20g \t %20g\n", precision, x, rounded); //for debugging |
---|
| 87 | return rounded; |
---|
| 88 | } |
---|
[109] | 89 | |
---|
[1275] | 90 | namespace fpExcept |
---|
[1280] | 91 | { //shared for all platforms, using crossplatform constants, can be defined in build_config (but also conditionally changed from the code) |
---|
[1275] | 92 | int wanted_exceptions = |
---|
| 93 | #ifdef WANTED_FP_EXCEPTIONS |
---|
| 94 | WANTED_FP_EXCEPTIONS; |
---|
| 95 | #else |
---|
| 96 | FPEX_DIV0; //default when 'WANTED_FP_EXCEPTIONS' is not defined (add more?) |
---|
| 97 | #endif |
---|
| 98 | } |
---|
[109] | 99 | |
---|
[1251] | 100 | // Idea: enable selected floating point exceptions when the app starts and disable them temporarily when dividing values in ExtValue, so that we can directly handle problematic cases there. |
---|
| 101 | // This allows to catch problematic situations when the program performs calculations using NaN, INF etc. |
---|
| 102 | |
---|
[247] | 103 | #ifdef IPHONE |
---|
| 104 | //TODO! -> ? http://stackoverflow.com/questions/12762418/how-to-enable-sigfpe-signal-on-division-by-zero-in-ios-app |
---|
[1275] | 105 | namespace fpExcept |
---|
| 106 | { |
---|
| 107 | void init() {} |
---|
| 108 | void enable() {} |
---|
| 109 | void disable() {} |
---|
| 110 | } |
---|
[247] | 111 | #endif |
---|
[109] | 112 | |
---|
[285] | 113 | #ifdef MACOS |
---|
[1275] | 114 | //TODO...? (even less reasons to omit this in macos :-P) |
---|
| 115 | namespace fpExcept |
---|
| 116 | { |
---|
| 117 | void init() {} |
---|
| 118 | void enable() {} |
---|
| 119 | void disable() {} |
---|
| 120 | } |
---|
[285] | 121 | #endif |
---|
| 122 | |
---|
| 123 | |
---|
[247] | 124 | #if defined LINUX || defined TIZEN || defined __ANDROID__ |
---|
[109] | 125 | #include <fenv.h> |
---|
| 126 | |
---|
[1275] | 127 | namespace fpExcept |
---|
| 128 | { |
---|
| 129 | void init() {} |
---|
| 130 | void enable() |
---|
| 131 | { |
---|
| 132 | feclearexcept(wanted_exceptions); |
---|
| 133 | feenableexcept(wanted_exceptions); |
---|
| 134 | } |
---|
| 135 | void disable() |
---|
| 136 | { |
---|
| 137 | fedisableexcept(wanted_exceptions); |
---|
| 138 | } |
---|
| 139 | }; |
---|
[109] | 140 | #endif |
---|
| 141 | |
---|
| 142 | |
---|
| 143 | |
---|
[1251] | 144 | #if defined(__BORLANDC__) || defined(_MSC_VER) |
---|
| 145 | |
---|
[1275] | 146 | namespace fpExcept |
---|
| 147 | { |
---|
[1251] | 148 | // in Borland, there was once a problem like this: |
---|
[145] | 149 | // http://qc.embarcadero.com/wc/qcmain.aspx?d=5128 |
---|
| 150 | // http://www.delorie.com/djgpp/doc/libc/libc_112.html |
---|
| 151 | // ? http://www.c-jump.com/CIS77/reference/Intel/CIS77_24319002/pg_0211.htm |
---|
| 152 | // ? http://www.jaist.ac.jp/iscenter-new/mpc/altix/altixdata/opt/intel/vtune/doc/users_guide/mergedProjects/analyzer_ec/mergedProjects/reference_olh/mergedProjects/instructions/instruct32_hh/vc100.htm |
---|
| 153 | // ? http://www.plantation-productions.com/Webster/www.artofasm.com/Linux/HTML/RealArithmetica2.html |
---|
| 154 | // http://blogs.msdn.com/b/oldnewthing/archive/2008/07/03/8682463.aspx |
---|
| 155 | // where each cast of a double into an int would cause an exception. |
---|
| 156 | // But it was resolved by restarting windows and cleaning all intermediate compilation files :o (restarting windows was the key element! restarting BC++Builder and deleting files would not help) |
---|
[109] | 157 | |
---|
| 158 | |
---|
[1251] | 159 | #if defined(__BORLANDC__) // adding a missing constant and a function |
---|
| 160 | #define _MCW_EM 0x0008001f // Interrupt Exception Masks - from Visual C++'s float.h |
---|
| 161 | |
---|
| 162 | void _controlfp_s(unsigned int* _CurrentState, unsigned int _NewValue, unsigned int _Mask) //pretends to be the real _controlfp_s() function |
---|
| 163 | { |
---|
| 164 | *_CurrentState = _control87(_NewValue, _Mask); |
---|
| 165 | } |
---|
| 166 | #endif |
---|
| 167 | |
---|
| 168 | #if defined(_MSC_VER) |
---|
| 169 | #pragma fenv_access (on) |
---|
| 170 | #endif |
---|
| 171 | |
---|
| 172 | // http://stackoverflow.com/questions/2769814/how-do-i-use-try-catch-to-catch-floating-point-errors |
---|
| 173 | |
---|
| 174 | //#include "log.h" |
---|
| 175 | |
---|
[109] | 176 | unsigned int fp_control_word_std; |
---|
| 177 | unsigned int fp_control_word_muted; |
---|
| 178 | |
---|
[1275] | 179 | void init() |
---|
[109] | 180 | { |
---|
[1251] | 181 | _controlfp_s(&fp_control_word_std, 0, 0); //in Visual C++, the default value is exactly the masks listed below, and we have to turn them off to enable exceptions |
---|
[109] | 182 | // Make the new fp env same as the old one, except for the changes we're going to make |
---|
[1275] | 183 | fp_control_word_muted = fp_control_word_std & ~wanted_exceptions; |
---|
[109] | 184 | } |
---|
| 185 | |
---|
[1275] | 186 | void enable() |
---|
[109] | 187 | { |
---|
[1251] | 188 | //_fpreset(); //not needed since we just _clearfp()... mentioned in https://stackoverflow.com/questions/4282217/visual-c-weird-behavior-after-enabling-floating-point-exceptions-compiler-b |
---|
| 189 | unsigned int was = _clearfp(); //need to clean so that there is no exception... |
---|
| 190 | //logPrintf("","fpExceptEnable",LOG_INFO,"control87 status before clear was %08x", was); |
---|
| 191 | _controlfp_s(&was, fp_control_word_muted, _MCW_EM); |
---|
[109] | 192 | } |
---|
| 193 | |
---|
[1275] | 194 | void disable() |
---|
[109] | 195 | { |
---|
[1251] | 196 | //_fpreset(); //not needed since we just _clearfp()... mentioned in https://stackoverflow.com/questions/4282217/visual-c-weird-behavior-after-enabling-floating-point-exceptions-compiler-b |
---|
| 197 | unsigned int was = _clearfp(); //need to clean so that there is no exception... |
---|
[375] | 198 | //logPrintf("","fpExceptDisable",LOG_INFO,"control87 status before clear was %08x", was); |
---|
[1251] | 199 | _controlfp_s(&was, fp_control_word_std, _MCW_EM); |
---|
[109] | 200 | } |
---|
[1275] | 201 | |
---|
| 202 | }; |
---|
| 203 | |
---|
[109] | 204 | #endif |
---|