[PHP] 4 bytes inlezen als IEEE 754 32bit float

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
Ik heb een stream binaire data waarin een 4-bytes float zit verwerkt in het IEEE 754 formaat.
> Wikipedia: IEEE 754-1985

Ik zoek een oplossing om deze 4 bytes, opgeslagen in een string, om te zetten in een float.

Waardes als NaN, -INF en INF moeten bewaard blijven.

pack/unpack zijn niet betrouwbaar omdat de floats per platform kunnen afwijken; althans, dat maak ik op uit de omschrijving "f: float (machine dependent size and representation)".
> http://nl.php.net/manual/en/function.pack.php

Ik heb zelf een functie geschreven; hij werkt niet echt naar behoren:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    public function readFloat() {
        //leest 4 bytes
        $str = $this->readFully(4);
        
        //zet bytes om in ints
        $b0 = ord($str[0]);
        $b1 = ord($str[1]);
        $b2 = ord($str[2]);
        $b3 = ord($str[3]);

        echo "\r\n".str_pad(decbin($b0<<24|$b1<<16|$b2<<8|$b3), 32, '0', STR_PAD_LEFT);

        //sign
        $sgn = ($b0&0x80)>>7;
        
        //exponent
        $exp = (($b0&0x7F)<<1|($b1&0x80)>>7)-0x7F;

        //fraction
        $frc = ($b1&0x7F)<<16|$b2<<8|$b3;

        echo "\r\n".'SGN:'.$sgn.', EXP:'.$exp.', FRC:'.$frc."\r\n";

        $float = pow(-1,$sgn)*pow(2,$exp)*(1+$frc);
        return $float;
    }


De resultaten van deze functie zijn: (Input, Output, "Debug")
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
0
5.87747175411E-39
0000 0000 0000 0000 0000 0000 0000 0000
SGN:0, EXP:-127, FRC:0

1
1
0011 1111 1000 0000 0000 0000 0000 0000
SGN:0, EXP:0, FRC:0

-1
-1
1011 1111 1000 0000 0000 0000 0000 0000
SGN:1, EXP:0, FRC:0

NaN
1.42724803299E+45
0111 1111 1100 0000 0000 0000 0000 0000
SGN:0, EXP:128, FRC:4194304

INF
3.40282366921E+38
0111 1111 1000 0000 0000 0000 0000 0000
SGN:0, EXP:128, FRC:0

-INF
-3.40282366921E+38
1111 1111 1000 0000 0000 0000 0000 0000
SGN:1, EXP:128, FRC:0

0.35414314 (random)
873616.25
0011 1110 1011 0101 0101 0010 0100 0000
SGN:0, EXP:-5, FRC:5166302

[ Voor 23% gewijzigd door Gamebuster op 09-05-2010 23:32 ]

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
PHP:
1
var_dump(unpack('f', pack('i', 1059760811)));

This is, of course, machine dependent, but I don't know of any machine running PHP that doesn't use IEEE 754 floats.
:p

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
64bit systemen?

Maar owke, ik zal die eens proberen, maar heb toch liever dat er een functie is die zelf met de bitjes speelt zodat ik zeker weet dat het op ieder systeem werkt.

edit:

0: 0
1: 1
-1: -1
NaN: NAN
INF: INF
-INF: -INF
RAND: 0.622510313988

Il pleut :)
Maar nogmaals, kan ik zeker zijn dat dit op IEDER (normaal) systeem werkt? 64bit, 32bit, win, lin, mac...

Ik test het in een Mac OS X versie, maar weet niet of ik 64bit of 32bit heb.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    /**
     * Reads a single signed 32bit int.
     * @return int The int.
     */
    public function readInt() {
        $str = $this->readFully(4);

        $b0 = ord($str[0]);
        $b1 = ord($str[1]);
        $b2 = ord($str[2]);
        $b3 = ord($str[3]);

        $sgn = ($b0&0x80)>>7;
        $int = ($b0&0x7F)<<24|$b1<<16|$b2<<8|$b3;

        if($sgn) {
            $int -= 0x80000000;
        }

        return $int;
    }

    /**
     * Reads a single 32bit float.
     * @return float The float.
     */
    public function readFloat() {
        $int = $this->readInt();
        $array = unpack('f',pack('i', $int));
        return $array[1];
    }

[ Voor 107% gewijzigd door Gamebuster op 09-05-2010 23:40 ]

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03-10 16:38

.oisyn

Moderator Devschuur®

Demotivational Speaker

Idd, ik zou er niet vanuit gaan dat je code ooit op een machine runt die geen 32 bits ieee float gebruikt.

Desalniettemin, het is vrij logisch dat je functie niet naar behoren werkt. Je handelt denormal numbers niet af (waar 0 ook onder valt), en je doet niets met de speciale waarden voor NaN e.d.. Ook je implementatie van je fraction klopt niet (je deelt niet door 223)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
.oisyn schreef op zondag 09 mei 2010 @ 23:40:
Idd, ik zou er niet vanuit gaan dat je code ooit op een machine runt die geen 32 bits ieee float gebruikt.

Desalniettemin, het is vrij logisch dat je functie niet naar behoren werkt. Je handelt denormal numbers niet af (waar 0 ook onder valt), en je doet niets met de speciale waarden voor NaN e.d.. Ook je implementatie van je fraction klopt niet (je deelt niet door 223)
Owke, lekker simpel. :P

Dan ga ik 'ff verder kloten om te kijken of ik het wat efficienter kan krijgen.

edit:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    /**
     * Reads a single signed 32bit int.
     * @return int The int.
     */
    public function readInt() {
        $array = unpack('N',$this->readFully(4));
        $int = $array[1];
        if($int > 0x7FFFFFFF) {
            $int -= 0x100000000;
        }
        return $int;
    }

    /**
     * Reads a single 32bit float.
     * @return float The float.
     */
    public function readFloat() {
        $array = unpack('f',strrev($this->readFully(4)));
        $float = $array[1];
        return $float;
    }


Dit is 'm geworden :)
(Gekloot met big endianness)

[ Voor 34% gewijzigd door Gamebuster op 09-05-2010 23:58 ]

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Mjah, eerst wil je dat het overal werkt, en dan gebruik je strrev om de endianness hard om te draaien. :p Dat vind ik een beetje gek, want het gaat waarschijnlijk mis met php6 als UTF-8 standaard wordt, en werkt niet goed als je systeem juist big-endian is ipv little-endian. Alternatief (voorbeeld met 2.6100787562286E+20):
PHP:
1
echo current(unpack('f', pack('L', current(unpack('N', 'abcd')))));

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
pedorus schreef op maandag 10 mei 2010 @ 00:43:
Mjah, eerst wil je dat het overal werkt, en dan gebruik je strrev om de endianness hard om te draaien. :p Dat vind ik een beetje gek, want het gaat waarschijnlijk mis met php6 als UTF-8 standaard wordt, en werkt niet goed als je systeem juist big-endian is ipv little-endian. Alternatief (voorbeeld met 2.6100787562286E+20):
PHP:
1
echo current(unpack('f', pack('L', current(unpack('N', 'abcd')))));
De data die gelezen wordt is altijd Big Endian; als ik het omdraai werkt het, dus mijn php versie is little endian :) :P

Verschilt dat dan weer wel? irritant zeg.

Ik voeg wel een eenmalige platform validatie toe; endianness, 32bit of 64bit ints/floats en float-formaat bekijken door enkele resultaten van pack(*) te "bekijken" om zeker te zijn dat het goed gaat. Voor endianness kan ik dynamisch alle input omdraaien indien nodig. 64bit floats/ints heb ik nog nooit meegemaakt in PHP, evenals afwijkend float formaat.

Als achteraf blijkt dat het toch voorkomt kan ik altijd nog de methodes aanpassen.

Overigs verwacht ik sowieso niet dat mijn code direct zonder problemen in PHP6 draait. Ik maak mijn code voor PHP5 zonder te kijken naar PHP6. PHP6 is toekomstmuziek waar ik voorlopig niet mee te maken krijg, zeker omdat dit 1 van mijn laatste PHP projecten zal zijn.

[ Voor 32% gewijzigd door Gamebuster op 10-05-2010 03:38 ]

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • CyBeR
  • Registratie: September 2001
  • Niet online

CyBeR

💩

(jarig!)
Gamebuster schreef op maandag 10 mei 2010 @ 03:31:
[...]

De data die gelezen wordt is altijd Big Endian; als ik het omdraai werkt het, dus mijn php versie is little endian :) :P
Big endian is ook wel bekend als "network byte order". M.a.w: het is de bedoeling dat alles wat over een netwerk heen gaat big endian is. Intel PC's echter zijn little endian, dus alles wat die van een netwerk lezen moet eerst even omgedraaid worden als dat uitmaakt (zoals bij ints, floats, etc.)
Verschilt dat dan weer wel? irritant zeg.

Ik zal 'ff de "current" methode onderzoeken.
Ja, dat verschilt. Jouw Intel Macje is dan wel little endian, maar mijn PowerPC is gewoon big endian, evenals mijn SPARC doos, etc. In C heb je functies die die conversie voor je doen, htonl() en ntohl(). Die opereren beide op 32-bit waarden. Op een big-endian systeem doen ze dus niets (en worden ze ook weggeoptimaliseerd). Ik weet niet of php een equivalent heeft.

All my posts are provided as-is. They come with NO WARRANTY at all.


Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
CyBeR schreef op maandag 10 mei 2010 @ 03:38:
[...]


Big endian is ook wel bekend als "network byte order". M.a.w: het is de bedoeling dat alles wat over een netwerk heen gaat big endian is. Intel PC's echter zijn little endian, dus alles wat die van een netwerk lezen moet eerst even omgedraaid worden als dat uitmaakt (zoals bij ints, floats, etc.)


[...]


Ja, dat verschilt. Jouw Intel Macje is dan wel little endian, maar mijn PowerPC is gewoon big endian, evenals mijn SPARC doos, etc. In C heb je functies die die conversie voor je doen, htonl() en ntohl(). Die opereren beide op 32-bit waarden. Op een big-endian systeem doen ze dus niets (en worden ze ook weggeoptimaliseerd). Ik weet niet of php een equivalent heeft.
Ok; ik voer gewoon eenmalig een pack-call uit en kijk of het resultaat little- of big endian is. Probleem opgelost.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
define("IS_LITTLE_ENDIAN",strpos(pack('s',0x4020),'@'));

    /**
     * Reads a single signed 32bit int.
     * @return int The int.
     */
    public function readInt() {
        $str = $this->readFully(4);
        if(IS_LITTLE_ENDIAN) {
            $str = strrev($str);
        }
        $array = unpack('l',$str);
        return (int)$array[1];
    }

    /**
     * Reads a single 32bit float.
     * @return float The float.
     */
    public function readFloat() {
        $str = $this->readFully(4);
        if(IS_LITTLE_ENDIAN) {
            $str = strrev($str);
        }
        $array = unpack('f',$str);
        $float = $array[1];
        return (float)$float;
    }
?>


Dit moet werken lijkt mij; 1 als het little endian is, 0 als het big is.
Ik krijg iig een nette "1".

Hier werkt-ie iig.

Volledige source:
DataInputStream
InputStream
DataOutputStream
OutputStream

Inspired by Java :)

[ Voor 37% gewijzigd door Gamebuster op 10-05-2010 03:59 ]

Let op: Mijn post bevat meningen, aannames of onwaarheden

Pagina: 1