J'aimerais afficher la représentation binaire (ou hexadécimale) d'un nombre à virgule flottante. Je sais convertir à la main (en utilisant la méthode ici ), mais je suis intéressé par les exemples de code qui font la même chose.
Bien que je sois particulièrement intéressé par les solutions C++ et Java, je me demande si les langages le rendent particulièrement facile, alors je fais ceci language agnostic J'aimerais voir des solutions dans d'autres langues.
EDIT: J'ai une bonne couverture de C, C++, C # et Java. Existe-t-il des gourous des langues alternatives qui souhaitent s’ajouter à la liste?
C/C++ est facile.
union ufloat {
float f;
unsigned u;
};
ufloat u1;
u1.f = 0.3f;
Ensuite, vous venez de générer u1.u
. Vous pouvez adapter cette implémentation .
Doubles tout aussi facile.
union udouble {
double d;
unsigned long u;
}
parce que les doubles sont 64 bits.
Java est un peu plus facile: utilisez Float.floatToRawIntBits () combiné avec Integer.toBinaryString () et Double.doubleToRawLongBits combiné avec Long.toBinaryString () .
En C:
int fl = *(int*)&floatVar;
&floatVar
obtiendrait la mémoire d'adresse puis (int*)
serait un pointeur sur cette mémoire d'adresse, enfin le * pour obtenir la valeur des 4 octets flottants dans int . Vous pourrez ensuite imprimer le format binaire ou le format hexadécimal.
Java: une recherche google trouve ce lien sur Les forums de Sun
spécifiquement (je n'ai pas essayé moi-même)
long binary = Double.doubleToLongBits(3.14159);
String strBinary = Long.toBinaryString(binary);
Dans .NET (y compris C #), vous avez BitConverter
qui accepte de nombreux types, permettant l’accès au binaire brut; Pour obtenir l'hex, ToString("x2")
est l'option la plus courante (peut-être encapsulée dans une méthode utilitaire):
byte[] raw = BitConverter.GetBytes(123.45);
StringBuilder sb = new StringBuilder(raw.Length * 2);
foreach (byte b in raw)
{
sb.Append(b.ToString("x2"));
}
Console.WriteLine(sb);
Curieusement, la base 64 a une conversion d'une ligne (Convert.ToBase64String
), mais la base 16 nécessite plus d'efforts. Sauf si vous référencez Microsoft.VisualBasic, dans ce cas:
long tmp = BitConverter.DoubleToInt64Bits(123.45);
string hex = Microsoft.VisualBasic.Conversion.Hex(tmp);
Je l'ai fait comme ça:
/*
@(#)File: $RCSfile: dumpdblflt.c,v $
@(#)Version: $Revision: 1.1 $
@(#)Last changed: $Date: 2007/09/05 22:23:33 $
@(#)Purpose: Print C double and float data in bytes etc.
@(#)Author: J Leffler
@(#)Copyright: (C) JLSS 2007
@(#)Product: :PRODUCT:
*/
/*TABSTOP=4*/
#include <stdio.h>
#include "imageprt.h"
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
extern const char jlss_id_dumpdblflt_c[];
const char jlss_id_dumpdblflt_c[] = "@(#)$Id: dumpdblflt.c,v 1.1 2007/09/05 22:23:33 jleffler Exp $";
#endif /* lint */
union u_double
{
double dbl;
char data[sizeof(double)];
};
union u_float
{
float flt;
char data[sizeof(float)];
};
static void dump_float(union u_float f)
{
int exp;
long mant;
printf("32-bit float: sign: %d, ", (f.data[0] & 0x80) >> 7);
exp = ((f.data[0] & 0x7F) << 1) | ((f.data[1] & 0x80) >> 7);
printf("expt: %4d (unbiassed %5d), ", exp, exp - 127);
mant = ((((f.data[1] & 0x7F) << 8) | (f.data[2] & 0xFF)) << 8) | (f.data[3] & 0xFF);
printf("mant: %16ld (0x%06lX)\n", mant, mant);
}
static void dump_double(union u_double d)
{
int exp;
long long mant;
printf("64-bit float: sign: %d, ", (d.data[0] & 0x80) >> 7);
exp = ((d.data[0] & 0x7F) << 4) | ((d.data[1] & 0xF0) >> 4);
printf("expt: %4d (unbiassed %5d), ", exp, exp - 1023);
mant = ((((d.data[1] & 0x0F) << 8) | (d.data[2] & 0xFF)) << 8) |
(d.data[3] & 0xFF);
mant = (mant << 32) | ((((((d.data[4] & 0xFF) << 8) |
(d.data[5] & 0xFF)) << 8) | (d.data[6] & 0xFF)) << 8) |
(d.data[7] & 0xFF);
printf("mant: %16lld (0x%013llX)\n", mant, mant);
}
static void print_value(double v)
{
union u_double d;
union u_float f;
f.flt = v;
d.dbl = v;
printf("SPARC: float/double of %g\n", v);
image_print(stdout, 0, f.data, sizeof(f.data));
image_print(stdout, 0, d.data, sizeof(d.data));
dump_float(f);
dump_double(d);
}
int main(void)
{
print_value(+1.0);
print_value(+2.0);
print_value(+3.0);
print_value( 0.0);
print_value(-3.0);
print_value(+3.1415926535897932);
print_value(+1e126);
return(0);
}
Sous Sun UltraSPARC, j'ai eu:
SPARC: float/double of 1
0x0000: 3F 80 00 00 ?...
0x0000: 3F F0 00 00 00 00 00 00 ?.......
32-bit float: sign: 0, expt: 127 (unbiassed 0), mant: 0 (0x000000)
64-bit float: sign: 0, expt: 1023 (unbiassed 0), mant: 0 (0x0000000000000)
SPARC: float/double of 2
0x0000: 40 00 00 00 @...
0x0000: 40 00 00 00 00 00 00 00 @.......
32-bit float: sign: 0, expt: 128 (unbiassed 1), mant: 0 (0x000000)
64-bit float: sign: 0, expt: 1024 (unbiassed 1), mant: 0 (0x0000000000000)
SPARC: float/double of 3
0x0000: 40 40 00 00 @@..
0x0000: 40 08 00 00 00 00 00 00 @.......
32-bit float: sign: 0, expt: 128 (unbiassed 1), mant: 4194304 (0x400000)
64-bit float: sign: 0, expt: 1024 (unbiassed 1), mant: 2251799813685248 (0x8000000000000)
SPARC: float/double of 0
0x0000: 00 00 00 00 ....
0x0000: 00 00 00 00 00 00 00 00 ........
32-bit float: sign: 0, expt: 0 (unbiassed -127), mant: 0 (0x000000)
64-bit float: sign: 0, expt: 0 (unbiassed -1023), mant: 0 (0x0000000000000)
SPARC: float/double of -3
0x0000: C0 40 00 00 .@..
0x0000: C0 08 00 00 00 00 00 00 ........
32-bit float: sign: 1, expt: 128 (unbiassed 1), mant: 4194304 (0x400000)
64-bit float: sign: 1, expt: 1024 (unbiassed 1), mant: 2251799813685248 (0x8000000000000)
SPARC: float/double of 3.14159
0x0000: 40 49 0F DB @I..
0x0000: 40 09 21 FB 54 44 2D 18 @.!.TD-.
32-bit float: sign: 0, expt: 128 (unbiassed 1), mant: 4788187 (0x490FDB)
64-bit float: sign: 0, expt: 1024 (unbiassed 1), mant: 2570638124657944 (0x921FB54442D18)
SPARC: float/double of 1e+126
0x0000: 7F 80 00 00 ....
0x0000: 5A 17 A2 EC C4 14 A0 3F Z......?
32-bit float: sign: 0, expt: 255 (unbiassed 128), mant: 0 (0x000000)
64-bit float: sign: 0, expt: 1441 (unbiassed 418), mant: -1005281217 (0xFFFFFFFFC414A03F)
Python:
Code:
import struct
def float2bin(number, hexdecimal=False, single=False):
bytes = struct.pack('>f' if single else '>d', number)
func, length = (hex, 2) if hexdecimal else (bin, 8)
byte2bin = lambda byte: func(ord(byte))[2:].ljust(length, '0')
return ''.join(map(byte2bin, bytes))
Échantillon:
>>> float2bin(1.0)
'1111110011110000000000000000000000000000000000000000000000000000'
>>> float2bin(-1.0)
'1011111111110000000000000000000000000000000000000000000000000000'
>>> float2bin(1.0, True)
'3ff0000000000000'
>>> float2bin(1.0, True, True)
'3f800000'
>>> float2bin(-1.0, True)
'bff0000000000000'
En Haskell, il n’existe pas de représentation interne des points flottants accessibles. Mais vous pouvez effectuer une sérialisation binaire à partir de nombreux formats, y compris Float et Double. La solution suivante est générique pour tout type ayant une instance de Data.Binary prise en charge:
module BinarySerial where
import Data.Bits
import Data.Binary
import qualified Data.ByteString.Lazy as B
elemToBits :: (Bits a) => a -> [Bool]
elemToBits a = map (testBit a) [0..7]
listToBits :: (Bits a) => [a] -> [Bool]
listToBits a = reverse $ concat $ map elemToBits a
rawBits :: (Binary a) => a -> [Bool]
rawBits a = listToBits $ B.unpack $ encode a
La conversion peut être faite avec rawBits:
rawBits (3.14::Float)
Mais si vous devez accéder à la valeur float de cette façon, vous faites probablement quelque chose de mal. La vraie question pourrait être Comment accéder à l’exposant et à la signification d’un nombre à virgule flottante? Les réponses sont exposant et significand de Prelude:
significand 3.14
0.785
exponent 3.14
2
Eh bien, les classes Float et Double (en Java) ont une méthode toHexString ('float'), ce qui est assez semblable à la conversion hex
Double.toHexString(42344);
Float.toHexString(42344);
C'est de la tarte!
Apparemment, personne ne se souciait de dire à quel point il est trivial d’obtenir la notation hexadécimale de l’exposant , alors voici:
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
// C++11 manipulator
cout << 23.0f << " : " << std::hexfloat << 23.0f << endl;
// C equivalent solution
printf("23.0 in hexadecimal is: %A\n", 23.0f);
}
Je devais penser à poster ici pendant un moment car cela pourrait inspirer les autres codeurs à faire du mal avec C. J'ai quand même décidé de le poster, mais rappelez-vous: n'écrivez pas ce genre de code dans une application sérieuse sans documentation appropriée et même alors. réfléchis trois fois.
Avec le disclaimer de côté, c'est parti.
Commencez par écrire une fonction pour imprimer, par exemple, une longue variable non signée au format binaire:
void printbin(unsigned long x, int n)
{
if (--n) printbin(x>>1, n);
putchar("01"[x&1]);
}
Malheureusement, nous ne pouvons pas utiliser directement cette fonction pour imprimer notre variable float, nous devons donc en modifier quelques-uns. Le hack a probablement l'air familier pour tous ceux qui ont lu l'existence de l'astuce racine carrée de Carmack pour Quake. L'idée est de définir une valeur pour notre variable float, puis d'obtenir le même masque de bits pour notre variable entier long. Nous prenons donc l’adresse mémoire de f, la convertissons en une valeur * longue et utilisons ce pointeur pour obtenir le masque binaire de f comme un long signe non signé. Si vous deviez imprimer cette valeur aussi longtemps que non signée, le résultat serait un désordre, mais les bits sont les mêmes que dans la valeur float d'origine, donc cela n'a pas vraiment d'importance.
int main(void)
{
long unsigned lu;
float f = -1.1f;
lu = *(long*)&f;
printbin(lu, 32);
printf("\n");
return 0;
}
Si vous pensez que cette syntaxe est affreuse, vous avez raison.
Vous pouvez facilement convertir variable float en variable int (ou double en long) en utilisant un tel code en C #:
float f = ...;
unsafe
{
int i = *(int*)&f;
}
En C++, vous pouvez afficher la représentation binaire de la manière suivante:
template <class T>
std::bitset<sizeof(T)*8> binary_representation(const T& f)
{
typedef unsigned long TempType;
assert(sizeof(T)<=sizeof(TempType));
return std::bitset<sizeof(T)*8>(*(reinterpret_cast<const TempType*>(&f)));
}
la limite ici est due au fait que le paramètre bitset long est un unsigned long, de sorte qu'il fonctionne pour flotter, vous pouvez utiliser autre chose que le jeu de bits et l'extension qui l'assert.
En passant, la suggestion de cletus échoue en ce sens que vous avez besoin d’un «long non long» pour couvrir un double, de toute façon vous avez besoin de quelque chose qui montre la représentation binaire (1 ou 0).
Pour référence future, C++ 2a introduit un nouveau modèle de fonction bit_cast
qui fait le travail.
template< class To, class From >
constexpr To bit_cast(const From& from) noexcept;
Nous pouvons simplement appeler,
float f = 3.14;
std::bit_cast<int>(f);
Pour plus de détails, voir https://en.cppreference.com/w/cpp/numeric/bit_cast