Typecast a hex string to single

I have strings of hex values that I need to typecast to single precision values. I don't actually care about the actual single precision value, I am just trying to package the binary into a single datatype for transmission.
I want something like
MyHexString = '7f8e2d38';
out = typecast(MyHexString,'single'); %This doesn't work because typecast needs number
But the typecast.m function requires a numeric value. All I want to do is to produce the single precision number equivalent of the binary data held in my hex string.
FYI: I was using
out = typecast(uint32(hex2dec(MyHexString)),'single');
This worked most of the time but is occasionally produced incorrect results because the hex2dec function produces a double and there is some loss on the subsequent uint32 cast. At least that's what I think is wrong.
How to I package hex into a single datatype?

2 Comments

How about using hex2num.
James Tursa
James Tursa on 18 Aug 2022
Edited: James Tursa on 18 Aug 2022
"... occasionally produced incorrect results ..."
Can you post some specific examples where this happens? How can there be a loss converting to uint32?

Sign in to comment.

 Accepted Answer

Jan
Jan on 18 Aug 2022
Edited: Jan on 18 Aug 2022
str = '7f8e2d38';
vec = uint8(sscanf(str, '%2x'));
num = typecast(vec, 'single')
num = single 4.1379e-05
num = typecast(flip(vec), 'single') % Maybe MSB?
num = single NaN
NaN due to initial 7f

13 Comments

@Jan Thanks for helping with this! I don't quite understand why converting to a set of uint8s and then doing type cast works, but converting to a uint32 doesn't. Maybe something to do with the 'single' typecast operation? If you want to respond as to why this works (for posterity's sake) I'd appreciate it. Regardless, here is the script that does the conversion per the sscanf method you proposed. Thanks so much.
doubleNumIn = 1660811774.24557;
fullHexStringIn = num2hex(doubleNumIn);
topHexStringIn = fullHexStringIn(1:8);
botHexStringIn = fullHexStringIn(9:16);
topVecIn = uint8(sscanf(topHexStringIn,'%2x'));
botVecIn = uint8(sscanf(botHexStringIn,'%2x'));
topSingleIn = typecast(topVecIn,'single');
botSingleIn = typecast(botVecIn,'single');
singleComplex = topSingleIn+1i*botSingleIn;
%singleComplex IS THEN TRANSMITTED VIA COMPLEX SINGLE UDP MESSAGE.
topSingleOut = real(singleComplex);
botSingleOut = imag(singleComplex);
topVecOut = typecast(topSingleOut,'uint8');
botVecOut = typecast(botSingleOut,'uint8');
topHexVecOut = dec2hex(topVecOut)';
botHexVecOut = dec2hex(botVecOut)';
topHexStringOut = topHexVecOut(:)';
botHexStringOut = botHexVecOut(:)';
fullHexStringOut = [topHexStringOut, botHexStringOut];
doubleNumOut = hex2num(fullHexStringOut);
%Check input and output is correct
doubleNumIn == doubleNumOut
ans = logical
1
s = '7f8e2d38';
d = sscanf(s, '%2x').'
d = 1×4
127 142 45 56
u = uint32(hex2dec(s))
u = uint32 2140024120
typecast(u, 'uint8')
ans = 1×4
56 45 142 127
You see, this is the same byte pattern with MSB order. Casting this to a single replies NaN due to the leading 7f. But actually the conversion works correctly with 4 bytes or 1 UINT32.
sccanf is faster than hex2dec and it avoids the indirection over a double.
@Jan Okay. Thanks. After some more testing, I am still seeing an error with the conversion that I have traced back to this section of the conversion:
topSingleIn = typecast(topVecIn,'single');
botSingleIn = typecast(botVecIn,'single');
singleComplex = topSingleIn+1i*botSingleIn;
When one of these values (topSingleIn or botSingleIn) becomes NaN during the typecast operation it will force the other in the singleComplex representation to be listed as NaN as well. See below.
>> topSingleIn
topSingleIn =
single
-7.3895907e-32
>> botSingleIn
botSingleIn =
single
NaN
>> topSingleIn+1i*botSingleIn
ans =
single
NaN + NaNi
>> typecast(topSingleIn,'uint8')
ans =
1×4 uint8 row vector
65 216 191 139
>> typecast(real(topSingleIn+1i*botSingleIn),'uint8')
ans =
1×4 uint8 row vector
214 107 207 255
The summation of the single values to a complex number forces the real or imaginary part to be NaN if the other is NaN. This is root cause of the problem. Not sure how to get around this. Any ideas?
For some context: I need these single values to be combined as a single precision complext number so that I can concatinate it to a vector I am sending via UDP. This is how I am timestamping each vector that is transmitted over UDP. My other data is complex single precision data, which is why I going through all this work to get my timestamps into this odd format.
Thanks.
UPDATE:
I have found that complex.m will produce the correct complex number when the real or imaginary part is NaN.
>> blah = 5 + 1i* NaN
blah =
NaN + NaNi
>> blah2 = complex(5, NaN)
blah2 =
5.000000000000000 + NaNi
The line in my script from my previous post should be
%singleComplex = topSingleIn+1i*botSingleIn; PRODUCES ISSUES
singleComplex = complex(topSingleIn, botSingleIn);
I believe this may have been the issue with my original script, but will now use the sscanf method because it is faster. Thanks so much for your help.
You have 8 hex digits to transmit, which is 32 bits of data. Single precision is 32 bits of data. However in 32 bit representation
S111 1111 1xxx xxxx xxxx xxxx xxxx xxxx
is reserved. S = 0 and all x = 0 is +inf, S = 1 and all x = 0 is -inf, S = 0 and some x non-zero is quiet nan, S = 1 and some x non-zero is signaling nan. In nearly all cases when calculations are done on nan, matlab converts all varieties of nan to 0111 1111 1000 0000 0000 0000 0000 0001 (but would probably copy values intact when copying from single precision to single precision.)
It so happens that hex 7F8 followed by nonzero is a nan pattern.
So one out of every 256 possible bit patterns is reserved for nan or inf.
You can remap values through techniques such as swapping bytes or reversing bits... but unless at least 1/256 of all patterns cannot possibly appear in your situation, you have a problem.
Have you considered splitting the bytes over the real and imaginary components? Separately each group has value no more than 65535
@Walter Roberson This is really helpful. Let me step back a bit. I am trying to take a posixtime timestamp and transmit it via a single precision complex UDP message. I trying to transmit the double precision output of
posixtime(datetime('now'))
My original thought was to take that 64 bits of info, break it into two 32 bit messages, stich them together as a complex number with each real and imag part as a single precision, and transmit that. I'd then undo all that type conversion on the receiver of the UDP message.
Do you know if the typecast operation has access to those reserved bits? The help file seems to suggest that the underlying binary isn't affected like it is with a cast command. If this is the case, then wouldn't I have access to all 32 bits?
typecast has access to all of the bits as stored in memory.
What precision do you need? round() of posixtime would require only 31 bits to represent to seconds. If you only need millisecond precision then you do not need the full 64 bits and so could split the value into parts that are individually certain to be less than nan in bit representation
I do need sub-second precision. Ideally I would need microsecond precision, but could work with millisecond precision if I don't have the bits necessary. For example, I'd like to transmit the integer
format long
round(posixtime(datetime('now'))*10^6)
ans =
1.660924474546694e+15
@Walter Roberson After thinking a bit, I came to realize that millisecond precision is okay for my purposes. So my solution now is to split the digits of the millisecond version of the posix time into parts, both of which are limited by flintmax('single'). A basic example is shown below. I think because I limit the values to no greated than flintmax('single'), I should never have NaN values transmitted. The maximum transmitted value will be 167,772,169,999,999 (~1.7e14) which is far more than current posixtime in milliseconds (~1.7e12). The problem I was having previously is that I was trying to transmit microsecond time which is ~1.7e15, which is greater than 1.7e14.
currTime = posixtime(datetime('now'));
millisecondsIn = round(currTime*10^3)
millisecondsIn = 1.6609e+12
digitsplit = 7;
topDigits = floor(millisecondsIn*10^-digitsplit);
botDigits = millisecondsIn - topDigits*10^digitsplit;
topDigitsSingle = single(topDigits);
botDigitsSingle = single(botDigits);
singleComplex = complex(topDigitsSingle, botDigitsSingle);
%singleComplex IS THEN TRANSMITTED VIA COMPLEX SINGLE UDP MESSAGE.
%Max transmitted value is based on flintmax('single') = 16777216
% Real can transmit up to 16777216
% Imag can transmit up to 9999999
%Range of transmitted values is -167772169999999 <= ms <= 167772169999999
%This is sufficient to transmit millisecond precision posix timestamps
%
topDoubleOut = double(real(singleComplex));
botDoubleOut = double(imag(singleComplex));
millisecondsOut = topDoubleOut*10^digitsplit + botDoubleOut
millisecondsOut = 1.6609e+12
millisecondsOut == millisecondsIn
ans = logical
1
You are transmitting in binary, right? Rather than formatting the single precision data as text?
Because if so then it does not matter that the printable version of a series of bytes might be NaN, as long as the byte sequence is exactly recoverable when transmitted in binary.
format long g
s = '7f8e2d38';
d = uint8(sscanf(s, '%2x').')
d = 1×4
127 142 45 56
df = typecast(fliplr(d), 'single')
df = single
NaN
typecast(df, 'uint8')
ans = 1×4
56 45 142 127
filename = tempname();
fid = fopen(filename, 'w')
fid =
3
fwrite(fid, df, 'single')
ans =
1
fclose(fid)
ans =
0
ls('-l', filename)
-rw-r--r-- 1 mluser worker 4 Aug 19 18:21 /tmp/tp3dab8982_f12e_4817_9513_f7aa0e567ad9
fid = fopen(filename, 'r')
fid =
3
dfr = fread(fid, '*single')
dfr = single
NaN
fclose(fid)
ans =
0
typecast(dfr, 'uint8')
ans = 1×4
56 45 142 127
Yes. That's what I was originally doing i.e. transmitting the raw binary. I believe the main issues I was having is that I was converting the double to a hex and then splitting up that hex. When one part of the resulting split hex string would result in a NaN value, using () +1i() in matlab would then force the other part of the number to be NaN as well. I got around that by using complext(real, imag). The code I posted above that splits the digits rather than the hex seems to do what I need related to transmitting time values. I believe that the transmission of the raw binary would work as well. Thanks for all your help!

Sign in to comment.

More Answers (0)

Categories

Products

Release

R2022a

Asked:

on 17 Aug 2022

Commented:

on 19 Aug 2022

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!