-1

I am working on a programming language and in short I need to steal the 3 least significant bits from a double, when working with an integer I can do the following

long long int make(long long int x)
{
    return x << 3;
}
long long int take(long long int x)
{
    return x >> 3;
}

However when doing that to doubles (first converting into a long long binary representation of course) it simply makes the number not anywhere like it was. Worth to mention that my code will only ship to platforms where CHAR_BIT == 8.

Ideas?

Eric Postpischil
  • 168,892
  • 12
  • 149
  • 276
Null
  • 45
  • 1
  • 6
  • 1
    You need to split the number into sign, fraction, and exponent. Then you can steal bits from the fraction, and adjust the exponent accordingly. – Barmar Dec 08 '21 at 21:36
  • And how is it done? I am aware that you will not send source code but I am looking at more of an explantion please – Null Dec 08 '21 at 21:38
  • 2
    If instead of shifting the number like you're doing there with your `long long` values, you zero out the bottom three bits and replace them with your own, you should be able to get away with this, since the bottom three bits are (usually!) the lowest-precision bits of the significand. This might change, for example, `0.5` to `0.5000000000000003331`. (Not saying whether I think this is a good idea or not. :-) ) See [this popular question](https://stackoverflow.com/questions/47981) if you need hints. – Steve Summit Dec 08 '21 at 21:38
  • 1
    See the [wikipedia](https://en.wikipedia.org/wiki/IEEE_754) page about floating point format. – Barmar Dec 08 '21 at 21:39
  • 1
    The least three bits of the integer part? If so, and the number is negative, do you want the least three bits of the magnitude of the integer part or the least three bits of the integer part converted to two’s complement? Or do you want the least three bits of the significand? Or the least three bits of the encoding? If the `double` is a NaN or an infinity, what result do you want? – Eric Postpischil Dec 08 '21 at 21:49
  • Do not tag both C and C++ exception when asking about differences or interactions between the two languages. Pick one and delete the other tag. – Eric Postpischil Dec 08 '21 at 21:50
  • 1
    @EricPostpischil I assume the intent is to, in effect, inject 3 bits of noise, sort of like small-scale steganography. And it is not evidently important that the "augmented" values be usable as-is (i.e., "breaking" an inf by temporarily turning it into a NaN wouldn't be unacceptable), given the way they seem to be currently handling long longs. – Steve Summit Dec 08 '21 at 21:51
  • @SteveSummit for my small double 3.555 test it works like a charm! I will inform you :) – Null Dec 08 '21 at 22:01
  • 1
    @EricPostpischil I got a respone, but what I meant if I wasn't clear is injecting 3 bits to the least significant bits (if you were to memcpy it into a int64) – Null Dec 08 '21 at 22:03
  • 1
    Could you elaborate on "I need to steal the 3 least significant bits from a double"? What is your purpose? Is it something like this: https://stackoverflow.com/questions/33955713/storing-data-in-the-most-significant-bits-of-a-pointer that you are trying to do? You can "hide" some information in a [(quiet) `NaN`](https://en.cppreference.com/w/cpp/numeric/math/nan), but not in a general `double`. – Bob__ Dec 08 '21 at 22:25
  • Trick-question, had you realized that all you did was `x / 8` and `x * 8`? Since this was *arithmetic* shift to begin with for signed integers? Once you understand that (and stop trying to "optimize" arithmetic as bit operations!!!), you get the expected behavior for `double` as well. Because you don't even wanted to remove LSB, you wanted to adjust the exponent! – Ext3h Dec 09 '21 at 11:17
  • I didn't try to optimize any operation. What is wrong with you? I think my question was explained perfectly in the thread. I wanted to adjust the 3 LSB's as if it is was an integer. That has nothing to do with optimizing the * and / operators friend. @Ext3h – Null Dec 09 '21 at 13:21
  • @Null: It is not explained clearly, and that is at least part of why your question was voted down and closed. First, asking for the “bits“ of a `double` raises ambiguity about whether you are seeking to operate on the bits that encode a `double` (its representation) or the bits in a binary numeral representing its value. Second, asking for the LSBs “as if it was an integer” raises ambiguity about whether you want the absolute three lowest significant bits or the three lowest significant bits of the integer portion. You have not fully clarified these in the comments… – Eric Postpischil Dec 09 '21 at 14:20
  • … and answering in the comments is insufficient; the question should be updated with the clarifications. – Eric Postpischil Dec 09 '21 at 14:20

2 Answers2

2
typedef union
{
    double d;
    struct
    {
        uint64_t frac   :52;
        uint64_t exp    :11;
        uint64_t sign   :1;
    };
}DBL;

double steal(double val, int nbits)
{
    uint64_t mask = ~((1ULL << nbits) - 1);
    DBL d = {.d = val};
    d.frac &= mask;
    return d.d;
}


int main(void)
{
    double x = 1.0/6;
    printf("%.100f\n", x);
    printf("%.100f\n", steal(x, 3));
    x = 3.14658456758768765878445676547564765765657567567657754836459845798457498457;
    printf("%.100f\n", x);
    printf("%.100f\n", steal(x, 20));
}

https://godbolt.org/z/dh1Ge7YYe

Maybe something like this. It is not portable as bitfields are implementation-defined, but gcc, clang, IAR, Keil, GHC implement them in this way.

0___________
  • 47,100
  • 4
  • 27
  • 62
1

Since type punning isn't allowed in C++, here's a version copying the double to a unsigned char array and clears the 3 least significant bits in it and then copies it back into the double:

#include <bit>
#include <cstring>
#include <limits>

double take(double x) {
    static_assert(std::endian::native == std::endian::little ||
                  std::endian::native == std::endian::big);
    static_assert(std::numeric_limits<double>::is_iec559);
    unsigned char buf[sizeof x];
    std::memcpy(buf, &x, sizeof x);
    if constexpr(std::endian::native == std::endian::little)
        buf[0] &= 0b11111000;
    else
        buf[sizeof buf - 1] &= 0b11111000;
    std::memcpy(&x, buf, sizeof x);
    return x;
}

Demo

Ted Lyngmo
  • 60,763
  • 5
  • 37
  • 77
  • 1st one looks functionally correct - although only 1 bytes needs copying - not 8. 2nd one (the original) depends on the last digit/nibble having 4 significant bits. Perhaps OK with common 53-bit significant for `double`, yet not so with others. IAC, NMDV. – chux - Reinstate Monica Dec 09 '21 at 10:43
  • @chux-ReinstateMonica "_only 1 bytes needs copying_" - I wasn't sure about that. If only one needs being copied back and forth, would [this](https://godbolt.org/z/zzrqh7PbT) also be valid? It _feels_ invalid... Re: "_2nd one_" - True. I added a `static_assert` to both my functions to make sure they are IEEE-754 `double`s. – Ted Lyngmo Dec 09 '21 at 11:09
  • 1
    I see [that](https://godbolt.org/z/zzrqh7PbT) code as likely correct - and best here so far. (be aware endian of `double` can uncommonly differ from endian of `long` - guess assert would fail) Did not know of `is_iec559`. LSNED. – chux - Reinstate Monica Dec 09 '21 at 11:23
  • 1
    @chux-ReinstateMonica "_likely correct_" - Ok, but I'll leave the version I have in the answer right now. I find the rules a bit tricky :-) Re: "_uncommonly differ_" - Yes in that case the first `static_assert` should fail. "_If the platform uses mixed endian, `std::endian::native` equals neither `std::endian::big` nor `std::endian::little`_" – Ted Lyngmo Dec 09 '21 at 11:24
  • 1
    The `hexfloat` code is broken because the `hexfloat` format does not guarantee the end of the significand bits are aligned with a hexadecimal digit. C++’s `hexfloat` stems from C‘s `%a` format, which merely guarantees that the leading digit is non-zero if the number is normal. An implementation is free to show the start of (approximately) π as `0x1.`, `0x3.`, `0x6.`, or `0xc.`, leaving the least significant bit in corresponding positions at the end. – Eric Postpischil Dec 09 '21 at 14:34
  • Even if it were not broken in this regard, using the I/O formatting to access the bits of a `double` is hugely inefficient, and there are simpler and better alternatives, such as `frexp`. – Eric Postpischil Dec 09 '21 at 14:35
  • @EricPostpischil Oups, then I misunderstood `hexfloat` so I removed that part of the answer. Do you happen to know the answer to what chux and I discussed above? Would it be ok to manipulate the LSB in the `double` directly (instead of `memcpy`ing the whole `double` out, changing it and copying it back) like I did [here](https://godbolt.org/z/zzrqh7PbT)? – Ted Lyngmo Dec 09 '21 at 16:14