it-swarm-ko.tech

고정 소수점 수학을 수행하는 가장 좋은 방법은 무엇입니까?

FPU가없는 Nintendo DS)의 프로그램 속도를 높여야하므로 부동 소수점 수학 (에뮬레이트되고 느린)을 고정 소수점으로 변경해야합니다.

시작 방법은 부동 소수점을 정수로 변경하고 변환해야 할 때마다 x >> 8을 사용하여 고정 소수점 변수 x를 실제 숫자로 변환하고 x << 8 고정 소수점으로 변환합니다. 곧 변환해야 할 항목을 추적하는 것이 불가능하다는 것을 알았고 숫자의 정밀도를 변경하는 것이 어렵다는 것을 깨달았습니다 (이 경우 8).

내 질문은, 어떻게 더 쉽고 빠르게해야합니까? FixedPoint 클래스를 만들어야합니까, 또는 고정 점 8 typedef 또는 일부 함수/매크로가있는 구조체 또는 변환해야합니까? 변수 이름에 고정 소수점임을 표시하기 위해 무언가를 넣어야합니까?

45
Jeremy Ruten

고정 소수점 클래스를 사용해 볼 수 있습니다 (최신 사용 가능 @ https://github.com/eteran/cpp-utilities )

// From: https://github.com/eteran/cpp-utilities/edit/master/Fixed.h
// See also: http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math
/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2015 Evan Teran
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef FIXED_H_
#define FIXED_H_

#include <ostream>
#include <exception>
#include <cstddef> // for size_t
#include <cstdint>
#include <type_traits>

#include <boost/operators.hpp>

namespace numeric {

template <size_t I, size_t F>
class Fixed;

namespace detail {

// helper templates to make magic with types :)
// these allow us to determine resonable types from
// a desired size, they also let us infer the next largest type
// from a type which is Nice for the division op
template <size_t T>
struct type_from_size {
    static const bool is_specialized = false;
    typedef void      value_type;
};

#if defined(__GNUC__) && defined(__x86_64__)
template <>
struct type_from_size<128> {
    static const bool           is_specialized = true;
    static const size_t         size = 128;
    typedef __int128            value_type;
    typedef unsigned __int128   unsigned_type;
    typedef __int128            signed_type;
    typedef type_from_size<256> next_size;
};
#endif

template <>
struct type_from_size<64> {
    static const bool           is_specialized = true;
    static const size_t         size = 64;
    typedef int64_t             value_type;
    typedef uint64_t            unsigned_type;
    typedef int64_t             signed_type;
    typedef type_from_size<128> next_size;
};

template <>
struct type_from_size<32> {
    static const bool          is_specialized = true;
    static const size_t        size = 32;
    typedef int32_t            value_type;
    typedef uint32_t           unsigned_type;
    typedef int32_t            signed_type;
    typedef type_from_size<64> next_size;
};

template <>
struct type_from_size<16> {
    static const bool          is_specialized = true;
    static const size_t        size = 16;
    typedef int16_t            value_type;
    typedef uint16_t           unsigned_type;
    typedef int16_t            signed_type;
    typedef type_from_size<32> next_size;
};

template <>
struct type_from_size<8> {
    static const bool          is_specialized = true;
    static const size_t        size = 8;
    typedef int8_t             value_type;
    typedef uint8_t            unsigned_type;
    typedef int8_t             signed_type;
    typedef type_from_size<16> next_size;
};

// this is to assist in adding support for non-native base
// types (for adding big-int support), this should be fine
// unless your bit-int class doesn't nicely support casting
template <class B, class N>
B next_to_base(const N& rhs) {
    return static_cast<B>(rhs);
}

struct divide_by_zero : std::exception {
};

template <size_t I, size_t F>
Fixed<I,F> divide(const Fixed<I,F> &numerator, const Fixed<I,F> &denominator, Fixed<I,F> &remainder, typename std::enable_if<type_from_size<I+F>::next_size::is_specialized>::type* = 0) {

    typedef typename Fixed<I,F>::next_type next_type;
    typedef typename Fixed<I,F>::base_type base_type;
    static const size_t fractional_bits = Fixed<I,F>::fractional_bits;

    next_type t(numerator.to_raw());
    t <<= fractional_bits;

    Fixed<I,F> quotient;

    quotient  = Fixed<I,F>::from_base(next_to_base<base_type>(t / denominator.to_raw()));
    remainder = Fixed<I,F>::from_base(next_to_base<base_type>(t % denominator.to_raw()));

    return quotient;
}

template <size_t I, size_t F>
Fixed<I,F> divide(Fixed<I,F> numerator, Fixed<I,F> denominator, Fixed<I,F> &remainder, typename std::enable_if<!type_from_size<I+F>::next_size::is_specialized>::type* = 0) {

    // NOTE(eteran): division is broken for large types :-(
    // especially when dealing with negative quantities

    typedef typename Fixed<I,F>::base_type     base_type;
    typedef typename Fixed<I,F>::unsigned_type unsigned_type;

    static const int bits = Fixed<I,F>::total_bits;

    if(denominator == 0) {
        throw divide_by_zero();
    } else {

        int sign = 0;

        Fixed<I,F> quotient;

        if(numerator < 0) {
            sign ^= 1;
            numerator = -numerator;
        }

        if(denominator < 0) {
            sign ^= 1;
            denominator = -denominator;
        }

            base_type n      = numerator.to_raw();
            base_type d      = denominator.to_raw();
            base_type x      = 1;
            base_type answer = 0;

            // egyptian division algorithm
            while((n >= d) && (((d >> (bits - 1)) & 1) == 0)) {
                x <<= 1;
                d <<= 1;
            }

            while(x != 0) {
                if(n >= d) {
                    n      -= d;
                    answer += x;
                }

                x >>= 1;
                d >>= 1;
            }

            unsigned_type l1 = n;
            unsigned_type l2 = denominator.to_raw();

            // calculate the lower bits (needs to be unsigned)
            // unfortunately for many fractions this overflows the type still :-/
            const unsigned_type lo = (static_cast<unsigned_type>(n) << F) / denominator.to_raw();

            quotient  = Fixed<I,F>::from_base((answer << F) | lo);
            remainder = n;

        if(sign) {
            quotient = -quotient;
        }

        return quotient;
    }
}

// this is the usual implementation of multiplication
template <size_t I, size_t F>
void multiply(const Fixed<I,F> &lhs, const Fixed<I,F> &rhs, Fixed<I,F> &result, typename std::enable_if<type_from_size<I+F>::next_size::is_specialized>::type* = 0) {

    typedef typename Fixed<I,F>::next_type next_type;
    typedef typename Fixed<I,F>::base_type base_type;

    static const size_t fractional_bits = Fixed<I,F>::fractional_bits;

    next_type t(static_cast<next_type>(lhs.to_raw()) * static_cast<next_type>(rhs.to_raw()));
    t >>= fractional_bits;
    result = Fixed<I,F>::from_base(next_to_base<base_type>(t));
}

// this is the fall back version we use when we don't have a next size
// it is slightly slower, but is more robust since it doesn't
// require and upgraded type
template <size_t I, size_t F>
void multiply(const Fixed<I,F> &lhs, const Fixed<I,F> &rhs, Fixed<I,F> &result, typename std::enable_if<!type_from_size<I+F>::next_size::is_specialized>::type* = 0) {

    typedef typename Fixed<I,F>::base_type base_type;

    static const size_t fractional_bits = Fixed<I,F>::fractional_bits;
    static const size_t integer_mask    = Fixed<I,F>::integer_mask;
    static const size_t fractional_mask = Fixed<I,F>::fractional_mask;

    // more costly but doesn't need a larger type
    const base_type a_hi = (lhs.to_raw() & integer_mask) >> fractional_bits;
    const base_type b_hi = (rhs.to_raw() & integer_mask) >> fractional_bits;
    const base_type a_lo = (lhs.to_raw() & fractional_mask);
    const base_type b_lo = (rhs.to_raw() & fractional_mask);

    const base_type x1 = a_hi * b_hi;
    const base_type x2 = a_hi * b_lo;
    const base_type x3 = a_lo * b_hi;
    const base_type x4 = a_lo * b_lo;

    result = Fixed<I,F>::from_base((x1 << fractional_bits) + (x3 + x2) + (x4 >> fractional_bits));

}
}

/*
 * inheriting from boost::operators enables us to be a drop in replacement for base types
 * without having to specify all the different versions of operators manually
 */
template <size_t I, size_t F>
class Fixed : boost::operators<Fixed<I,F>> {
    static_assert(detail::type_from_size<I + F>::is_specialized, "invalid combination of sizes");

public:
    static const size_t fractional_bits = F;
    static const size_t integer_bits    = I;
    static const size_t total_bits      = I + F;

    typedef detail::type_from_size<total_bits>             base_type_info;

    typedef typename base_type_info::value_type            base_type;
    typedef typename base_type_info::next_size::value_type next_type;
    typedef typename base_type_info::unsigned_type         unsigned_type;

public:
    static const size_t base_size          = base_type_info::size;
    static const base_type fractional_mask = ~((~base_type(0)) << fractional_bits);
    static const base_type integer_mask    = ~fractional_mask;

public:
    static const base_type one = base_type(1) << fractional_bits;

public: // constructors
    Fixed() : data_(0) {
    }

    Fixed(long n) : data_(base_type(n) << fractional_bits) {
        // TODO(eteran): assert in range!
    }

    Fixed(unsigned long n) : data_(base_type(n) << fractional_bits) {
        // TODO(eteran): assert in range!
    }

    Fixed(int n) : data_(base_type(n) << fractional_bits) {
        // TODO(eteran): assert in range!
    }

    Fixed(unsigned int n) : data_(base_type(n) << fractional_bits) {
        // TODO(eteran): assert in range!
    }

    Fixed(float n) : data_(static_cast<base_type>(n * one)) {
        // TODO(eteran): assert in range!
    }

    Fixed(double n) : data_(static_cast<base_type>(n * one))  {
        // TODO(eteran): assert in range!
    }

    Fixed(const Fixed &o) : data_(o.data_) {
    }

    Fixed& operator=(const Fixed &o) {
        data_ = o.data_;
        return *this;
    }

private:
    // this makes it simpler to create a fixed point object from
    // a native type without scaling
    // use "Fixed::from_base" in order to perform this.
    struct NoScale {};

    Fixed(base_type n, const NoScale &) : data_(n) {
    }

public:
    static Fixed from_base(base_type n) {
        return Fixed(n, NoScale());
    }

public: // comparison operators
    bool operator==(const Fixed &o) const {
        return data_ == o.data_;
    }

    bool operator<(const Fixed &o) const {
        return data_ < o.data_;
    }

public: // unary operators
    bool operator!() const {
        return !data_;
    }

    Fixed operator~() const {
        Fixed t(*this);
        t.data_ = ~t.data_;
        return t;
    }

    Fixed operator-() const {
        Fixed t(*this);
        t.data_ = -t.data_;
        return t;
    }

    Fixed operator+() const {
        return *this;
    }

    Fixed& operator++() {
        data_ += one;
        return *this;
    }

    Fixed& operator--() {
        data_ -= one;
        return *this;
    }

public: // basic math operators
    Fixed& operator+=(const Fixed &n) {
        data_ += n.data_;
        return *this;
    }

    Fixed& operator-=(const Fixed &n) {
        data_ -= n.data_;
        return *this;
    }

    Fixed& operator&=(const Fixed &n) {
        data_ &= n.data_;
        return *this;
    }

    Fixed& operator|=(const Fixed &n) {
        data_ |= n.data_;
        return *this;
    }

    Fixed& operator^=(const Fixed &n) {
        data_ ^= n.data_;
        return *this;
    }

    Fixed& operator*=(const Fixed &n) {
        detail::multiply(*this, n, *this);
        return *this;
    }

    Fixed& operator/=(const Fixed &n) {
        Fixed temp;
        *this = detail::divide(*this, n, temp);
        return *this;
    }

    Fixed& operator>>=(const Fixed &n) {
        data_ >>= n.to_int();
        return *this;
    }

    Fixed& operator<<=(const Fixed &n) {
        data_ <<= n.to_int();
        return *this;
    }

public: // conversion to basic types
    int to_int() const {
        return (data_ & integer_mask) >> fractional_bits;
    }

    unsigned int to_uint() const {
        return (data_ & integer_mask) >> fractional_bits;
    }

    float to_float() const {
        return static_cast<float>(data_) / Fixed::one;
    }

    double to_double() const        {
        return static_cast<double>(data_) / Fixed::one;
    }

    base_type to_raw() const {
        return data_;
    }

public:
    void swap(Fixed &rhs) {
        using std::swap;
        swap(data_, rhs.data_);
    }

public:
    base_type data_;
};

// if we have the same fractional portion, but differing integer portions, we trivially upgrade the smaller type
template <size_t I1, size_t I2, size_t F>
typename std::conditional<I1 >= I2, Fixed<I1,F>, Fixed<I2,F>>::type operator+(const Fixed<I1,F> &lhs, const Fixed<I2,F> &rhs) {

    typedef typename std::conditional<
        I1 >= I2,
        Fixed<I1,F>,
        Fixed<I2,F>
    >::type T;

    const T l = T::from_base(lhs.to_raw());
    const T r = T::from_base(rhs.to_raw());
    return l + r;
}

template <size_t I1, size_t I2, size_t F>
typename std::conditional<I1 >= I2, Fixed<I1,F>, Fixed<I2,F>>::type operator-(const Fixed<I1,F> &lhs, const Fixed<I2,F> &rhs) {

    typedef typename std::conditional<
        I1 >= I2,
        Fixed<I1,F>,
        Fixed<I2,F>
    >::type T;

    const T l = T::from_base(lhs.to_raw());
    const T r = T::from_base(rhs.to_raw());
    return l - r;
}

template <size_t I1, size_t I2, size_t F>
typename std::conditional<I1 >= I2, Fixed<I1,F>, Fixed<I2,F>>::type operator*(const Fixed<I1,F> &lhs, const Fixed<I2,F> &rhs) {

    typedef typename std::conditional<
        I1 >= I2,
        Fixed<I1,F>,
        Fixed<I2,F>
    >::type T;

    const T l = T::from_base(lhs.to_raw());
    const T r = T::from_base(rhs.to_raw());
    return l * r;
}

template <size_t I1, size_t I2, size_t F>
typename std::conditional<I1 >= I2, Fixed<I1,F>, Fixed<I2,F>>::type operator/(const Fixed<I1,F> &lhs, const Fixed<I2,F> &rhs) {

    typedef typename std::conditional<
        I1 >= I2,
        Fixed<I1,F>,
        Fixed<I2,F>
    >::type T;

    const T l = T::from_base(lhs.to_raw());
    const T r = T::from_base(rhs.to_raw());
    return l / r;
}

template <size_t I, size_t F>
std::ostream &operator<<(std::ostream &os, const Fixed<I,F> &f) {
    os << f.to_double();
    return os;
}

template <size_t I, size_t F>
const size_t Fixed<I,F>::fractional_bits;

template <size_t I, size_t F>
const size_t Fixed<I,F>::integer_bits;

template <size_t I, size_t F>
const size_t Fixed<I,F>::total_bits;

}

#endif

플로트/더블을 거의 대체 할 수 있도록 설계되었으며 정밀도를 선택할 수 있습니다. 필요한 모든 수학 연산자 오버로드를 추가하기 위해 부스트를 사용 하므로이도 필요합니다 (라이브러리 종속성이 아니라 헤더 종속성 일뿐입니다).

BTW, 일반적인 사용법은 다음과 같습니다.

using namespace numeric;
typedef Fixed<16, 16> fixed;
fixed f;

유일한 실제 규칙은 숫자가 시스템의 기본 크기 (예 : 8, 16, 32, 64)를 더해야한다는 것입니다.

47
Evan Teran

현대적인 C++ 구현에서는 구체적 클래스와 같이 단순하고 간결한 추상화를 사용하면 성능이 저하되지 않습니다. 고정 소수점 계산은 정확하게 적절하게 설계된 클래스를 사용하면 많은 버그로부터 당신을 구할 수있는 곳입니다.

따라서 고정 점 8 클래스 를 작성해야합니다. 철저히 테스트하고 디버깅하십시오. 일반 정수를 사용하는 것과 비교하여 성능을 확신 해야하는 경우 측정하십시오.

고정 소수점 계산의 복잡성을 한 곳으로 옮김으로써 많은 문제를 피할 수 있습니다.

원하는 경우 클래스를 템플릿으로 만들고 기존 FixedPoint8 함께 typedef FixedPoint<short, 8> FixedPoint8; 그러나 대상 아키텍처에서는 이것이 필요하지 않으므로 처음에는 템플릿의 복잡성을 피하십시오.

인터넷 어딘가에 좋은 고정 소수점 클래스가있을 것입니다. Boost 라이브러리에서 시작하겠습니다.

31
Antti Kissaniemi

부동 소수점 코드는 실제로 소수점을 사용합니까? 그렇다면:

먼저 고정 소수점 수학 소개에 대한 Randy Yates의 논문을 읽어야합니다. http://www.digitalsignallabs.com/fp.pdf

그런 다음 부동 소수점 코드에서 "프로파일 링"을 수행하여 코드의 "중요한"지점에 필요한 고정 소수점 값의 적절한 범위를 파악해야합니다 (예 : U (5,3) = 왼쪽에 5 비트, 오른쪽에 3 비트, 부호 없음.

이 시점에서 위에서 언급 한 논문에서 산술 규칙을 적용 할 수 있습니다. 규칙은 산술 연산으로 인해 발생하는 비트를 해석하는 방법을 지정합니다. 매크로 나 함수를 작성하여 작업을 수행 할 수 있습니다.

부동 소수점 대 고정 소수점 결과를 비교하기 위해 부동 소수점 버전을 유지하는 것이 편리합니다.

9
ryu

고정 소수점 표현 변경은 일반적으로 '스케일링'이라고합니다.

퍼포먼스 페널티가없는 클래스로이 작업을 수행 할 수 있다면 그렇게하는 것입니다. 컴파일러와 인라인 방법에 크게 의존합니다. 클래스를 사용하여 성능이 저하되는 경우보다 전통적인 C 스타일 접근법이 필요합니다. OOP 접근 방식은 기존의 구현 방식과 거의 비슷한 컴파일러 적용 유형 안전성을 제공합니다.

@cibyr은 좋은 OOP 구현입니다. 이제는 더 전통적인 것입니다.

어떤 변수가 스케일링되었는지 추적하려면 일관된 규칙을 사용해야합니다. 각 변수 이름 끝에 표기를하여 값의 스케일 여부를 표시하고 x >> 8 및 x << 8로 확장되는 매크로 SCALE () 및 UNSCALE ()을 작성하십시오.

#define SCALE(x) (x>>8)
#define UNSCALE(x) (x<<8)

xPositionUnscaled = UNSCALE(10);
xPositionScaled = SCALE(xPositionUnscaled);

너무 많은 표기법을 사용하는 것이 추가 작업처럼 보일 수 있지만 다른 줄을 보지 않고 모든 줄이 올바른지 한 눈에 알 수있는 방법에 주목하십시오. 예를 들면 다음과 같습니다.

xPositionScaled = SCALE(xPositionScaled);

검사에 의해 분명히 잘못되었습니다.

이것은 Joel이이 게시물에서 언급 이라는 Apps Hungarian 아이디어의 변형입니다.

6
Bart

처리하기 위해 특별한 하드웨어가없는 CPU에서는 부동 소수점을 전혀 사용하지 않습니다. 내 조언은 모든 숫자를 특정 요소로 확장 된 정수로 취급하는 것입니다. 예를 들어, 모든 화폐 가치는 센트 (float)가 아닌 정수 (센트)입니다. 예를 들어 0.72는 정수 72로 표시됩니다.

덧셈과 뺄셈은 (0.72 + 1이 72 + 100이 172가 1.72가 됨)과 같은 매우 간단한 정수 연산입니다.

곱하기는 정수 곱하기가 필요하고 (0.72 * 2가 72 * 200이 14400이 144가 됨 (스케일 백)이 1.44가 됨)과 같은 스케일 백이 필요하기 때문에 약간 더 복잡합니다.

더 복잡한 수학 (사인, 코사인 등)을 수행하기위한 특수 함수가 필요할 수도 있지만 조회 테이블을 사용하여 속도를 높일 수도 있습니다. 예 : 고정 2 표현을 사용하므로 (0.0,1] (0-99) 범위에 100 개의 값만 있고 sin/cos는이 범위를 벗어나 반복되므로 100 정수 조회 테이블 만 필요합니다.

건배, Pax.

6
paxdiablo

고정 소수점 숫자를 처음 발견했을 때 Joe Lemieux의 기사 C에서 고정 소수점 수학 이 매우 유용하다는 사실을 발견했으며 고정 소수점 값을 나타내는 한 가지 방법을 제안합니다.

그래도 고정 소수점 숫자에 그의 조합 표현을 사용하지 않았습니다. 나는 주로 C에서 고정 소수점에 대한 경험이 있으므로 클래스를 사용할 수있는 옵션이 없었습니다. 그러나 대부분의 경우 매크로에서 소수 비트 수를 정의하고 설명 변수 이름을 사용하면 상당히 쉽게 작업 할 수 있다고 생각합니다. 또한 곱셈, 특히 나눗셈을위한 매크로 또는 함수를 사용하는 것이 가장 좋습니다. 또는 읽을 수없는 코드를 빨리 얻습니다.

예를 들어, 24.8 값으로

 #include "stdio.h"

/* Declarations for fixed point stuff */

typedef int int_fixed;

#define FRACT_BITS 8
#define FIXED_POINT_ONE (1 << FRACT_BITS)
#define MAKE_INT_FIXED(x) ((x) << FRACT_BITS)
#define MAKE_FLOAT_FIXED(x) ((int_fixed)((x) * FIXED_POINT_ONE))
#define MAKE_FIXED_INT(x) ((x) >> FRACT_BITS)
#define MAKE_FIXED_FLOAT(x) (((float)(x)) / FIXED_POINT_ONE)

#define FIXED_MULT(x, y) ((x)*(y) >> FRACT_BITS)
#define FIXED_DIV(x, y) (((x)<<FRACT_BITS) / (y))

/* tests */
int main()
{
    int_fixed fixed_x = MAKE_FLOAT_FIXED( 4.5f );
    int_fixed fixed_y = MAKE_INT_FIXED( 2 );

    int_fixed fixed_result = FIXED_MULT( fixed_x, fixed_y );
    printf( "%.1f\n", MAKE_FIXED_FLOAT( fixed_result ) );

    fixed_result = FIXED_DIV( fixed_result, fixed_y );
    printf( "%.1f\n", MAKE_FIXED_FLOAT( fixed_result ) );

    return 0;
}

어느 쪽이 기록

 9.0 
 4.5 

해당 매크로에는 모든 종류의 정수 오버플로 문제가 있으므로 매크로를 단순하게 유지하고 싶었습니다. 이것은 C 에서이 작업을 수행 한 방법에 대한 빠르고 더러운 예입니다. C++에서는 연산자 오버로드를 사용하여 훨씬 더 깨끗한 것을 만들 수 있습니다. 실제로 C 코드를 훨씬 더 예쁘게 만들 수 있습니다 ...

나는 이것이 오래 전부터 말하는 것이라고 생각합니다 : typedef와 macro 접근법을 사용하는 것이 좋다고 생각합니다. 고정 소수점 값을 포함하는 변수에 대해 분명한 한 유지 관리하기가 어렵지는 않지만 C++ 클래스만큼 아름답지는 않을 것입니다.

내가 너의 입장에 있다면 병목 현상이 어디에 있는지 보여주기 위해 프로파일 링 번호를 얻으려고 노력할 것이다. 상대적으로 적은 수의 경우 typedef 및 매크로를 사용하십시오. 그래도 모든 부동 소수점을 고정 소수점 수학으로 대체해야한다고 결정하면 클래스를 사용하는 것이 좋습니다.

5
ryan_s

게임 프로그래밍 전문가의 속임수 의 원래 버전에는 고정 소수점 수학 구현에 대한 전체 장이 있습니다.

4
Ana Betts

어떤 방법을 사용하든간에 (나는 typedef와 변환을위한 일부 CPP 매크로에 의존하고 있습니다), 어떤 훈련을 통해 앞뒤로 변환해야합니다.

앞뒤로 변환 할 필요가 없다는 것을 알게 될 것입니다. 전체 시스템의 모든 것이 x256이라고 상상해보십시오.

1
jfm3
template <int precision = 8> class FixedPoint {
private:
    int val_;
public:
    inline FixedPoint(int val) : val_ (val << precision) {};
    inline operator int() { return val_ >> precision; }
    // Other operators...
};
1
cibyr