HP Forums

Full Version: internal number representation
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hello all,

I still have some question about the internal representation of real numbers in the Prime.

there is (in BCD):

1 nibble for the sign (4 bits)
12 nibbles for the mantissa
3 nibbles for the signed exponent

How is the sign represented within the 4 sign bits? What can these 4 bits represent (+ infinity, -infinity, ...) and how will it be done?

How is the signed exponent represented within the 3 BCD nibbles? Is it shifted by 499, so -499 will be 000, 0 will be 499 and +499 will be 998?

Is it possible to have a detailed description of the significance of the various bits?

Is there a way to see the internal binary representation of a real number (something like casting a real to a longint)?

Thank you very much for the help, I want to explain it to my students, but I'm not sure about how it will be done and don't want to expose it wrong.

You can directly look at the binary structure of any THPObj (as we call them) by storing the item using AFiles. AFilesB will allow direct return of the item as byte arrays. Another post from cyrille (coincidentally near the same time as your question) talks a bit about the header on the front of the objects.

Quote:Actually, what you are doing when you do:
Is that you are storing a string object in a file called DATO.txt...

You can see that by doing
R->B(AFilesB("DATO.txt", 0, 10)) which will return {#FFh,#FFh,#12h,#CDh,#1h,#0h,#0h,#0h,#31h,#0h}
Here, the FFFF means nothing, the 1 is a flag, the 2 means string, CD is a pad, 1, 0, 0, 0 is the size of the string in characters and 31, 0 is the character 1 in 16 bit wchar...

AFiles("num"):=0 followed by AFilesB("num",0,16) returns the full byte representation of the file. In your case, skip the first 8 and look at the last 8 bytes. That is the real number encoding in BCD.

I'm also including some comments from a header file which should be helpful.

* see the HP_Real structure to see how reals are stored (sign, exponent, mantissa)
 * real range is 1.0E-49999 to 9.99999999999999E49999 (high res, 15 digit mantissa)
 * or            1.0E-499 to 9.99999999999E499 (low res, 12 digit mantissa)
 * The library guaranties at most an error of 1 unit on the 12th significant digit.
 * This is why in most cases, all calculations are performed using the 15 digits and
 * rounded to 12 for user display/return.
 * note that fPack and fUnpack functions allow to pack and unpack low precision reals
 * into a 64 bit structure. cast unto a u64 for convenience. Normal reals are 128 bits.
 * That format is similar to the HP48 calculator format.

Note that we really only use the e499 version from the end user perspective. Other longer use is only internal and even then we don't use the extended exponent capacity at all. Also, when saving to disk the reals get packed to save space. Hence, the return from AFiles only takes 8 bytes for a single real. "Packing" basically means that there is only space for the 12digit BCD, 3 byte exponent, and 1 byte sign vs the 15 digit bcd, 4 byte exponent, and 1 byte sign of the "unpacked" real.

If you're interested, the code to do the packing looks like this:

PackedHP_Real fPack(HP_Real const *a) 
  HP_Real b= *a;
  fNorm_L(ERNone, &b); //normalize to 12 digit result
  u64 r= b.M&0x0ffffffffffff000LL;
  if (b.s==SignNeg) r+= (u64)9LL<<60; else
  if (b.s!=SignPos) r+= (b.s+4LL)<<60;
  r= r|(u64)(b.e&0xfff);
  return r;

Heres is more comments about the unpacked HP_Real.

/// A HP_Real is a group of 3 elements. A Sign (which is also can carry information on 
/// actual datatype Infinite/NaN (Not a Number) information/
/// The exponent.
/// The mantissa, which contains the 15 decimal digits. 
/// The digits are encoded in binary coded decimal (BCD). I.e., 0x123456789012345 corresponds to the
/// number 123456789012345 rather than 8.19855..E16
/// The most significant digit is always 0 (this is to leave space in algorithms for 
/// operations on the mantissa to carry over without losing any digits).
/// With a zero exponent, the second-most significant digit of the mantissa is in the one's position
/// of the represented real.

typedef i8 Sign;
i8 const SignPos  = 1;        // do not change these signs, some functions
i8 const SignNeg  = -SignPos;       // fPos and fNeg depend on positive numbers having a positive sign
i8 const SignNan  = 0;        // and nan being 0
i8 const SignPInf = 2;
i8 const SignNInf = -SignPInf;

enum { // definitions of flags for the THPObj, THPReal, THPComplex... etc

typedef struct { u16 EmbeddedRefCount; u8 type:4, Flags:4; ///< type, flags and embedding comes from THPObj. 
                 Sign s; i32 e; union { u32 m[2]; u64 M; }; } HP_Real;

// these max exponents could be changed with no problems...
  enum { 
    MaxExponent=49999, // could be changed up to 0x7fffffff-15;
    MaxExponent_L=499 // could be increased to 0x7ff = 2047

Really, the primary difference between the HP48 series encoding of reals and the newer encoding is that instead of encoding the exponent as BCD, that is encoded as a plain integer value while the mantissa is still kept in BCD. Perhaps someone can link to or find a good reference where the 48 real number encoding is explained???

From this explanation, you can probably pretty easily figure out how a complex is encoded (2 paired reals with a different header), a matrix (size info, followed by reals), a complex matrix, etc.
Thank you Tim !

Your informations were very useful.
The exponent is a signed binary integer, not BCD.

On the HP 48, the sign nibble is encoded with 0 meaning positive and 9 meaning negative.

The 3 nibble of the exponent are encoded in BCD, but if the value is negative, then it's encoded as 1000-ABS(exponent). Put another way, if value in the exponent nibbles is more than 499, then take the value, subtract it from 1000 , negate it and that's the value. For example, if the exponent nibbles contain 600, then actual exponent is -(1000-600) = -400.

See pages 104-105 here: http://www.hpcalc.org/details/1693
Thank you very much David!

A very useful document
Two key differences between HP_Real and IEEE 754/854 formats is that HP_Real doesn't have signed zeroes and doesn't have denormalized numbers (no gradual underflow).

The 15-digit HP_Real can be used for internal calculations: to provide guard digits and to simulate gradual underflow of 12-digit HP_Real values, among other uses. The greater exponent range allows things like more direct calculation of sqrt(x^2+y^2) — by using 15-digit HP_Reals for the operations even though x and y are 12-digit HP_Real values.

The two systems also have different rounding behaviours.

NaNs are also treated differently (especially when used via PPL).

The math library used in Prime was originally a pure rewrite of the HP 48 assembly, following the exact same format...
But as time went on and backward compatibility was deemed less and less important, the 2 formats have started to drift in order to simplify things on non BCD microprocessors.

Reference URL's