преобразование Golang float32 в float половинной точности (GLSL float16) как uint16

Мне нужно передать некоторые данные из Go в шейдер 300 es. Данные состоят из двух uint16, упакованных в uint32. Каждый uint16 представляет собой число с плавающей запятой половинной точности (float16). Я нашел некоторый код Java PD, который выглядит так, как будто он справится с этой задачей, но я борюсь с переносом последнего оператора, который использует пару сдвигов вправо с нулевым расширением (я думаю, что другие сдвиги в порядке, т. Е. Неотрицательные). Поскольку Go немного умен с расширением, решение для порта ускользает от меня. Я действительно подумал, может быть, первый можно было бы изменить на левый сдвиг, поскольку он просто позиционирует один бит для добавления? но последняя смена сносит мой мозг до глубины души :)

Между прочим, я надеюсь, что правильно сделал скобки, поскольку приоритет операторов в Go и Java различается в отношении '-' и '››' ...

Мне нужно пойти другим путем, но, надеюсь, это будет проще без правильных сдвигов ... знаменитые последние слова!

Код Java:

https://stackoverflow.com/a/6162687/345165

// returns all higher 16 bits as 0 for all results
public static int fromFloat( float fval )
{
    int fbits = Float.floatToIntBits( fval );
    int sign = fbits >>> 16 & 0x8000;          // sign only
    int val = ( fbits & 0x7fffffff ) + 0x1000; // rounded value

    if( val >= 0x47800000 )               // might be or become NaN/Inf
    {                                     // avoid Inf due to rounding
        if( ( fbits & 0x7fffffff ) >= 0x47800000 )
        {                                 // is or must become NaN/Inf
            if( val < 0x7f800000 )        // was value but too large
                return sign | 0x7c00;     // make it +/-Inf
            return sign | 0x7c00 |        // remains +/-Inf or NaN
                ( fbits & 0x007fffff ) >>> 13; // keep NaN (and Inf) bits
        }
        return sign | 0x7bff;             // unrounded not quite Inf
    }
    if( val >= 0x38800000 )               // remains normalized value
        return sign | val - 0x38000000 >>> 13; // exp - 127 + 15
    if( val < 0x33000000 )                // too small for subnormal
        return sign;                      // becomes +/-0
    val = ( fbits & 0x7fffffff ) >>> 23;  // tmp exp for subnormal calc
    return sign | ( ( fbits & 0x7fffff | 0x800000 ) // add subnormal bit
         + ( 0x800000 >>> val - 102 )     // round depending on cut off
      >>> 126 - val );   // div by 2^(1-(exp-127+15)) and >> 13 | exp=0
}

Мой частичный порт:

func float32toUint16(f float32) uint16 {
    fbits := math.Float32bits(f)
    sign := uint16((fbits >> 16) & 0x00008000)
    rv := (fbits & 0x7fffffff) + 0x1000

    if rv >= 0x47800000 {
        if (fbits & 0x7fffffff) >= 0x47800000 {
            if rv < 0x7f800000 {
                return sign | 0x7c00
            }
            return sign | 0x7c00 | uint16((fbits&0x007fffff)>>13)
        }
        return sign | 0x7bff
    }
    if rv >= 0x38800000 {
        return sign | uint16((rv-0x38000000)>>13)
    }
    if rv < 0x33000000 {
        return sign
    }
    rv = (fbits & 0x7fffffff) >> 23
    return sign | uint16(((fbits&0x7fffff)|0x800000)+(0x800000>>(rv-102))>>(126-rv)) //these two shifts are my problem
}

func pack16(f1 float32, f2 float32) uint32 {
    ui161 := float32toUint16(f1)
    ui162 := float32toUint16(f2)
    return ((uint32(ui161) << 16) | uint32(ui162))
}

Я нашел то, что выглядело как еще более эффективный код, без ветвления, но понимание механизма того, как это работает, чтобы иметь возможность его портировать, немного выходит за рамки моих ржавых (не языковых) навыков.

https://stackoverflow.com/a/5587983

Ваше здоровье

[Edit] Похоже, что код работает со значениями, которые я использую в настоящее время (это трудно быть точным, поскольку у меня нет опыта отладки шейдеров). Думаю, мой вопрос касается правильности моего переноса, особенно последних двух смен.

[Edit2] В свете дня я вижу, что я уже ошибся с приоритетом в одном месте и исправил приведенный выше пример.

измененный:

    return sign | uint16(rv-(0x38000000>>13))

to:

    return sign | uint16((rv-0x38000000)>>13)

person Peter    schedule 17.09.2020    source источник
comment
Что не так с вашим частичным портом? Это дает неправильные результаты? Это паника? Пожалуйста, включите примеры входных данных, ожидаемых и фактических результатов.   -  person icza    schedule 17.09.2020
comment
Кстати, в Go есть float16 реализация: github.com/x448/float16, а вот ее _ 2_ реализация.   -  person icza    schedule 17.09.2020
comment
Код работает для значений, которые я сейчас использую, но почти уверен, что хотя бы один из двух сдвигов, о которых я упомянул, неправильный, поэтому я знаю, что в какой-то момент он меня укусит :) Думаю, мой вопрос должен был заключаться в том, как их перенести две смены? Спасибо за ссылку на библиотеку.   -  person Peter    schedule 18.09.2020