# Software implementation of 48-bit double

Discussion in 'C Programming' started by Philipp Klaus Krause, Mar 28, 2012.

1. ### Philipp Klaus KrauseGuest

Are there any free implementations of C11-compliant (though AFAIK this
is the same as older standard here) 48 bit double?

Philipp

Philipp Klaus Krause, Mar 28, 2012

2. ### Philipp Klaus KrauseGuest

On 28.03.2012 22:05, Philipp Klaus Krause wrote:
> Are there any free implementations of C11-compliant (though AFAIK this
> is the same as older standard here) 48 bit double?
>
> Philipp

Less than 48 bits, if that is possible, would be interesting, too.

Philipp

Philipp Klaus Krause, Mar 28, 2012

3. ### jacob naviaGuest

Le 28/03/12 22:05, Philipp Klaus Krause a Ã©crit :
> Are there any free implementations of C11-compliant (though AFAIK this
> is the same as older standard here) 48 bit double?
>
> Philipp

Yes. Google after "CEPHES Mathematical library" By S. L. Moshier

jacob navia, Mar 28, 2012
4. ### BGBGuest

On 3/28/2012 1:05 PM, Philipp Klaus Krause wrote:
> Are there any free implementations of C11-compliant (though AFAIK this
> is the same as older standard here) 48 bit double?
>

if relevant, I use a shaved-down 64-bit double as a 48-bit double
(mostly related to stuffing floating-point values into pointers on
64-bit targets, although a person can technically go the other direction
as well and encode a pointer into a double on both 32 and 64-bit targets).

the conversion then was to simply discard the low 16 bits.

the main merit though was that this allows reasonably efficient
conversions to/from the format.

although not strictly portable, a conversion like this can be made to
work on many common targets:

uint64_t DoubleToDouble48(double v)
{ return((*(uint64_t *)(&v))>>16); }

double Double48ToDouble(uint64_t v)
{
uint64_t v2=v<<16;
return(*(double *)(&v2));
}

and, for the pointer case (assumes a target with 64-bit pointers):
byte *double48_ptrbase; //base address

void *DoubleToPtrDouble48(double v)
{
return(double48_ptrbase+((*(uint64_t *)(&v))>>16));
}

double PtrDouble48ToDouble(void *v)
{
uint64_t v2=(v-double48_ptrbase)<<16;
return(*(double *)(&v2));
}

acknowledged in advance that this is non-portable / undefined / ...

other conversions are possible, but are generally considerably longer
and more expensive.

generally, it is faster (with the shift trick) to perform arithmetic by
converting back to normal doubles and doing arithmetic this way (doing
the arithmetic via integer operations tends to be fairly expensive).

or such...

BGB, Mar 28, 2012
5. ### Kaz KylhekuGuest

On 2012-03-28, BGB <> wrote:
> On 3/28/2012 1:05 PM, Philipp Klaus Krause wrote:
>> Are there any free implementations of C11-compliant (though AFAIK this
>> is the same as older standard here) 48 bit double?
>>

>
> if relevant, I use a shaved-down 64-bit double as a 48-bit double
> (mostly related to stuffing floating-point values into pointers on
> 64-bit targets

Ouch, that's a lot of precision bits to give up just for this kind of stuffing
trick. I would not be willing to sacrifice more than, say, 3. That's enough
for a type tag with 8 values.

> and, for the pointer case (assumes a target with 64-bit pointers):
> byte *double48_ptrbase; //base address
>
> void *DoubleToPtrDouble48(double v)
> {
> return(double48_ptrbase+((*(uint64_t *)(&v))>>16));
> }

Instead of shifting and using an offset, you could put a couple of tag bits
into the low order bits of the pointer which indicate "this is a double". Then
mask them to zero to retrieve the float.

Kaz Kylheku, Mar 29, 2012
6. ### BGBGuest

On 3/28/2012 4:12 PM, Kaz Kylheku wrote:
> On 2012-03-28, BGB<> wrote:
>> On 3/28/2012 1:05 PM, Philipp Klaus Krause wrote:
>>> Are there any free implementations of C11-compliant (though AFAIK this
>>> is the same as older standard here) 48 bit double?
>>>

>>
>> if relevant, I use a shaved-down 64-bit double as a 48-bit double
>> (mostly related to stuffing floating-point values into pointers on
>> 64-bit targets

>
> Ouch, that's a lot of precision bits to give up just for this kind of stuffing
> trick. I would not be willing to sacrifice more than, say, 3. That's enough
> for a type tag with 8 values.
>

I had considered before giving it a dedicated 56-bit range, but never
really got around it, and found it good enough.

it is still considerably more accurate than the 28-bit flonum used on
32-bit targets.

>> and, for the pointer case (assumes a target with 64-bit pointers):
>> byte *double48_ptrbase; //base address
>>
>> void *DoubleToPtrDouble48(double v)
>> {
>> return(double48_ptrbase+((*(uint64_t *)(&v))>>16));
>> }

>
> Instead of shifting and using an offset, you could put a couple of tag bits
> into the low order bits of the pointer which indicate "this is a double". Then
> mask them to zero to retrieve the float.

this is possible, except that low-order tag bits are really annoying as
they require constraining that every pointer be aligned, say, on 8 bytes
(and a random byte-aligned character pointer can make a mess of things).

in my case, I had used a different strategy:
unusable parts of the address space are used for tagged values.

on x86, this basically means the range from 0xC0000000 to 0xFFFFFFFF,
since this is space is (generally) reserved for the OS on both Windows
and Linux.

both "fixnum" and "flonum" are 28 bits in this case, with the rest of
the space being used for other smaller type ranges.

on x86-64, a 56-bit glob of address space was used for tagged values
(IIRC, starting at something like 0x7F000000_00000000), which was
in-turn divided into around 256 48-bit regions.

on current HW though, a person could probably get by with a larger 60 or
62-bit region though.

say, a 60 bit region starting at 0x70000000_00000000, probably using the
next 4 bits as a tag, giving 16 regions each being 56 bits.

or, more extreme:
0x40000000_00000000 to 0xBFFFFFFF_FFFFFFFF is used as a single giant
63-bit region, with 3 more bits as tags, allowing for up to 8 60-bit
regions.

but, even with 16 bits shaved off, a double is still plenty accurate for
most things IME.

note that this stuff is not used for addressable memory objects though,
which use a different strategy for identifying types:
namely, identifying type either by memory region (ex: cons cells or
interned strings) or by tags within the memory-object headers (most
other allocated memory). granted, technically, these are themselves
based on address regions (for example, the MM/GC will check against
relevant "heap chunks" or similar, when relevant).

note that the MM/GC does not require pointers to point at the start of
memory objects (it can find the start of the memory object by itself),
as well as determine when address is outside of areas it knows about.

pretty much all types are (canonically) identified by "type names" (as
strings), rather than by tag bits or similar (although tag values are
used internally).

or such...

BGB, Mar 29, 2012

### Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.