What are the words?
October 22, 2005 10:55 AM   Subscribe

Given an integer like this: 2916352 I'm told this is a 32 bit integer, where the "upper word" represents the whole number part of a value, and the "lower word" represents the decimal part. What is the mathematical formula to extract these words?
posted by 31d1 to Computers & Internet (25 answers total)
 
Divide the number by 2^16. The part to the left of the decimal point is the high word. To get the low word, multiply the fractional part (to the right of the decimal point) by 2^16.

Or, you could do it the way a programmer would: get it out of that useless base-10 notation and write it out as hex. Now the first four digits are the high word and the second four are the low word.
posted by Mars Saxman at 11:03 AM on October 22, 2005


Divide the number by 2^16. The part to the left of the decimal point is the high word. To get the low word, multiply the fractional part (to the right of the decimal point) by 2^16.

In other words, the lower word is the remainder after the division.
posted by cillit bang at 11:25 AM on October 22, 2005


You haven't provided enough information for a definitive answer, given that there might be issues with how signed numbers are represented, endian issues, or rounding concerns. However, in general, the representation you are talking about is called fixed-point format.

The simplest representation for your example (ignoring sign, etc.) could be specified as follows:

r: real number
n: number in fixed-point format

n = r * 65536
r = n / 65536

The individual words can be computed as follows:

upper_word = floor(r)
lower_word = (r-floor(r)) * 65536

upper_word = floor(n / 65536)
lower_word = n - (floor(n / 65536) * 65536)

n = upper_word * 63556 + lower_word
r = upper_word + lower_word / 65536

In many programming languages these operation can be more efficiently implemented using the bit-shift and bitwise-and operators.
posted by RichardP at 11:26 AM on October 22, 2005


Assuming this is part of a program, there are always easier ways to transform a number like this. You could use a bitwise shift it 16 places to the right for the first half and do a bitwise AND ('&' in C) of 2^16 (65536) of the original integer to get the decimal part. Some languages even have functions specifically for this, but bitwise operators are available in any language worth using. ;-)

If this is part of a program, you can do a google search for "bitwise operators" and whatever language you're working in and you should be able to figure out how to shift and AND easily. If not, tell us what language and I'm sure someone will be happy to post sample code.
posted by culbeda at 11:34 AM on October 22, 2005


Your terms are confusing. There is no "decimal" or fractional part to an integer. It's true that computers let you get at the upper word of a 32-bit integer but conceptually there's nothing special about it. The upper word is merely used for counting higher binary digits.

Do you mean a number with a decimal point like 3251.153125? That's usually in IEEE 754.
posted by fleacircus at 11:37 AM on October 22, 2005


Ah I suppose I didn't understand you were being given a number and asked to extract parts of it as if it were fixed point.
posted by fleacircus at 11:44 AM on October 22, 2005


fleacircus, IEEE 754 is a floating-point representation. I am pretty sure 31d1 is asking about a fixed-point representation.
posted by RichardP at 11:44 AM on October 22, 2005


If you just want the value of the number whose 32-bit fixed-point representation has the decimal value 2916352, then divide 2916352 by 2^16 to get 44.5. If you want the decimal values of the integer and fractional parts, you can get the high word (in C or Perl) with 2916352 >> 16 which is 44 and the low word with 2916352 & 0xFFFF which is 32768 (since the binary fractional part is 1000 0000 0000 0000 where the 1 represents 1/2).
posted by nicwolff at 12:54 PM on October 22, 2005


It may help to think of a fixed-point 32-bit number as being the number of 65536ths rather than as a number with an integer and fractional part.
posted by kindall at 3:23 PM on October 22, 2005


Here's the solution in C . It assumes a 32-bit x86 architecture. If you have a different machine word, you must adjust the constants appropriately or write meta-code using defines.

/*note the output paramater pointers*/
void extract(uint input, uint *whole, uint *fraction){
    *whole = (input >> 16);
    *fraction = input & 0x00000000FFFFFFFF;
    return;
}


A possible problem here is that the professor has not mentioned whether or not this is a signed or unsigned number. If it is signed, then we have a problem with the above. Stop thinking of it as a number, and start thinking of it as a bitstring, and you'll find the solution to that.
posted by Netzapper at 3:46 PM on October 22, 2005


Netzapper, you meant to use 0x0000FFFF as your bit-mask constant to extract the lower 16 bits.
posted by RichardP at 4:21 PM on October 22, 2005


2916352 in hex is 0x002c8000. The upper word is 0x002c. The lower word is 0x8000. There's no need to write programs to do this.
posted by Rhomboid at 4:27 PM on October 22, 2005


And yes, if this is a question from a class or textbook then it's terribly specified, because it contains a number of ambiguities.
posted by Rhomboid at 4:28 PM on October 22, 2005


Response by poster: The question is not for a class - its how the current-value of hardware sensors is displayed in the registry of (some? current?) macs. If you're on a mac, check out the output of "ioreg -n IOHWSensor | more" and look for lines with the phrase "current-value" to see what I mean.

I was led to believe that the numbers displayed simply needed to be divided by 2^16 to get the actual value, then someone tells me that the number (the number i used above is an actual example), was a 32 bit integer with the upper word representing the whole number part of a (Celsius) temperature reading , and the lower words gives the fractional part.

So they said my answer (n/2^16) happened to be close, but was not the actual number.

I'm still not getting this ... Here (in the comments) is the knowledge imparted to me, if it helps. I'm looking to find the formulaic way to translate the above number into a Celsius reading, given these conditions.

Thanks for the anwers so far, they've given this n00b lots to think about.
posted by 31d1 at 5:23 PM on October 22, 2005


Right, so you can either call it the use of a 32-bit integer to represent a decimal fraction, or call it a fixed-point number.

Ignore the value 2916352 in decimal. It's much more useful in hex; ie 0x002c8000. So the upper word 0x002c is the whole number bit and the lower word 0x8000 is the fractional bit.

0x002C -> 44d
0x8000 -> 32768d

So your temperature is 44.32768 degrees c;
(divided by 2^16 gives 44.5, slightly different indeed)
posted by cogat at 5:48 PM on October 22, 2005


I don't think that's correct, cogat; I've never heard of a number representation which involved converting the lower 16 bits to decimal and then stuffing them after a decimal point like that.

Some googling shows that IOHWSensor sometimes (usually? but not always) returns a value tririeved from a Maxim 6690 via the motherboard's I2C bus. Presumably (I'm guessing here) the two values you can extract from the 32-bit int are the values from Table 2 and Table 3 of the sensor's datasheet [pdf]. In which case what you have is, in the upper half, the temperature rounded to an integer value; and in the other half, an indication of what the fractional part of the unrounded value was. It's not a scheme that you would normally use to represent a number; it's an artifact of the way that particular chip communicates with the CPU.

For what it's worth, a good way to test whether you're interpreting a value like this correctly is to sample it frequently and graph it and make sure it varies smoothly in a way you'd expect as the machine changes temperature.
posted by hattifattener at 6:41 PM on October 22, 2005


Yeah, again, the total number is the number of 65536ths.

0x8000 would be 1/2. The hexadecimal point is to the left of that. 0x8 is half of the radix (16) so 0x0.8 is the same as 0.5.
posted by kindall at 7:05 PM on October 22, 2005


cogat is wrong, kindall is correct - fixed point is used in embedded systems all the time.
posted by rfs at 8:35 PM on October 22, 2005


Nice Googling, hatti! So we're finding the temperature inside my PowerBook? Neat!

One issue we can probably ignore is that the high word represents the integer part of the Celsius temperature but does so in 2's-complement form, which means that if the high bit is one the rest of the bits are to be inverted and 1 is added to get the additive inverse of the actual negative value. But there's really little chance that your Mac's CPU is colder than ice, so let's not worry about this.

Next issue: if there is more than one temperature sample being digitized each second, then the low word is not populated and should be ignored. Given the assignment we can presume that this is not the case.

Issue #3: only the high three bits of the low word are significant. So we should mask with 0xD000 rather than 0xFFFF to get the fractional part's value.

Last issue: the temperature data are rounded up before the integer value is taken, and the implication seems to be that that's done by just adding 1/2 to the value - hatti, I'm not sure where you're getting that the fractional part is that of the original value, seems to me that this bit

When the conversion rate is less than 1Hz, the
extended data can be read from the read external
extended temperature register (10h) and the read inter-
nal extended temperature register (11h), and the first 3
bits of the register represent 1/2, 1/4, and 1/8 of a
degree. Measurements are offset by +1/2°C to mini-
mize quantization errors; for example, +99.6°C is
reported as +100°C.


indicates that the whole "extended" value represented is 1/2 less than that measured. So, once you mask off the low five bits I still think you should be able to divide by 2^16 and subtract .5 to get the actual temperature Celsius that was measured. And in C or Perl that would be:

( $n & 0xFFFFD000 ) / ( 2 ** 16 ) - .5
 
posted by nicwolff at 11:09 PM on October 22, 2005


float fix2float(int in)
{
    int upper=in >> 16;
    int lower=in & 0xFFFF;
    return ((float)upper) + ((float) lower)/0x10000;
}
PS everyone stop beating up on cogat and read his last line.

If hattifattener is right and the data is REALLY from a MAX6690, the upper word is the rounded result so you'll have to decrement it if the lower word is >= 0x8000 because that means the upper was rounded up. Also, the data sheet says there are 3 bits of precision in the bottom word and the remaining bits could be unspecified so you should mask them off. So (and this is code is VERY specific to the MAX6690 now):
float fix2float(int in)
{
    int upper=in >> 16;
    int lower=in & 0xE000; // not 0xD000, nicwolff
    if(lower >= 0x8000)
        --upper;
    return ((float)upper) + ((float) lower)/0x10000;
}
But then, it's accurate only to 2°C, so you could probably just use the top word only. The device is capable of reading temperatures below 0°C in which case the top word is a 2's complement signed number. Right-shifting this as a signed integer will sign-fill and preserve the value that you want. Masking the bottom half will make it non-negative, also the desired outcome.
posted by polyglot at 11:17 PM on October 22, 2005


Whoops, that should be 0xE000 and 0xFFFFE000, not D. Silly me.

Although note, the chip is sending this data in 8-bit registers, which we're all assuming Apple is saving in the low bits of the high word and the high bits of the low word. Which looks right given the sample data. And they may well be masking off the insignificant low 5 bits of the "extended" fractional-part register when they copy the data, so the old

$n / ( 2 ** 16 ) - 5

may still work reliably.
posted by nicwolff at 11:31 PM on October 22, 2005


Response by poster: wow, this is getting so far beyond me.

I'm doing all this in awk, which does not have bitwise operators or hex or anything. I have to do everything in decimal. Here's what I have so far (snippet from within awk):

d=(($2/(2^16))%1); e=($2/(2^16)); f=(e-d); g=($2%(2^16));

where d gets the fractional part of the high word, e gets the word, and f is the whole number. So f seems to be the correct whole number reading, and g seems to be the lower word in decimal, but I can't figure out what to do with that given what's been said above.

Any ideas that I could use in this format? Am I way off? Is this just getting dumb?
posted by 31d1 at 9:43 AM on October 23, 2005


Response by poster: It seems like gawk would be much better for this, it recognizes hex and does bitwise operations, but gawk is not in the default mac install, and if possible (I know it's silly) i'd like to keep this to that. Also, if I can get this correct in awk, it'll be a (cool) one liner.
posted by 31d1 at 9:47 AM on October 23, 2005


Well, I'd do it in Perl. But the point is that you don't need to separate the integer and fractional parts to get the final value, just zero the low 13 bits, divide by 2^16, and subtract .5. Or, equivalently, cut off the low 13 bits, divide by 2^11, and subtract .5. "Cutting off the low 13 bits" is the same as rounding down to the nearest 1/8, so you could just do

int( $n / 2^13 ) / 2^3
 
posted by nicwolff at 3:52 PM on October 23, 2005


Whoops, forgot the .5:

int( $n / 2^13 ) / 2^3 - .5

And, grrr, awk has no "int" (or "floor" or "trunc") operator, so:

( $n / 2^13 - $n / 2^13 % 1 ) / 2^3 - .5
 
posted by nicwolff at 4:07 PM on October 23, 2005


« Older sprint cell phone suggestions?   |   Low-density cheesecake Newer »
This thread is closed to new comments.