8
votes

Array d'octet à UINT64 comme chaîne

Pensons à la situation suivante.

La routine Go crée un tableau d'octets où Packs A UINT64 Number 5577006791947779410 Dans 8 octets Big Endian [77, 101, 130, 33, 7, 252 , 253, 82] .

dans le code JavaScript, je reçois ces octets comme uint8array . Nous savons que JavaScript ne prend pas en charge actuellement uint64 comme type numérique sûr et ne peut pas effectuer les opérations binaire sur des entiers supérieurs à 32 bits, donc des choses comme buf [0] << / code> ne travaillera jamais.

Alors, quel est le processus de décodage de ces octets directement à la chaîne numérique "5577006791947779410" ?

PS Je sais qu'il y a beaucoup de Bibliothèques pour travailler avec de gros entiers dans JavaScript, mais ils sont généralement énormes et offrent beaucoup d'opérations mathématiques que je n'ai pas besoin ici. Je cherche une solution simple moderne moderne pour juste décodage Be-Emballé et int64 octets à la chaîne numérique. Avez-vous quelque chose en tête?


0 commentaires

4 Réponses :


8
votes

EDIT: strong> pour la conversion (U) INT64, je recommanderais désormais la solution de @ ls_dev. J'utiliserais ma solution que lorsque vous avez une quantité inconnue ou plus importante d'octets.

J'ai commencé avec https://stackoverflow.com/ A / 21668344/3872370 et modifié et modifié: p>

p>

function Int64ToString(bytes, isSigned) {
  const isNegative = isSigned && bytes.length > 0 && bytes[0] >= 0x80;
  const digits = [];
  bytes.forEach((byte, j) => {
    if(isNegative)
      byte = 0x100 - (j == bytes.length - 1 ? 0 : 1) - byte;
    for(let i = 0; byte > 0 || i < digits.length; i++) {
      byte += (digits[i] || 0) * 0x100;
      digits[i] = byte % 10;
      byte = (byte - digits[i]) / 10;
    }
  });
  return (isNegative ? '-' : '') + digits.reverse().join('');
}

const tests = [
  {
    inp: [77, 101, 130, 33, 7, 252, 253, 82],
    signed: false,
    expectation: '5577006791947779410'
  },
  {
    inp: [255, 255, 255, 255, 255, 255, 255, 255],
    signed: true,
    expectation: '-1'
  },
];

tests.forEach(test => {
  const result = Int64ToString(test.inp, test.signed);
  console.log(`${result} ${result !== test.expectation ? '!' : ''}=== ${test.expectation}`);
});


4 commentaires

Merci pour la réponse! Cela semble bien fonctionner pour uint64 . Quelles modifications dois-je avoir besoin de faire pour que cela fonctionne avec int64 aussi? J'ai créé une aire de jeux ici: jsfiddle.net/wcqlj1qg .


J'ai mis à jour ma réponse et l'a téléchargé sur CodePen.io/stephtr/pen/brbvxr Les modifications Les obligations nécessaires sont (évidemment) en ajoutant le signe moins, nier les bits et le soustraction 1.


Pourrait le rendre légèrement plus compact en modifiant octet> 0 à octet car octet est toujours positif. Aussi j == octets.length - 1? 0: 1 peut être écrit simplement j! = Bytes.length - 1 puisque le booléen sera contraint à un numéro :)


J'ai réfléchi à cela lors de la rédaction du code, mais à mon avis qui diminue la lisibilité.



1
votes

Voici ma solution. La stratégie générale est la suivante:

  • Si le nombre est négatif, nier-le en utilisant le complément de 2 et ajoutez un signe négatif à la fin
  • représente des nombres de taille arbitraires sous forme de chiffres de chiffres de 0 à 9
  • pour chaque octet dans le uint8array (du plus au moins significatif), multipliez le total de l'exécution de 256 et ajoutez-lui la valeur du nouvel octet
  • Pour multiplier un nombre par 256, double-le 8 fois (depuis 2 ** 8 == 256 )
  • Pour ajouter deux chiffres, utilisez l'algorithme de l'école primaire:
    • Commencez avec le chiffre moins significatif
    • Ajouter des chiffres correspondants des deux numéros
    • Le chiffre résultant est la somme MOD 10; Le transport est 1 si la somme est de 10 ou plus, sinon 0
    • Continuez à ajouter des chiffres correspondants avec le transport jusqu'à ce que nous ajoutons les chiffres les plus significatifs et le transport est 0

      Quelques notes sur le sténographie:

      • n1 [i] || 0 obtient le i th chiffre de N1 . Si cela est passé la fin de i , nous le traitons comme un 0 (numéros imaginaires représentés avec des 0s infinis devant eux). Même avec n2 .
      • ajouté> 9 produit un booléen, qui est automatiquement converti en un numéro (1 si ajouté> = 10 , 0 sinon)
      • i vérifie s'il y a plus de chiffres dans l'un des ajouts ou le port est toujours non nul
      • string (b) .split (''). mappe (numéro) .Reverse () convertit, par exemple. 100 à '100' , puis ['1', '0', '0', '0'] , puis [1, 0 , 0] , puis [0, 0, 1] de sorte qu'il est représenté dans le base-10
      • résultat.Reverse (). Joindre ('') Convertit, par ex. [0, 0, 1] à [1, 0, 0] , puis '100'

        code: xxx


2 commentaires

Merci beaucoup pour votre réponse. Votre code et votre explication sont simplement géniaux. Maintenant, je me demande quelle solution est meilleure: votre ou Stephan's . Sa solution est plus courte et ne contient que 2 boucles, votre stratégie est plus détaillée et claire. Nous avons probablement besoin de la vérification des perfs ici.


Oui, je suis sûr que c'est probablement plus rapide, mais j'ai trouvé cela plus facile à conceptualiser.



1
votes

Ceci fait la version uint64 code> - Je ne peux pas imaginer qu'un interchange est que em> difficile:

p>

<!DOCTYPE html>
<html>

<body>
<span id='out1'></span>
<br>
<span id='out2'></span>
<br>
<span id='out3'></span>
</body>

<script>
fnl='';
be=[77, 101, 130, 33, 7, 252, 253, 82];

function paddedBinary(n) {
pad='';
sv=128;
while (sv>n) {pad+='0';sv/=2;}
return pad+n.toString(2);
}

for (let i=0;i<8;i++)
fnl+=paddedBinary(be[i]);

out1.textContent=fnl;

dec=new Array(64);
for (let i=0;i<64;i++) dec[i]=new Array(21).fill(0);

function make2s() {
dec[0][0]=1;
for (let i=1;i<64;i++) {
for (let j=0;j<21;j++) 
dec[i][j]=2*dec[i-1][j];
for (let j=0;j<21;j++) 
if (dec[i][j]>9) {
dec[i][j]-=10;
dec[i][j+1]++;
}
}
}

function int64add(v1,v2) {
var res=new Array(21).fill(0);
for (let i=0;i<21;i++)
res[i]=v1[i]+v2[i];
for (let i=0;i<21;i++)
if (res[i]>9) {
res[i]-=10;
res[i+1]++;
}
return res;
}

make2s();
for (let i=0;i<64;i++)
out2.textContent+=dec[i]+' :: ';

cv=new Array(21).fill(0);
for (let i=0;i<fnl.length;i++)
if (fnl[i]=='1') cv=int64add(cv,dec[63-i]);

out3.textContent=cv;

</script>
</html>


1 commentaires

Merci pour la réponse!



4
votes

Une autre approche: diviser le problème dans deux UINT32 pour maintenir les calculs gérable.

Considérez UINT32 inférieur et supérieur ( l code> et h code>). Le nombre total pourrait être écrit comme H * 0x10000000000 + L code>. Considérant décimal, on pourrait également considérer les 9 chiffres inférieurs et les chiffres plus élevés ( ld code> et hd code>): ld = (h * 0x100000000 + l)% 1000000000 Code> et HD = (h * 0x10000000000 + L) / 1000000000 code>. Avec certains propriétés de l'arithmétique et d'algèbre des opérateurs, on peut briser ces opérations dans des opérations de 64 bits sûres sûres et composer une chaîne à la fin. P>

P>

function int64_to_str(a, signed) {
  const negative = signed && a[0] >= 128;
  const H = 0x100000000, D = 1000000000;
  let h = a[3] + a[2] * 0x100 + a[1] * 0x10000 + a[0]*0x1000000;
  let l = a[7] + a[6] * 0x100 + a[5] * 0x10000 + a[4]*0x1000000;
  if(negative) {
     h = H - 1 - h;
     l = H - l;
  }
  const hd = Math.floor(h * H / D + l / D);
  const ld = (((h % D) * (H % D)) % D + l) % D;
  const ldStr = ld + '';
  return (negative ? '-' : '') +
         (hd != 0 ? hd + '0'.repeat(9 - ldStr.length) : '') + ldStr;
}

let result = int64_to_str([77, 101, 130, 33, 7, 252, 253, 82], false);
let expectation = '5577006791947779410';
console.log(result + ' ' + (result === expectation ? '===' : '!==') + ' ' + expectation);

result = int64_to_str([255, 255, 255, 255, 255, 255, 255, 255], true);
expectation = '-1';
console.log(result + ' ' + (result === expectation ? '===' : '!==') + ' ' + expectation);


8 commentaires

Même si ce serait la meilleure approche pour traiter avec la taille fixe et que cela ne fonctionnera pas correctement dans certains cas. Je pense que la partie math.trunch (ld / d) doit être supprimée puisque le premier Summand contient déjà cette partie. Avec cela corrigé, je ne suis toujours pas sûr que cela fonctionne correctement. H * H peut entraîner des chiffres ci-dessus numéro.max_safe_integer . En divisant cela avec d et tronquage, la perte de précision devrait disparaître. Cependant, je suis plus inquiet pour (h% d) * (h% d) , puisque d * (h% d) est également trop grand, cette fois-ci avec le la précision complète étant nécessaire.


@Stephan (h% d) * (h% d) , c'est un bon point! Je pourrais utiliser 3 uint22, mais pas aujourd'hui ...


J'ai expérimenté un peu un peu et il semble que l'erreur introduite dans la multiplication ait heureusement compensée par l'erreur introduite lors du calcul du module (car d et h sont divisibles par 0x800) . Il n'est pas nécessaire de la diviser en trois groupes. Si vous ne voulez pas compter sur ce comportement, vous pouvez utiliser ((((h% d) * ((h% d) / 0x800))% d) * 0x800)% d au lieu de (h% d) * (h% d)% d (car d * (h% d) / 0x800 est plus petit que numéro.max_safe_integer ).


Pour être plus spécifique, les 11 derniers bits (donc 0x800) de d et donc h% d sont déjà nuls, donc il n'y a donc aucune erreur introduite en les définissant implicitement. zéro en raison de la "perte de précision". Juste une autre note: lorsque HD est zéro que les zéros de premier plan ne doivent pas être ajoutés.


J'ai mis à jour votre code pour inclure le support INT signé et une solution pour HD : CODEPEN.IO/STEPHTR/PEN/EVXQOP?Editors=0010


@Stephan beau travail. Si vous le souhaitez, vous êtes invité à la poster comme une réponse. Vous avez passé beaucoup plus d'efforts que moi :-)


J'ai mis à jour votre réponse, mais la modification a été déclinée d'une manière ou d'une autre. N'hésitez pas à le copier;)


Merci pour la réponse! J'aimerais pouvoir vous donner tous plus de upvotes ;)