Шестнадцатеричное представление двойной дробной части для SHA-256

Я пытаюсь написать хеш-функцию SHA-256 для практики. В википедии указано, что начальные значения хеша задаются дробными частями квадрата корни первых 8 простых чисел 2..19. Сейчас пытаюсь их вычислить. Что я сделал до сих пор:

#include <vector>
#include <cstdint>
#include <cmath>
#include <cstdio>

// fill primes with all prime values between min and max value
int getPrimes(uint32_t min, uint32_t max, std::vector<uint32_t>* primes)
{
  if (min < 1) min = 1;     // primes can only be >= 1
  if (min > max) return 0;  // max has to be larger than min


  for (uint32_t value = min; value <= max; value++)
  {

    uint32_t tmp;
    for (tmp = 2; tmp <= sqrt(value); tmp++)  // start to check with 2, because 1 is always going to work
    {
      if (value % tmp == 0)
      {
        break;
      }
    }
    if (tmp > sqrt(value)) primes->push_back(value);   // if no other integer divisor is found, add number to vector
  }

  return 0;
}

int main()
{
  std::vector<uint32_t> primes;

  getPrimes(2, 20, &primes);  // fills vector with all prime values between 2 and 20

  double tmp = sqrt(primes[0]);                       // get square root, returns double
  printf("value %f\n", tmp);                          // debug
  printf("size of double %i\n", sizeof(double));      // get representation byte size
  double * tmpOffset = &tmp;                          // get value offset
  unsigned char   * tmpChar   = (unsigned char*)tmpOffset;              // convert to char pointer

  printf("address of variable %i\n", &tmp);           // debug
  printf("raw values\n1:%X\n2:%X\n3:%X\n4:%X\n5:%X\n6:%X\n7:%X\n8:%X\n",
         (uint8_t)tmpChar[0], (uint8_t)tmpChar[1], (uint8_t)tmpChar[2], (uint8_t)tmpChar[3],
         (uint8_t)tmpChar[4], (uint8_t)tmpChar[5], (uint8_t)tmpChar[6], (uint8_t)tmpChar[7]);


  return 0;
}

Это возвращает первые 8 простых чисел, вычисляет квадратный корень из 2 и извлекает непосредственно из области памяти, где он хранится, фактические значения байтов:

value 1.414214
size of double 8
address of variable 6881016
raw values
1:CD
2:3B
3:7F
4:66
5:9E
6:A0
7:F6
8:3F

По сравнению со значением, указанным в статье Википедии 0x6a09e667, то, что я здесь делаю, выглядит ужасно неправильно. Происходит ли переназначение или насколько точна бинарная репрезентация двойника? Может ли кто-нибудь указать мне правильное направление, как правильно вычислить дробную часть в шестнадцатеричном формате?

Изменить: спасибо за помощь! Это некрасиво, но пока работает.

  printf("raw fractional part:\n0x%02X %02X %02X %02X %02X %02X %02X\n",
         (uint8_t)(0xf & tmpChar[6]), (uint8_t)tmpChar[5], (uint8_t)tmpChar[4], (uint8_t)tmpChar[3],
         (uint8_t)tmpChar[2], (uint8_t)tmpChar[1], (uint8_t)tmpChar[0]);


  uint32_t fracPart = (0xf & tmpChar[6]);
  fracPart <<= 8;
  fracPart |= tmpChar[5];
  fracPart <<= 8;
  fracPart |= tmpChar[4] ;
  fracPart <<= 8;
  fracPart |= tmpChar[3];
  fracPart <<= 4;
  fracPart |= (0xf0 & tmpChar[2]) >> 4;
  printf("fractional part: %X\n", fracPart);

Edit2 Немного более приятная реализация:

  uint32_t fracPart2 = *(uint32_t*)((char*)&tmp + 3);     // point to fractional part - 4 bit
  fracPart2 <<= 4;                                        // shift to correct value
  fracPart2 |= (0xf0 & *((char*)&tmp + 2)) >> 4;          // append last 4 bit
  printf("beautiful fractional part: %X\n", fracPart2);

Это решение сильно зависит от платформы, и во втором подходе я собираюсь сделать что-то вроде ссылки в комментарии 2.

Изменить3

Итак, это мое окончательное решение, которое не зависит от внутреннего представления двойника и вычисляет дробь только с помощью математики.

uint32_t getFractionalPart(double value)
{
  uint32_t retValue = 0;

  for (uint8_t i = 0; i < 8; i++)
  {
    value = value - floor(value);
    retValue <<= 4;
    value *= 16;
    retValue += floor(value);
  }

  return retValue;
}

person Aeonos    schedule 09.03.2017    source источник
comment
Как вы подозреваете, двоичное представление чисел типа double и float не так просто. Я бы предложил создать шестнадцатеричную строку математически, вместо того, чтобы возиться с байтами и сомнительными приведениями указателя. (повторно умножаем на 16 и получаем целую часть)   -  person qxz    schedule 09.03.2017
comment
0x6a09e667 означает, что √2 равно 1.6a09e667…₁₆ в шестнадцатеричном формате, читайте как преобразовать десятичные дроби в шестнадцатеричные дроби"> stackoverflow.com/questions/20650954/. double не представлено так. Прочитайте en.wikipedia.org/wiki/Double-precision_format_floating-point.   -  person kennytm    schedule 09.03.2017


Ответы (1)


Следует иметь в виду, что двойное значение здесь равно 64 битам. Если вы посмотрите на представление IEEE для двойных чисел по ссылке ниже, оно имеет 1 знаковый бит, 11 битов экспоненты, а остальные — биты точности.

Теперь, когда вы посмотрите на результат, который вы получили, взгляните на кусочки, которые я поставил в кавычки, они выглядят знакомыми? Причина, по которой число находится в обратном порядке, заключается в порядке байтов. В этом случае с 12-го бита начинается дробная часть, а затем она перемещается назад.

1:CD
2:3B
3:'7'F
4:'66'
5:'9E'
6:'A0'
7:F'6'
8:3F

or

8:3F
7:F'6'
6:'A0'
5:'9E'
4:'66'
3:'7'F
2:3B
1:CD

https://en.wikipedia.org/wiki/Double-precision_format_floating-point

person revelationnow    schedule 09.03.2017