68 lines
3 KiB
C++
68 lines
3 KiB
C++
// Small numeric helpers shared by the sim formulas.
|
|
//
|
|
// The original engine converts floating point to integer by truncation toward zero
|
|
// (the MSVC float-to-long helper); `Ftol`/`Ftoi64` reproduce that so every rounding site
|
|
// in this module is explicit about which conversion it performs.
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <cmath>
|
|
|
|
namespace sots::sim {
|
|
|
|
// Truncating float -> int32 conversion. Out-of-range input saturates (the original
|
|
// helper's behaviour there is undefined; saturating keeps our tests deterministic).
|
|
inline int Ftol(double v) {
|
|
if (!(v == v)) return 0; // NaN
|
|
if (v >= 2147483647.0) return 2147483647;
|
|
if (v <= -2147483648.0) return -2147483647 - 1;
|
|
return static_cast<int>(v); // C++ static_cast truncates toward zero
|
|
}
|
|
|
|
// Truncating float -> int64 conversion.
|
|
inline std::int64_t Ftoi64(double v) {
|
|
if (!(v == v)) return 0;
|
|
if (v >= 9223372036854775807.0) return INT64_MAX;
|
|
if (v <= -9223372036854775808.0) return INT64_MIN;
|
|
return static_cast<std::int64_t>(v);
|
|
}
|
|
|
|
// The engine's "round" helper is `fistp` followed by `fild`: it rounds with the x87's
|
|
// current mode, which MSVC leaves at round-to-nearest, ties-to-EVEN, and hands the result
|
|
// back as a float rather than an int. Ties therefore go to the even neighbour, not away
|
|
// from zero -- 0.5 rounds to 0 and 1.5 rounds to 2. (B4 correction; the module previously
|
|
// used round-half-away-from-zero here.)
|
|
inline double RoundHalfEven(double v) {
|
|
if (!(v == v)) return v;
|
|
return std::nearbyint(v); // the default FE_TONEAREST mode is ties-to-even
|
|
}
|
|
|
|
// The same rounding, delivered as an int for the callers that immediately truncate it.
|
|
inline int RoundToInt(double v) { return Ftol(RoundHalfEven(v)); }
|
|
|
|
inline double Clamp01(double v) { return v < 0 ? 0 : (v > 1 ? 1 : v); }
|
|
|
|
// Narrow to float32 and widen back. The engine keeps most colony and movement scalars in
|
|
// 4-byte floats and does the arithmetic on the x87 stack, so every store back to such a
|
|
// field rounds to single precision; a formula that skips that step drifts.
|
|
inline double F32(double v) { return static_cast<double>(static_cast<float>(v)); }
|
|
|
|
// A float32 literal as the image holds it. The compiler widened these decimals once, at
|
|
// compile time, so `0.02` in the disassembly is really 0.019999999552965164; using the exact
|
|
// decimal rounds differently at a truncation or comparison boundary.
|
|
inline constexpr double WidenedFloatLiteral(float v) { return static_cast<double>(v); }
|
|
|
|
template <class T>
|
|
inline T ClampT(T v, T lo, T hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
|
|
|
// Saturating add clamped to +/-2,000,000,000 -- the treasury never overflows.
|
|
// CONFIDENCE: high.
|
|
inline int SaturatingAdd(int a, int b) {
|
|
const std::int64_t s = static_cast<std::int64_t>(a) + static_cast<std::int64_t>(b);
|
|
constexpr std::int64_t kLimit = 2000000000;
|
|
if (s > kLimit) return static_cast<int>(kLimit);
|
|
if (s < -kLimit) return static_cast<int>(-kLimit);
|
|
return static_cast<int>(s);
|
|
}
|
|
|
|
} // namespace sots::sim
|