Ir al contenido


Foto

leer y guardar un tipo flotante según el tipo de endian

tipos flotantes endianess Lazarus

  • Por favor identifícate para responder
10 respuestas en este tema

#1 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 19 febrero 2016 - 06:08

Hoy les vengo con una duda a la que no se como responderme. Hasta el momento no había prestado atención al tema del endianess pero cuando uno va a almacenar valores en un archivo y los cuáles puede ser compartidos o leerse en diferentes equipos pueden empezar los peros si justamente sus procesadores son de distinto endian.

No me había percatado de esto sino fuera porque durante una de mis investigaciones para mi proyecto personal comentaba de esto. Y se me prendió la alarma.

 

¿Que es eso del endian? Pues básicamente el formato en como se representan los números de más de un byte en el procesador. Basicamente hay dos tipos: big-endian y litte-endian.

En el primero los bits más representativos están al principio, mientras que para little-endian en cambio son los menos representativos. Es decir están invertidos.

 

Ya estuve leyendo en la documentación de Lazarus que efectivamente hay maneras seguras de poder ir de un tipo de endian a otro. Y hay un buen repertorio de funciones para trabajar con los tipos enteros:


delphi
  1. function SwapEndian(const AValue: SmallInt): SmallInt;{$ifdef SYSTEMINLINE}inline;{$endif}
  2. function SwapEndian(const AValue: Word): Word;{$ifdef SYSTEMINLINE}inline;{$endif}
  3. function SwapEndian(const AValue: LongInt): LongInt;
  4. function SwapEndian(const AValue: DWord): DWord;
  5. function SwapEndian(const AValue: Int64): Int64;
  6. function SwapEndian(const AValue: QWord): QWord;
  7.  
  8. function BEtoN(const AValue: SmallInt): SmallInt;{$ifdef SYSTEMINLINE}inline;{$endif}
  9. function BEtoN(const AValue: Word): Word;{$ifdef SYSTEMINLINE}inline;{$endif}
  10. function BEtoN(const AValue: LongInt): LongInt;{$ifdef SYSTEMINLINE}inline;{$endif}
  11. function BEtoN(const AValue: DWord): DWord;{$ifdef SYSTEMINLINE}inline;{$endif}
  12. function BEtoN(const AValue: Int64): Int64;{$ifdef SYSTEMINLINE}inline;{$endif}
  13. function BEtoN(const AValue: QWord): QWord;{$ifdef SYSTEMINLINE}inline;{$endif}
  14.  
  15. function LEtoN(const AValue: SmallInt): SmallInt;{$ifdef SYSTEMINLINE}inline;{$endif}
  16. function LEtoN(const AValue: Word): Word;{$ifdef SYSTEMINLINE}inline;{$endif}
  17. function LEtoN(const AValue: LongInt): LongInt;{$ifdef SYSTEMINLINE}inline;{$endif}
  18. function LEtoN(const AValue: DWord): DWord;{$ifdef SYSTEMINLINE}inline;{$endif}
  19. function LEtoN(const AValue: Int64): Int64;{$ifdef SYSTEMINLINE}inline;{$endif}
  20. function LEtoN(const AValue: QWord): QWord;{$ifdef SYSTEMINLINE}inline;{$endif}
  21.  
  22. function NtoBE(const AValue: SmallInt): SmallInt;{$ifdef SYSTEMINLINE}inline;{$endif}
  23. function NtoBE(const AValue: Word): Word;{$ifdef SYSTEMINLINE}inline;{$endif}
  24. function NtoBE(const AValue: LongInt): LongInt;{$ifdef SYSTEMINLINE}inline;{$endif}
  25. function NtoBE(const AValue: DWord): DWord;{$ifdef SYSTEMINLINE}inline;{$endif}
  26. function NtoBE(const AValue: Int64): Int64;{$ifdef SYSTEMINLINE}inline;{$endif}
  27. function NtoBE(const AValue: QWord): QWord;{$ifdef SYSTEMINLINE}inline;{$endif}
  28.  
  29. function NtoLE(const AValue: SmallInt): SmallInt;{$ifdef SYSTEMINLINE}inline;{$endif}
  30. function NtoLE(const AValue: Word): Word;{$ifdef SYSTEMINLINE}inline;{$endif}
  31. function NtoLE(const AValue: LongInt): LongInt;{$ifdef SYSTEMINLINE}inline;{$endif}
  32. function NtoLE(const AValue: DWord): DWord;{$ifdef SYSTEMINLINE}inline;{$endif}
  33. function NtoLE(const AValue: Int64): Int64;{$ifdef SYSTEMINLINE}inline;{$endif}
  34. function NtoLE(const AValue: QWord): QWord;{$ifdef SYSTEMINLINE}inline;{$endif}

Gracias las funciones XtoN es posible leer ya sea BE (Big-endian) o LE (Litte-endian) y pasarlo al tipo nativo del procesador. Y a su inverso NtoX hacer lo opuesto.

 

De esta forma uno por ejemplo puede definir que sus archivos siempre se guarden en big-endian por ejemplo y proceder a emplear BEtoN() para leerlo y recuperar el dato sin problemas. Y usar NtoBE() al momento de guardarlo.

 

Hasta ahí todo bien... con los enteros. Pero luego recordé el hilo en el que escafandra daba una advertencia sobre como leer el signo de un doble y "adivinar" en que "mitad" saber leer.

 

Y ahora me asalta la duda ¿En los tipos flotantes también rige el sistema endianess? Porque me llama poderosamente la atención que si efectivamente es asi, ¿porqué es que no existe entonces las funciones sobrecargadas para los flotantes? O es que yo soy medio lento y no las encuentro.

 

Esto me lleva entonces a considerar a escribir mi propia implementación análogas a las detalladas anteriormente. Pensé en estudiar por el comienzo... a ver que es lo que hace en IsNan() y en IsInfinite():


delphi
  1. function IsNan(const d : Double): Boolean;
  2. var
  3. fraczero, expMaximal: boolean;
  4. begin
  5. {$if defined(FPC_BIG_ENDIAN) or defined(FPC_DOUBLE_HILO_SWAPPED)}
  6. expMaximal := ((TSplitDouble(d).cards[0] shr 20) and $7ff) = 2047;
  7. fraczero:= (TSplitDouble(d).cards[0] and $fffff = 0) and
  8. (TSplitDouble(d).cards[1] = 0);
  9. {$else FPC_BIG_ENDIAN}
  10. expMaximal := ((TSplitDouble(d).cards[1] shr 20) and $7ff) = 2047;
  11. fraczero := (TSplitDouble(d).cards[1] and $fffff = 0) and
  12. (TSplitDouble(d).cards[0] = 0);
  13. {$endif FPC_BIG_ENDIAN}
  14. Result:=expMaximal and not(fraczero);
  15. end;


delphi
  1. function IsInfinite(const d : Double): Boolean;
  2. var
  3. fraczero, expMaximal: boolean;
  4. begin
  5. {$if defined(FPC_BIG_ENDIAN) or defined(FPC_DOUBLE_HILO_SWAPPED)}
  6. expMaximal := ((TSplitDouble(d).cards[0] shr 20) and $7ff) = 2047;
  7. fraczero:= (TSplitDouble(d).cards[0] and $fffff = 0) and
  8. (TSplitDouble(d).cards[1] = 0);
  9. {$else FPC_BIG_ENDIAN}
  10. expMaximal := ((TSplitDouble(d).cards[1] shr 20) and $7ff) = 2047;
  11. fraczero := (TSplitDouble(d).cards[1] and $fffff = 0) and
  12. (TSplitDouble(d).cards[0] = 0);
  13. {$endif FPC_BIG_ENDIAN}
  14. Result:=expMaximal and fraczero;
  15. end;

Siendo TSplitDouble un simple record:


delphi
  1. type
  2. TSplitDouble = packed record
  3. cards: Array[0..1] of cardinal;
  4. end;

Y Cardinal un simple alias del LongWord.

 

Pero hago aguas, al intentar razonarlo. ¿Alguna guía?

 

Saludos,


  • 0

#2 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 21 febrero 2016 - 10:41

¿Alguien tiene algún norte para tirar? :(

He estado entre ayer y hoy tratando de entender fielmente como es que interviene el proceso de intercambio de los bytes entre el Big-Endian y Little-Endian y que rol cumple en ello la función IsNan() y/o IsInfinite().

La teoría dice que en Little-Endian los bytes están ordenados de forma inversa. Es decir que dado un número double 0xaabbccddeeffgghh como lo conocemos habitualmente, en Litte-Endian estará ordenado entonces como hhggffeeddccbbaa. De este modo entonces ya que el formato del double es:

 

[63][62..52][51..0] para el signo, exponente, y fracción respectivamente. En 0xaa tendremos lo siguiente: seee|eeee es decir el bit de signo, seguido de los 7 bits más significativos del exponente. En 0xbb tendremos eeee|ffff es decir los 4bits más bajos y restantes del exponente y a continuación los 4 bits más significativos de la fracción. El resto de los bytes hacen a la fracción.

 

Pues, al poner esto en práctica y para entender el funcionamiento de IsNan() hice a mano una prueba de escritorio a con la representación binaria del NAN: 7F..F pero o es que hago mal el dezplazamiento a la derecha de la parte alta (Cards[1] = [eeffgghh]) o metí mal el dedo al calcular el binario de $FFFFF, 2047 y $7FF.

 

He tratado de localizar fuentes precisas que traten el tema de endianness con los flotantes pero no he llegado a algo concreto, preciso y sea digerible. Incluso he probado a ver si por casualidad ya hay algo en Delphi. Y si bien encontré un hilo en el foro de Embarcadero, no se termina enteniendo. Esto es lo último, y que parece más rescatable, de entender. Claro está: no expone el concepto desde el aspecto de los tipos flotantes pero es algo.

 

Por si sirve de ayuda. Esto es lo que hace BEtoN()


delphi
  1. function BEtoN(const AValue: Int64): Int64;{$ifdef SYSTEMINLINE}inline;{$endif}
  2. begin
  3. {$IFDEF ENDIAN_BIG}
  4. Result := AValue;
  5. {$ELSE}
  6. Result := SwapEndian(AValue);
  7. {$ENDIF}
  8. end;

Y si inverso NtoBE():


delphi
  1. function NtoBE(const AValue: Int64): Int64;{$ifdef SYSTEMINLINE}inline;{$endif}
  2. begin
  3. {$IFDEF ENDIAN_BIG}
  4. Result := AValue;
  5. {$ELSE}
  6. Result := SwapEndian(AValue);
  7. {$ENDIF}
  8. end;

La versión análoga para el caso de LE es casi idéntica. Lo que cambia es el IFDEF que en lugar de ser BIG es el ENDIAN_LITTLE.

 

Lo importante de todo, como pueden ver, es esa función de intercambio:


delphi
  1. function SwapEndian(const AValue: LongInt): LongInt; assembler; nostackframe;
  2. asm
  3. {$ifdef win64}
  4. movl %ecx, %eax
  5. {$else win64}
  6. movl %edi, %eax
  7. {$endif win64}
  8. bswap %eax
  9. end;
  10.  
  11.  
  12. function SwapEndian(const AValue: DWord): DWord; assembler; nostackframe;
  13. asm
  14. {$ifdef win64}
  15. movl %ecx, %eax
  16. {$else win64}
  17. movl %edi, %eax
  18. {$endif win64}
  19. bswap %eax
  20. end;
  21.  
  22.  
  23. function SwapEndian(const AValue: Int64): Int64; assembler; nostackframe;
  24. asm
  25. {$ifdef win64}
  26. movq %rcx, %rax
  27. {$else win64}
  28. movq %rdi, %rax
  29. {$endif win64}
  30. bswap %rax
  31. end;
  32.  
  33.  
  34. function SwapEndian(const AValue: QWord): QWord; assembler; nostackframe;
  35. asm
  36. {$ifdef win64}
  37. movq %rcx, %rax
  38. {$else win64}
  39. movq %rdi, %rax
  40. {$endif win64}
  41. bswap %rax
  42. end;

Mi objetivo es lograr algo parecido a eso, pero con los flotantes. A ver si con todo esto alguien puede darme más luz.

 

Saludos,


  • 0

#3 enecumene

enecumene

    Webmaster

  • Administrador
  • 7.419 mensajes
  • LocationRepública Dominicana

Escrito 22 febrero 2016 - 09:21

Esta función lo tenía por ahí y creo que sirve para Big Endians con valores flotantes 4 bytes IEEE, espero te sirva:


delphi
  1. function SwapEndian(value:single):single;
  2. var
  3. h,i: TFloatRec;
  4. begin
  5. h.floatvalue := value;
  6. i.bytevalues[0] := h.bytevalues[3];
  7. i.bytevalues[1] := h.bytevalues[2];
  8. i.bytevalues[2] := h.bytevalues[1];
  9. i.bytevalues[3] := h.bytevalues[0];
  10. result := i.floatvalue;
  11. end;


  • 1

#4 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 22 febrero 2016 - 11:28

 

Esta función lo tenía por ahí y creo que sirve para Big Endians con valores flotantes 4 bytes IEEE, espero te sirva:


delphi
  1. function SwapEndian(value:single):single;
  2. var
  3. h,i: TFloatRec;
  4. begin
  5. h.floatvalue := value;
  6. i.bytevalues[0] := h.bytevalues[3];
  7. i.bytevalues[1] := h.bytevalues[2];
  8. i.bytevalues[2] := h.bytevalues[1];
  9. i.bytevalues[3] := h.bytevalues[0];
  10. result := i.floatvalue;
  11. end;

 

Gracias Fernando por esa muestra. ¿Por casualidad tienes la declaración del tipo TFloatRec?

 

En Lazarus existe un TFloatRec, pero quizá no sea el mismo que aparenta en tu código:


delphi
  1. TFloatRec = Record
  2. Exponent: Integer;
  3. Negative: Boolean;
  4. Digits: Array[0..18] Of Char;
  5. End;

Saludos,


  • 0

#5 enecumene

enecumene

    Webmaster

  • Administrador
  • 7.419 mensajes
  • LocationRepública Dominicana

Escrito 22 febrero 2016 - 01:24

La que dispone de Delphi, tiene una pequeña diferencia:.
  • 0

#6 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 22 febrero 2016 - 01:50

Si, ya vi esa página. Y por eso te lo comento.

Porque evidentemente el TFloatRec de tu código es distinto, no tiene ningún campo con los nombres de tu ejemplo. Pareciera dar a entender algo como esto:


php
  1. TFloatRec = record
  2. floatvalue: single;
  3. bytesvalues: array[0..3] of byte; // no estoy seguro del tipo!

Algo me dice que TFloatRec es un registro variant, pero tu código no da pista de cual es el campo a modo de "bandera". Por lo general se hace algo así:


php
  1. TRegistroVariant1 = record
  2. case Campo: boolean of
  3. true: (CampoX: tipoX)
  4. false: (CampoY: tipoY)

O algo como:


php
  1. TRegistroVariant2 = record
  2. case Campo: integer of
  3. 0: (Campo0: tipo0);
  4. ...
  5. 10: (Campo10: tipo10);

Es decir, debe haber un campo que indique cual forma se va a emplear. La otra que quizá sea, es que quizá tenga puesta la cláusula absolute:


php
  1. TFloatRec = record
  2. floatvalue: single;
  3. bytesvalues: array[0..3] of byte absolute floatvalue; // pero no estoy seguro si así es como se declararía

Saludos,


  • 0

#7 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.111 mensajes
  • LocationMadrid - España

Escrito 23 febrero 2016 - 10:35

Yo no me preocuparía tanto por el tipo de dato.

Si recuerdas el tema del signo de un double en C, la función a bajo nivel que propuse era esta:


cpp
  1. int Sign(double d)
  2. {
  3. if(d==0) return 0;
  4. DWORD* R = (DWORD*)&d + 1;
  5. if(*R >> 31 & 1) return -1;
  6. return 1;
  7. }

Esa función tiene en cuenta que los bytes están invertidos (little-endian) y que el tipo ocupa 64 bits. Lo primero que hace es buscar el segundo DWORD en memoria: (DWORD*)&d + 1. Puesto que al estar invertidos esa parte es la más significativa. Luego localiza el signo usando un simple desplazamiento.

 

Esto es así porque independientemente del tipo, los bytes están invertidos. Esta es la razón por la que no encuentras funciones SwapEndian que no reciban otra cosa que enteros. Un double es un "entero especial" de 64 bits en el que cada bit tiene un "significado especial" en la notación IEEE.

 

Las funciones que encontraste para Lazarus sirven para cualquier tipo numérico, solo debes conocer su tamaño y aplicar la que corresponda a cada caso.

 

Realiza algún experimento y nos cuentas.

 

PD/ Para el problema de leer y escribir números en un archivo que sea interpretable en cualquier plataforma, quizás lo más simple sea escribirlos en modo texto.

 

 

Saludos.


  • 2

#8 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 23 febrero 2016 - 11:13

Yo no me preocuparía tanto por el tipo de dato.

Si recuerdas el tema del signo de un double en C, la función a bajo nivel que propuse era esta:


cpp
  1. int Sign(double d)
  2. {
  3. if(d==0) return 0;
  4. DWORD* R = (DWORD*)&d + 1;
  5. if(*R >> 31 & 1) return -1;
  6. return 1;
  7. }

Esa función tiene en cuenta que los bytes están invertidos (little-endian) y que el tipo ocupa 64 bits. Lo primero que hace es buscar el segundo DWORD en memoria: (DWORD*)&d + 1. Puesto que al estar invertidos esa parte es la más significativa. Luego localiza el signo usando un simple desplazamiento.

 

Esto es así porque independientemente del tipo, los bytes están invertidos. Esta es la razón por la que no encuentras funciones SwapEndian que no reciban otra cosa que enteros. Un double es un "entero especial" de 64 bits en el que cada bit tiene un "significado especial" en la notación IEEE.

 

Las funciones que encontraste para Lazarus sirven para cualquier tipo numérico, solo debes conocer su tamaño y aplicar la que corresponda a cada caso.

 

Realiza algún experimento y nos cuentas.

 

PD/ Para el problema de leer y escribir números en un archivo que sea interpretable en cualquier plataforma, quizás lo más simple sea escribirlos en modo texto.

 

 

Saludos.

 

En cuanto pueda darme un tiempo para hacer unas pruebas lo veo. Se que debería ser fácil la cosa... se trata de intercambiar la posición de bytes. Pero por algún motivo que no logro descifrar tal intercambio de bytes debe ser congruente y compatible a su vez con la función IsNan() y es que por ésta empecé a hacer pruebas de escritorio y me he dado algunos golpes de porqué algo no me estaba cuadrando.

 

Una de las primeras cosas que podría probar sería algo como:


delphi
  1. function floatBEToN(AValue: Double): Double;
  2. var IntDouble: Int64 absolute AValue; // tengo entendido que con absolute se consigue que la variable IntDouble apunte a la misma memoria concreta de AValue
  3. begin
  4. result := Double(BEToN(IntDouble));
  5. end;

O cosas como BetoN(Int64(AValue)) aunque tengo mis reservas de que algo así funcione apropiadamente.

 

Quizá sea como dices que al final de todo no interesa si los bytes que se leen hacen a un entero o un número flotante, pero para mi no estaría mal que se pudiera evitar indirecciones y casteos y que existan funciones que trabajen con los flotantes. Si fuera viable concebir una función floatSwapEndian() sería buenísimo.

 

Mi problema es que la mejor forma de saber que esto funciona sería usarlo esto mismo en un equipo BIG-ENDIAN pero yo no dispongo de uno.

 

Lo de guardar en modo texto también tiene sus cosas... el problema de encode a más de uno puede dejarlo desvelado.

 

Saludos,


  • 0

#9 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.111 mensajes
  • LocationMadrid - España

Escrito 23 febrero 2016 - 11:46

...

Quizá sea como dices que al final de todo no interesa si los bytes que se leen hacen a un entero o un número flotante, pero para mi no estaría mal que se pudiera evitar indirecciones y casteos y que existan funciones que trabajen con los flotantes. Si fuera viable concebir
 
Mi problema es que la mejor forma de saber que esto funciona sería usarlo esto mismo en un equipo BIG-ENDIAN pero yo no dispongo de uno. 
...

 
Ten en cuenta que al trabajar a bajo nivel es inevitable realizar cast de tipos mediante punteros. Puedes usar la versión de SwapEndian int64 con el cast necesario para crearte tu función para double y listo. Un simple cast y una llamada.

 

Tampoco dispongo de un equipo BIG-ENDIAN pero el análisis de lo que ocurre en memoria en nuestros equipos little-endian basta para ver el asunto de la conversión.

 

Saludos.


  • 0

#10 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 23 febrero 2016 - 03:14

 
Ten en cuenta que al trabajar a bajo nivel es inevitable realizar cast de tipos mediante punteros. Puedes usar la versión de SwapEndian int64 con el cast necesario para crearte tu función para double y listo. Un simple cast y una llamada.

 

Tampoco dispongo de un equipo BIG-ENDIAN pero el análisis de lo que ocurre en memoria en nuestros equipos little-endian basta para ver el asunto de la conversión.

 

Saludos.

 

¿Entonces lo que estuve pensando en vos alta sobre hacer un Int64(BEtoN) podría resultar?

Umm. Lo voy a ver. Anoche había probado descifrar la función IsNan() y en como es que lee la mitad y aplica el desplazo de bits para compararlo con el byte donde se localiza el exponente según el formato IEEE. No guardé esa prueba pero recuerdo que estuve haciendo estos tipos de cast, y mostraba en edits en forma binaria el Cards[1] antes y después de aplicar shr. El original tenía 13 bits 1, y el corrimiento tenía 12. Uno de los 1 pasó a ser 0. Me animo a jurarlo. Los conté 3 veces.

Probaba justamente con el valor Nan para que la prueba diera true.

Tendré que volver sobre mis pasos. Algo se me escapa.

 

A falta de equipo BIG-ENDIAN no queda más que probar y confiar en que el hacer un IntToHex() muestre la cosa perfectamente cambiada.

 

Saludos,


  • 0

#11 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.111 mensajes
  • LocationMadrid - España

Escrito 23 febrero 2016 - 05:24

Ahora que estoy en casa, te pongo un ejemplo de como implementar una versión Double de SwapEndian. El cast debe hacerse sobre punteros y no sobre valores:


delphi
  1. function SwapEndianD(const AValue: Double): Double;
  2. var
  3.   I: Int64;
  4. begin
  5.   I:= SwapEndian(Pint64(@AValue)^);
  6.   Result:= PDouble(@I)^;
  7. end;



Saludos.
  • 1





Etiquetado también con una o más de estas palabras: tipos flotantes, endianess, Lazarus

IP.Board spam blocked by CleanTalk.