source: cpp/common/nonstd_math.cpp @ 1279

Last change on this file since 1279 was 1275, checked in by Maciej Komosinski, 15 months ago

More unification of floating point exception handling across platforms

  • Property svn:eol-style set to native
File size: 6.6 KB
RevLine 
[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]11RandomGenerator &rndGetInstance()
[109]12{
[867]13        static RandomGenerator rnd(0);
14        return rnd;
[109]15}
16
[1001]17
18std::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]58int 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]83double 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]90namespace fpExcept
91{ //shared for all platform, using crossplatform constants, can be defined in build_config (but also conditionally changed from the code)
92int 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]105namespace 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)
115namespace 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]127namespace 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]146namespace 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
162void _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]176unsigned int fp_control_word_std;
177unsigned int fp_control_word_muted;
178
[1275]179void 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]186void 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]194void 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
Note: See TracBrowser for help on using the repository browser.