//////////////////////////////////////////////////////////////////////////////// // // File: Pack64Float.as // Path: com.enso.Pack64Float // // Created by: Grant Cox // Copyright: © 2005 Grant Cox, enso . // // Notes referenced: http://babbage.cs.qc.edu/courses/cs341/IEEE-754references.html // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // Class: Pack64Float // // Utility functions for packing byte data into Actionscript's native 64bit // float, and for unpacking this byte data again. This data type follows the // IEEE-754 format. // //////////////////////////////////////////////////////////////////////////////// class Pack64Float { private static var power_array:Array = [1,0.5,0.25,0.125,0.0625,0.03125,0.015625,0.0078125,0.00390625,0.001953125,0.0009765625,0.00048828125,0.000244140625,0.0001220703125,0.00006103515625,0.000030517578125,0.0000152587890625,7.62939453125e-6,3.814697265625e-6,1.9073486328125e-6,9.5367431640625e-7,4.76837158203125e-7,2.38418579101563e-7,1.19209289550781e-7,5.96046447753906e-8,2.98023223876953e-8,1.49011611938477e-8,7.45058059692383e-9,3.72529029846191e-9,1.86264514923096e-9,9.31322574615479e-10,4.65661287307739e-10,2.3283064365387e-10,1.16415321826935e-10,5.82076609134674e-11,2.91038304567337e-11,1.45519152283669e-11,7.27595761418343e-12,3.63797880709171e-12,1.81898940354586e-12,9.09494701772928e-13,4.54747350886464e-13,2.27373675443232e-13,1.13686837721616e-13,5.6843418860808e-14,2.8421709430404e-14,1.4210854715202e-14,7.105427357601e-15,3.5527136788005e-15,1.77635683940025e-15,8.88178419700125e-16,4.44089209850063e-16,2.22044604925031e-16,1.11022302462516e-16,5.55111512312578e-17]; //////////////////////////////////////////////////////////////////////////// // // Function: pack // // Creates and returns a 64bit floating point Number containing the bytes // passed in the byte_array parameter. Each of these array elements must // be a single 8bit char or Number (except for the 8th element, which must // be 7bit). Each element will be limited to 8bit if found to be larger. // // Parameters: // byte_array - Array of char / Numbers, must be 8bit only, max of 8 elements. // // Returns: // 64bit floating point Number. // // Note: // Currently the 8th array element (if provided) must be 7bit or less. // Any higher numbers will cause the resulting Number to be NaN / Infinity, // as this is how the IEEE-754 defines. // //////////////////////////////////////////////////////////////////////////// public static function pack( byte_array:Array ):Number { // NaN is with exponent all 1's, so pack mantissa first var mantissa_bytes:Number = 7; if ( mantissa_bytes > byte_array.length ) mantissa_bytes = byte_array.length; var mantissa_digits:Number = 0; var mantissa:Number = 0; // there will probably be some remainder from the mantissa to // put in the exponent (depending on number of bits to pack) var exponent_remainder:Number = 0; for ( var i:Number =0; i < mantissa_bytes; i++){ // make sure we actually have a byte var this_byte = byte_array[i]; //if ( this_byte instanceof String ) this_byte = this_byte.charCodeAt(0); if ( this_byte > 255 ) this_byte %= 256; if ( (mantissa_digits + 8) <= 52 ){ // pack all of this byte mantissa += this_byte * Math.pow(2, -(52 - mantissa_digits) ); //mantissa += this_byte * power_array[52 - mantissa_digits]; } else { // pack part of this byte // add the minor 4 bits to the mantissa mantissa += (this_byte & 15) * Math.pow(2, -(52 - mantissa_digits) ); //mantissa += (this_byte & 15) * power_array[52 - mantissa_digits]; // put the major 4 bits to the remainder for the exponent exponent_remainder = this_byte >> 4; break; } // now we've stored these bits mantissa_digits += 8; } // now calculate the exponent (shifted 1023) var exponent:Number = -1023; if ( byte_array[7] != undefined ){ exponent_remainder |= ( byte_array[7] << 4 ); } if ( exponent_remainder != 0 ){ // we can only fit 11 binary digits on the exponent, not the full 12 exponent += (exponent_remainder & 4095);//(Math.pow(2, 12)-1) ); // keep the last bit for the sign exponent_remainder >>= 11; } var sign:Number = 0; // if there is a single digit left over, assign it to the sign if ( exponent_remainder != 0 ){ sign = exponent_remainder; } // if we have no exponent (all 0s), then we have no implied 1 if ( exponent == -1023 ){ mantissa *= 2; } else { // plus the hidden (implied) 1 mantissa += 1; } // now we have our 64 bits, construct the decimal number var decimal:Number = Math.pow( 2, exponent) * mantissa; if ( sign != 0 ) decimal = -decimal; //trace("mantissa: " + mantissa + " exponent: " + exponent + " sign: " + sign ); //trace("decimal: " + decimal ); return decimal; } //////////////////////////////////////////////////////////////////////////// // // Function: unpack // // Unpacks the passed floating point Number into an array of 8bit Numbers, // and returns this array. This array will always have 8 elements, unless // the provided Number was NaN or Infinity. // // Parameters: // input_float - Floating point Number to unpack. // // Returns: // Array of 8 8bit Numbers. // //////////////////////////////////////////////////////////////////////////// public static function unpack( input_float:Number ):Array { var return_bytes:Array = new Array(); if ( !isNaN(input_float) && input_float != Number.POSITIVE_INFINITY && input_float != Number.NEGATIVE_INFINITY ){ var decimal:Number = input_float; // first, the sign var sign:Number = 0; if ( input_float < 0 ) { sign = 1; input_float = -input_float; } // calculate the exponent and mantissa // probably slow, but can visualise it this way... // just keep on doubling / halving until we get a scientific notation number // count these steps, that's the exponent var exponent = 0; if ( input_float > 1 ){ exponent = -1; while( input_float > 1 ){ input_float *= 0.5; exponent ++; } } else { while( input_float < 1 ){ input_float *= 2; exponent ++; } // if we were less than 0 then it must be a negative exponent exponent = -exponent } // somehow the exponent can be less than -1023? => [45,49,243,235,119,206,1,0] if ( exponent < -1023) exponent = -1023 // now just reverse how we generated the decimal var mantissa:Number = decimal / Math.pow( 2, exponent); // if we have no exponent (all 0s), then we have no implied 1 if ( exponent == -1023 ){ mantissa *= 0.5; } else { // subtract the hidden (implied) 1 mantissa -= 1; } //trace("mantissa: " + mantissa + " exponent: " + exponent + " sign: " + sign ); //trace("decimal: " + decimal ); // the exponent is offset by 1023 exponent += 1023; // now we have our floating point in the different sections, regenerate the binary bytes // we can't use bit shifting on the mantissa as it is too big var exponent_remainder:Number = 0; var mantissa_digits:Number = 0; for ( var i:Number =0; i < 7; i++){ var this_byte:Number = 0; var this_fraction:Number = Math.pow(2, (52 - mantissa_digits) ); this_byte = Math.floor( (mantissa * this_fraction) % 256 ) ; if ( (mantissa_digits +8) <= 52 ){ // this is a complete packed byte return_bytes.push( this_byte ); } else { // this is partially shared with the exponent exponent_remainder = this_byte; } // now we've stored these bits mantissa_digits += 8; } // second is the remainder of the mantissa, with part of the exponent return_bytes.push( ((exponent & 15) << 4) | exponent_remainder ); // first is the sign, and the first 7 bits of the exponent return_bytes.push( sign << 7 | exponent >> 4); } return return_bytes; } //////////////////////////////////////////////////////////////////////////// // // Function: mb_pack // // Creates and returns a 64bit floating point Number containing the Numbers // passed in the data_array parameter. Unlike <> these Numbers are // not restriced to 8bit, they can also be 16bit. This works by reserving // the first byte of the 64bit FP for identifying which Numbers are multibyte, // which means only 7bytes of data can be packed per Number. // If the passed data_array contains more than 7bytes the remainder is silently // discarded. Passing less than 7bytes is fine. // // Parameters: // data_array - Array of Numbers, must be 8bit/16bit only, max of 7 bytes total. // // Returns: // 64bit floating point Number. // // Note: // Currently the Numbers passed can only be 8bit or 16bit. // //////////////////////////////////////////////////////////////////////////// public static function mb_pack( data_array:Array ):Number { // first byte will be identifying which items are multibyte // this just uses the minor 7 bits, with 0 indicating a byte, // and 1 being multibyte start. In case of having a spare byte // at the end (ie 3 multibytes passed) it will be marked 1. var multibyte_flags_byte:Number = 0; var flagged_byte_counter:Number = 0; var byte_array:Array = new Array(); for ( var i:Number = 0; i< data_array.length; i++ ) { var this_int:Number = data_array[i]; if ( this_int > 255 ){ // this number is multibyte multibyte_flags_byte |= Math.pow(2, (6-flagged_byte_counter) ); // push each byte var first_byte:Number = (this_int >> 8) & 0xFF; var second_byte:Number = this_int & 0xFF; byte_array.push( first_byte ); byte_array.push( second_byte ); flagged_byte_counter += 2; } else { byte_array.push( this_int ); // and record a 0 in our multibyte flag flagged_byte_counter ++; } } for ( var i:Number = flagged_byte_counter; i< 7; i++ ){ multibyte_flags_byte |= Math.pow(2, (6-i) ); byte_array.push(0); } //trace( " mb_pack: " + multibyte_flags_byte ); // now we have the flag of multibytes, and the array of bytes byte_array.push( multibyte_flags_byte ); var decimal:Number = pack( byte_array ); return decimal; } //////////////////////////////////////////////////////////////////////////// // // Function: mb_unpack // // Unpacks the passed floating point Number into an array of Numbers, // and returns this array. This array will be any number of 8bit / 16 bit // Numbers - it will not be packed with any additional values. // // Parameters: // input_float - Floating point Number to unpack. // // Returns: // Array of 8bit/16bit Numbers. // //////////////////////////////////////////////////////////////////////////// public static function mb_unpack( input_float:Number ):Array { var return_mbytes:Array = new Array(); var byte_array:Array = unpack(input_float); if ( byte_array != undefined && byte_array instanceof Array && byte_array.length > 0 ){ // the last byte tells us which of the rest are multibyte var multibyte_flags_byte:Number = byte_array[7]; //trace( " mb_unpack: " + multibyte_flags_byte ); var flagged_byte_counter:Number = 0; while( flagged_byte_counter < byte_array.length-1 ) { var this_int:Number = byte_array[flagged_byte_counter]; if ( multibyte_flags_byte & Math.pow(2, (6-flagged_byte_counter) ) ) { // this part of a multibyte // first check if the next bit indicates multi too... // if so, then these are actually unused bytes var next_bit = multibyte_flags_byte & Math.pow(2, (6-(flagged_byte_counter+1)) ) if ( flagged_byte_counter < byte_array.length-2 && next_bit == 0 ){ var mb_number:Number = (this_int << 8) | (byte_array[ flagged_byte_counter+1 ] ); return_mbytes.push( mb_number ); } flagged_byte_counter += 2; } else { // this is a single byte return_mbytes.push( this_int ); // and record a 0 in our multibyte flag flagged_byte_counter ++; } } } return return_mbytes; } }