johnmcfarlane / cnl

A Compositional Numeric Library for C++
Boost Software License 1.0
644 stars 62 forks source link

How to define a fixed_point on 16 bits using Boost multiprecision #907

Closed emiliopaolini closed 3 years ago

emiliopaolini commented 3 years ago

Hi everyone, i am trying to define a fixed_point type on 16 bits. I would like to use Boost::multiprecision as base type. I know it is possible to do the same by using types defined only in CNL, for example: using X = cnl::scaled_integer<cnl::overflow_integer<short int,cnl::saturated_overflow_tag>, cnl::power<-8>>; Instead of the short int, i would like to use boost::multiprecision or cnl::signed_multiprecision. Now the problem is the following: if i use this type: using X = cnl::scaled_integer<cnl::overflow_integer<cnl::signed_multiprecision<16>,cnl::saturated_overflow_tag>, cnl::power<-8>>; the same program, loses accuracy and gives strange results. Do you have any suggestions on how to define a fixed_point type on a custom number of bits that can saturate? Possibly by using boost::multiprecision. Thanks in advance!

johnmcfarlane commented 3 years ago

Hi @emiliopaolini can you tell me if the FAQs here provide any insight?

If not, can you show me an example of how you're using the type when you lose accuracy? If you're able to demonstrate it in a minimal example in Compiler Explorer like this one, that would be even better.

emiliopaolini commented 3 years ago

It is very difficult to show you an example since i am using scaled_integer as the computation type for a deep learning library. I just noticed that by changing the computation type from using X = cnl::scaled_integer<cnl::overflow_integer<short int,cnl::saturated_overflow_tag>, cnl::power<-8>>; to using X = cnl::scaled_integer<cnl::overflow_integer<cnl::signed_multiprecision<16>,cnl::saturated_overflow_tag>, cnl::power<-8>>; the results change. Anyway if i use this type: using X = cnl::static_number<16, -8, cnl::native_rounding_tag,cnl::saturated_overflow_tag>; i obtain the same results of X = cnl::scaled_integer<cnl::overflow_integer<short int,cnl::saturated_overflow_tag>, cnl::power<-8>>;. Maybe this can help in finding the problem with signed_multiprecision?

johnmcfarlane commented 3 years ago

Maybe this can help in finding the problem with signed_multiprecision?

All of this sounds consistent with your values overflowing as described in the FAQ.

You could try the following:

Essentially, when you multiply two 16-bit numbers together, you get a 32-bit number. If you store this result in a 16-bit type, e.g. cnl::signed_multiprecision<16>, then that 16-bit number will overflow when the result of the 16-bit multiplication is too wide. E.g. 10,000 * 10,000 is 100,000,000 which is too wide for a 16-bit integer.

Even if you assign the result back to a 16-bit number with exponent=-8, the overflow will already have occurred.

emiliopaolini commented 3 years ago

Ok maybe i understand the problem. Probably there was an overflow that i didn't notice. The thing that i don't get is the following: why these two types, that both saturate when overflow happens, give different results? using X = cnl::scaled_integer<cnl::overflow_integer<cnl::signed_multiprecision<16>,cnl::saturated_overflow_tag>, cnl::power<-8>>; using X = cnl::scaled_integer<cnl::overflow_integer<short int,cnl::saturated_overflow_tag>, cnl::power<-8>>;

johnmcfarlane commented 3 years ago

Many operations on fundamental integer types promote to wider types. On common systems, short int operations promote to int, thus avoiding the overflow in the first place:

short s = 10'000;
auto ss = s*s;  // type=int; value=100'000'000
emiliopaolini commented 3 years ago

Ok i understand! Thank you very much!