0

convert from float-point to custom numeric typeという質問に基づいて、浮動小数点型を整数の配列に変換するポータブルで安全な方法を見つけました。コードは正常に動作しますが、 から に変換するときの値によってdoubleunsigned long long unsigned long longsigned long long Visual C++ 2008、intel xe 2013、および gcc 4.7.2 では、コンパイル時のエラーではなく、表現可能な最小値またはゼロである無効な値で変換によって安全に表現されますが失敗します。

ここにコードがあります:(関数内のwhileループ内の最初のステートメントに注意してくださいmain

#ifndef CHAR_BIT
#include <limits.h>
#endif

#include <float.h>
#include <math.h>

typedef signed int          int32;
typedef signed long long    int64;
typedef unsigned int       uint32;
typedef unsigned long long uint64;

typedef float  float32;
typedef double float64;

// get size of type in bits corresponding to CHAR_BIT.
template<typename t>
struct sizeof_ex
{
    static const uint32 value = sizeof(t) * CHAR_BIT;
};

// factorial function
float64 fct(int32 i)
{
    float64 r = 1;
    do r *= i; while(--i > 1);
    return r;
}

int main()
{
    // maximum 2 to power that can be stored in uint32
    const uint32 power_2  = uint32(~0);
    // number of binary digits in power_2
    const uint32 digit_cnt = sizeof_ex<uint32>::value;
    // number of array elements that will store expanded value
    const uint32 comp_count = DBL_MAX_EXP / digit_cnt + uint32((DBL_MAX_EXP / digit_cnt) * digit_cnt < DBL_MAX_EXP);
    // array elements
    uint32 value[comp_count];

    // get factorial for 23
    float64 f = fct<float64>(23);
    // save sign for later correction
    bool sign = f < 0;
    // remove sign from float-point if exists
    if (sign) f *= -1;

    // get number of binary digits in f
    uint32 actual_digits = 0;
    frexp(f, (int32*)&actual_digits);

    // get start index in array for little-endian format
    uint32 start_index = (actual_digits / digit_cnt) + uint32((actual_digits / digit_cnt) * digit_cnt < actual_digits) - 1;

    // get all parts but the last
    while (start_index > 0)
    {
        // store current part
        // in this line the compiler fails
        value[start_index] = uint64(f / power_2);
        // exclude it from f
        f -= power_2 * float64(value[start_index]);
        // decrement index
        --start_index;
    }
    // get last part
    value[0] = uint32(f);
}

上記の変換コードは、コンパイラごとに異なる結果を返します。つまり、factorial 関数のパラメータが 20 の場合、すべてのコンパイラが有効な結果を返し、値が 20 を超える場合、一部のコンパイラは結果の一部を取得し、他のコンパイラは取得しない場合があります。大きくなる、例えば35ゼロになる。

これらのエラーが発生する理由を教えてください。

ありがとうございました。

4

1 に答える 1

1

あなたの変換ロジックは意味がないと思います。

「power_2」と呼ばれる値がありますが、これは実際には 2 の累乗であるとコメントしていますが、実際には 2 の累乗ではありません。

32 ビット未満で割ることによって、非常に大きな (>64 ビット) 数値のビットを抽出します。明らかに、その結​​果は 32 ビットを超えますが、それを切り捨てて 32 ビット値に格納します。次に、それを元の除数で再乗算し、フロートから減算します。ただし、数値が切り捨てられたため、元の値よりもはるかに少ない値を差し引いています。これは、ほぼ確実に期待したものではありませんでした。

もっと間違っていると思います.32ビットの長さの倍数ではない数値の場合、上位32ビットが常に必要なわけではなく、実際の長さmod 32が必要です.

これは、あなたがやろうとしていると私が思うことを行うコードのやや怠惰なハックです。pow()は最適化される可能性があることに注意してください。

while (start_index > 0)
{
    float64 fpow = pow(2., 32. * start_index);
    // store current part
    // in this line the compiler fails

    value[start_index] = f / fpow;
    // exclude it from f

    f -= fpow * float64(value[start_index]);
    // decrement index
    --start_index;
}

それはほとんどテストされていませんが、うまくいけば私の言いたいことを示しています。

于 2013-02-28T17:08:45.903 に答える