Safe Float design question

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Safe Float design question

Boost - Dev mailing list
Hi,
I looking for some design advice.

The context:
- I'm revisiting SafeFloat after some time and, while doing so, I'm
rethinking all the decisions from the past. Idea is to sent for review in
the following months.
- The goal of the library is being a drop-in replacement for float, that
adds checks to floating point operations and reports when a check failed.

Without SafeFloat, someone could write something like this:

#include <iostream>
#include <limits>
#include <cfenv>

using namespace std;

int main(){
    float a = 1.0f;
    float b = numeric_limits<float>::max();
    feclearexcept(FE_ALL_EXCEPT);
    float c = a/b;
    if(fetestexcept(FE_UNDERFLOW)) { cout << "underflow result\n"; }
}


What I expect when using safe_float is to declare upfront "what checks" I
care about, "what operations" to check, and "what to do when a check fails".

Some intention examples:
- I would like to check there was no "overflow_to_infinite" in "addition
operations", otherwise I "throw exception"..
- I would like to check there was no "division by zero" in "division",
otherwise I will "log to cerr and ignore it".
- I would like to check there was no "inexact" "addition", otherwise I
return an boost::unexpected.

I have at least 5 things to check (there is 5 flags in c++11::fenv). I have
at least 4 places to check (+/-/*//) and I want to keep what to do about it
customizable.

In addition, sometimes I want to check multiple things "overflow and
underflow", etc...

So, the question is how the user can pass all that information to the type
and it doesn't look as horrible nonsense.

My original option was:
int main(){
using CHECK = compose_policy<check_addition_overflow,
check_division_by_zero, check_division_underflow>::type;
using REPORT = report_throw_on_failure;
using sf = safe_float<float, CHECK, REPORT>;

try{
   sf a = 1.0_sf;
   sf b = numeric_limits<sf>::max();
   auto c = a/b;
} catch (safe_float_exception e){
   cout << e.message(); //this outputs there was a underflow
}

}


Please comment in what you think about this way to use. Is there a better
way to specify the policies to apply that I should try?

Best regards,
Damian













I expect when using safe float to write some code like this:

int main(){

safe_float<float>


}

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|

Re: Safe Float design question

Boost - Dev mailing list
Hi Damian,
I may not be answering your question directly, but maybe the following will
help.

You mention checking things as:
- I would like to check there was no "overflow_to_infinite" in "addition
operations", otherwise I "throw exception"..
- I would like to check there was no "division by zero" in "division",
otherwise I will "log to cerr and ignore it".

The two checks are very different in nature, and I do not even think they
belong into the same library. The first check traps a situation that (1)
the programmer-user cannot control (you do not know in advance if the
operation would overflow, it is not a bug to cause overflow), (2) Type
double has an inconvenient way of handling the situation (does not report
an error), so you want your type to handle it better.

The later check checks for programmer mistakes. These mistakes should not
be handled at run-time, but corrected in the code (the overflow cannot be
easily corrected in the code), and it adds overhead for situations that do
not occur for correct programs. The precondition violation should be
handled by contract libraries: otherwise you are preventing static-analysis
tools and UB sanitizers from finding division-by-zero bugs.

Next, if you go with providing checks separately from the method of
reporting them:

using sf = safe_float<float, CHECK, REPORT>;

You will loose the ability to report different kinds of errors in different
ways. You have to make the call: do you always want all checks to be
reported in the same way, or do you want, for instance to allow reporting
the inaccurate addition in a different way than overflowing to infinity? If
the latter, you will have to encode the method of handling inside the check:

using P = compose_policy<
  check_addition_overflow< report_throw_on_failure >,
  check_division_underflow< abort_on_failure >
>;

The design, where I first need to write 10 lines to build a policy, and
then define a type:

using fp = safe_float<double, P>;

seems the right choice to me. Of course, you should provide some predefined
policies, that would be most obviously chosen most of the time. (Like,
check and throw only underflow and overflow, check and abort only on
underflow and overflow).

Regards,
&rzej;

Finally
2018-07-17 4:18 GMT+02:00 Damian Vicino via Boost <[hidden email]>:

> Hi,
> I looking for some design advice.
>
> The context:
> - I'm revisiting SafeFloat after some time and, while doing so, I'm
> rethinking all the decisions from the past. Idea is to sent for review in
> the following months.
> - The goal of the library is being a drop-in replacement for float, that
> adds checks to floating point operations and reports when a check failed.
>
> Without SafeFloat, someone could write something like this:
>
> #include <iostream>
> #include <limits>
> #include <cfenv>
>
> using namespace std;
>
> int main(){
>     float a = 1.0f;
>     float b = numeric_limits<float>::max();
>     feclearexcept(FE_ALL_EXCEPT);
>     float c = a/b;
>     if(fetestexcept(FE_UNDERFLOW)) { cout << "underflow result\n"; }
> }
>
>
> What I expect when using safe_float is to declare upfront "what checks" I
> care about, "what operations" to check, and "what to do when a check
> fails".
>
> Some intention examples:
> - I would like to check there was no "overflow_to_infinite" in "addition
> operations", otherwise I "throw exception"..
> - I would like to check there was no "division by zero" in "division",
> otherwise I will "log to cerr and ignore it".
> - I would like to check there was no "inexact" "addition", otherwise I
> return an boost::unexpected.
>
> I have at least 5 things to check (there is 5 flags in c++11::fenv). I have
> at least 4 places to check (+/-/*//) and I want to keep what to do about it
> customizable.
>
> In addition, sometimes I want to check multiple things "overflow and
> underflow", etc...
>
> So, the question is how the user can pass all that information to the type
> and it doesn't look as horrible nonsense.
>
> My original option was:
> int main(){
> using CHECK = compose_policy<check_addition_overflow,
> check_division_by_zero, check_division_underflow>::type;
> using REPORT = report_throw_on_failure;
> using sf = safe_float<float, CHECK, REPORT>;
>
> try{
>    sf a = 1.0_sf;
>    sf b = numeric_limits<sf>::max();
>    auto c = a/b;
> } catch (safe_float_exception e){
>    cout << e.message(); //this outputs there was a underflow
> }
>
> }
>
>
> Please comment in what you think about this way to use. Is there a better
> way to specify the policies to apply that I should try?
>
> Best regards,
> Damian
>
>
>
>
>
>
>
>
>
>
>
>
>
> I expect when using safe float to write some code like this:
>
> int main(){
>
> safe_float<float>
>
>
> }
>
> _______________________________________________
> Unsubscribe & other changes: http://lists.boost.org/
> mailman/listinfo.cgi/boost
>

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|

Re: Safe Float design question

Boost - Dev mailing list
One more thing. Is there a need to keep every single aspect separate? E.g.,
is there a point in requiring that overflow is checked, but underflow is
ignored?
I see the point in in treating inaccurate representation as a separate
thing (because you will not want to have it most of the time), but things
like overflow and underflow -- do you not want them to always go in pair?

Also, given that safe_float<double> is to be a "drop-in" replacement for
double, type double is convertible to types int, unsigned int and bool.
Is safe_float<double> also going to be convertible to these types? If the
answer should be yes, do these conversions also require a dedicated policy?

Regards,
&rzej;

2018-07-17 10:55 GMT+02:00 Andrzej Krzemienski <[hidden email]>:

> Hi Damian,
> I may not be answering your question directly, but maybe the following
> will help.
>
> You mention checking things as:
> - I would like to check there was no "overflow_to_infinite" in "addition
> operations", otherwise I "throw exception"..
> - I would like to check there was no "division by zero" in "division",
> otherwise I will "log to cerr and ignore it".
>
> The two checks are very different in nature, and I do not even think they
> belong into the same library. The first check traps a situation that (1)
> the programmer-user cannot control (you do not know in advance if the
> operation would overflow, it is not a bug to cause overflow), (2) Type
> double has an inconvenient way of handling the situation (does not report
> an error), so you want your type to handle it better.
>
> The later check checks for programmer mistakes. These mistakes should not
> be handled at run-time, but corrected in the code (the overflow cannot be
> easily corrected in the code), and it adds overhead for situations that do
> not occur for correct programs. The precondition violation should be
> handled by contract libraries: otherwise you are preventing static-analysis
> tools and UB sanitizers from finding division-by-zero bugs.
>
> Next, if you go with providing checks separately from the method of
> reporting them:
>
> using sf = safe_float<float, CHECK, REPORT>;
>
> You will loose the ability to report different kinds of errors in
> different ways. You have to make the call: do you always want all checks to
> be reported in the same way, or do you want, for instance to allow
> reporting the inaccurate addition in a different way than overflowing to
> infinity? If the latter, you will have to encode the method of handling
> inside the check:
>
> using P = compose_policy<
>   check_addition_overflow< report_throw_on_failure >,
>   check_division_underflow< abort_on_failure >
> >;
>
> The design, where I first need to write 10 lines to build a policy, and
> then define a type:
>
> using fp = safe_float<double, P>;
>
> seems the right choice to me. Of course, you should provide some
> predefined policies, that would be most obviously chosen most of the time.
> (Like, check and throw only underflow and overflow, check and abort only on
> underflow and overflow).
>
> Regards,
> &rzej;
>
> Finally
> 2018-07-17 4:18 GMT+02:00 Damian Vicino via Boost <[hidden email]>:
>
>> Hi,
>> I looking for some design advice.
>>
>> The context:
>> - I'm revisiting SafeFloat after some time and, while doing so, I'm
>> rethinking all the decisions from the past. Idea is to sent for review in
>> the following months.
>> - The goal of the library is being a drop-in replacement for float, that
>> adds checks to floating point operations and reports when a check failed.
>>
>> Without SafeFloat, someone could write something like this:
>>
>> #include <iostream>
>> #include <limits>
>> #include <cfenv>
>>
>> using namespace std;
>>
>> int main(){
>>     float a = 1.0f;
>>     float b = numeric_limits<float>::max();
>>     feclearexcept(FE_ALL_EXCEPT);
>>     float c = a/b;
>>     if(fetestexcept(FE_UNDERFLOW)) { cout << "underflow result\n"; }
>> }
>>
>>
>> What I expect when using safe_float is to declare upfront "what checks" I
>> care about, "what operations" to check, and "what to do when a check
>> fails".
>>
>> Some intention examples:
>> - I would like to check there was no "overflow_to_infinite" in "addition
>> operations", otherwise I "throw exception"..
>> - I would like to check there was no "division by zero" in "division",
>> otherwise I will "log to cerr and ignore it".
>> - I would like to check there was no "inexact" "addition", otherwise I
>> return an boost::unexpected.
>>
>> I have at least 5 things to check (there is 5 flags in c++11::fenv). I
>> have
>> at least 4 places to check (+/-/*//) and I want to keep what to do about
>> it
>> customizable.
>>
>> In addition, sometimes I want to check multiple things "overflow and
>> underflow", etc...
>>
>> So, the question is how the user can pass all that information to the type
>> and it doesn't look as horrible nonsense.
>>
>> My original option was:
>> int main(){
>> using CHECK = compose_policy<check_addition_overflow,
>> check_division_by_zero, check_division_underflow>::type;
>> using REPORT = report_throw_on_failure;
>> using sf = safe_float<float, CHECK, REPORT>;
>>
>> try{
>>    sf a = 1.0_sf;
>>    sf b = numeric_limits<sf>::max();
>>    auto c = a/b;
>> } catch (safe_float_exception e){
>>    cout << e.message(); //this outputs there was a underflow
>> }
>>
>> }
>>
>>
>> Please comment in what you think about this way to use. Is there a better
>> way to specify the policies to apply that I should try?
>>
>> Best regards,
>> Damian
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>> I expect when using safe float to write some code like this:
>>
>> int main(){
>>
>> safe_float<float>
>>
>>
>> }
>>
>> _______________________________________________
>> Unsubscribe & other changes: http://lists.boost.org/mailman
>> /listinfo.cgi/boost
>>
>
>

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|

Re: Safe Float design question

Boost - Dev mailing list
Hi &rzej,

Thanks for all the comments, very helpful.

I think passing the reporting method to the check makes sense and helps
having uniformity at the interface level. Something I'm wondering is if
safe_float should receive only 1 policy (that could be composed, this is
what I have implemented now) or if I should use variadic template and allow
to pass multiple pass as many policies as wanted and make their composition
internally.

Something like:
safe_float<typename FP, class P, class... Ps>;
vs
safe_float<typename FP, class P>

I'm also implementing a convenience.hpp with compositions of policies that
may be common to mix, for example, I define both_flows as the check of both
underflow and overflows.

About division by zero. The following code compiles with no warning in
clang (-wall -pedatic).

float a = 1.0f;
float b = numeric_limits::max();
float c = a/b;
float d = c/b;
float e = a/d;

I'm not sure if there is any static analyzer that will catch that the flag
of div_by_zero is on after last operation, and probably there is even less
chance that this is catched if d and e are in different cpp files or one is
coming from user input as cin. Do you know about such a tool that will
catch things like this?


About the casts, I'm including cast policies for casts too, I calling them
allow_cast_from<T> and allow_cast_to<T>, by default they don't mix with
other types. Silently casting to other types is a big source of errors when
using float. I'm also including _sf, _sd _sld literal suffixes.

Best regards,
Damian

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost