perf(math): classify finite high-low components by binary64 bits
This commit is contained in:
@@ -24,6 +24,8 @@ __pycache__/
|
||||
.harness/config.json
|
||||
.harness/build/
|
||||
.harness/build-ninja-msvc-debug/
|
||||
.harness/build-ninja-msvc-debug-english/
|
||||
.harness/build-ninja-msvc-debug-prefix/
|
||||
.harness/build-ninja-msvc-release/
|
||||
.harness/performance/
|
||||
.harness/doxygen/
|
||||
|
||||
@@ -151,6 +151,26 @@ T1의 실제 Hex8 VTune stack은 `TwoSum → Add → Multiply → hex8_high_low:
|
||||
TwoProduct의 least-bit-sum < −1074 거부와 FMA residual·TwoSum 정규화는 그대로다.
|
||||
- zero/low-zero shortcut, nonzero multiplication association 변경은 채택하지 않았다.
|
||||
|
||||
이후 공통 산술 담당자는 실제 `fpclassify` 비용을 근거로 이 private header의
|
||||
finite 분류만 보강했다. `IsFiniteBinary64`는 memcpy로 읽은 binary64 exponent가
|
||||
2047인지 확인하며 NaN/Inf를 거부한다. 연산식·검사 위치·정규화·FMA·exact-tail
|
||||
기준은 그대로다. [독립 source 검토](../../.harness/performance/shared-classification/source-numerical-review.md)는
|
||||
분류와 지원 환경의 값·실패 조건 보존을 확인했다. Signaling NaN의 exception flag나
|
||||
trap 관측까지 동등하다고 주장하지 않는다. 원래 분류의 새 경계 검사 9/9와
|
||||
변경 후 pair bit oracle·Hex8·paired state/kinematics 62/62가 native 0으로 통과했다.
|
||||
[변경 전 기록](../../.harness/performance/shared-classification/before-native.json),
|
||||
[변경 후 기록](../../.harness/performance/shared-classification/after-native.json)과
|
||||
[검사 로그](../../.harness/performance/shared-classification/after-checks.txt)를 보존한다.
|
||||
이 결과는 최종 모델의 시간 목표나 전체 회귀 검사를 대체하지 않는다.
|
||||
|
||||
위 검증 중 기존 MSVC/Ninja의 include prefix 인식 오류도 확인했다. 이 PC에는
|
||||
한국어 compiler resource만 있어 `VSLANG=1033`만 지정한 fresh build도 dependency가
|
||||
0개였다. 실제 출력 접두사 `참고: 포함 파일: `를 별도 로컬 CMake include로 지정한
|
||||
fresh build에서는 `hex8_high_low.cpp.obj`의 13개 dependency와 산술 header를 확인했다.
|
||||
[실제 dependency](../../.harness/performance/shared-classification/after-dependencies.txt)를 보존한다.
|
||||
공용 preset·compiler flags·설치 환경은 바꾸지 않았다. 기존 자료의 직접 cpp 재컴파일과
|
||||
header 변경 추적의 한계를 구별하며 최종 검증도 보정한 fresh build에서 수행한다.
|
||||
|
||||
[독립 수치 검토](../../.harness/performance/t2/arithmetic-numerical-review.md)는
|
||||
위 두 helper proof/source 범위 PASS이며 실제 검사/속도와 전체 MKL diff 검토를
|
||||
대체하지 않는다. Frozen original oracle는 pinned `b4fc1bb`의 원본이며 include guard와
|
||||
|
||||
@@ -38,9 +38,18 @@ struct HighLow {
|
||||
inline HighLow InvalidHighLow() {
|
||||
return {std::numeric_limits<double>::quiet_NaN(), 0};
|
||||
}
|
||||
/// @brief Classifies finite binary64 values without floating arithmetic.
|
||||
inline bool IsFiniteBinary64(double value) {
|
||||
static_assert(sizeof(double) == sizeof(std::uint64_t) &&
|
||||
std::numeric_limits<double>::is_iec559 &&
|
||||
std::numeric_limits<double>::digits == 53);
|
||||
std::uint64_t bits = 0;
|
||||
std::memcpy(&bits, &value, sizeof(bits));
|
||||
return (bits & 0x7ff0000000000000ULL) != 0x7ff0000000000000ULL;
|
||||
}
|
||||
/// @brief Checks both physical components, before comparisons or publication.
|
||||
inline bool IsFinite(HighLow value) {
|
||||
return std::isfinite(value.high) && std::isfinite(value.low);
|
||||
return IsFiniteBinary64(value.high) && IsFiniteBinary64(value.low);
|
||||
}
|
||||
/// @brief Recovers the exact error of supported finite binary64 addition.
|
||||
inline HighLow TwoSum(double left, double right) {
|
||||
@@ -48,8 +57,8 @@ inline HighLow TwoSum(double left, double right) {
|
||||
const double bridge = sum - left;
|
||||
const double restored_left = sum - bridge;
|
||||
const double error = (left - restored_left) + (right - bridge);
|
||||
if (!std::isfinite(sum) || !std::isfinite(bridge) ||
|
||||
!std::isfinite(restored_left) || !std::isfinite(error))
|
||||
if (!IsFiniteBinary64(sum) || !IsFiniteBinary64(bridge) ||
|
||||
!IsFiniteBinary64(restored_left) || !IsFiniteBinary64(error))
|
||||
return InvalidHighLow();
|
||||
return sum == 0 && error == 0 ? HighLow{} : HighLow{sum, error};
|
||||
}
|
||||
@@ -76,7 +85,7 @@ inline HighLow Add(HighLow left, HighLow right) {
|
||||
const auto joined = TwoSum(head.high, middle.high);
|
||||
const double remainder = (joined.low + middle.low) + tail.low;
|
||||
if (!IsFinite(head) || !IsFinite(tail) || !IsFinite(middle) ||
|
||||
!IsFinite(joined) || !std::isfinite(remainder))
|
||||
!IsFinite(joined) || !IsFiniteBinary64(remainder))
|
||||
return InvalidHighLow();
|
||||
return TwoSum(joined.high, remainder);
|
||||
}
|
||||
@@ -119,14 +128,14 @@ inline int LowestBitExponent(double value) {
|
||||
/// @brief Gives a product and its FMA residual when the dyadic tail fits.
|
||||
/// @note Rejects underflow-risk exact tails, even if the rounded high is finite.
|
||||
inline HighLow TwoProduct(double left, double right) {
|
||||
if (!std::isfinite(left) || !std::isfinite(right)) return InvalidHighLow();
|
||||
if (!IsFiniteBinary64(left) || !IsFiniteBinary64(right)) return InvalidHighLow();
|
||||
if (left == 0 || right == 0) return {};
|
||||
if (LowestBitExponent(left) + LowestBitExponent(right) < -1074)
|
||||
return InvalidHighLow();
|
||||
const double product = left * right;
|
||||
if (!std::isfinite(product)) return InvalidHighLow();
|
||||
if (!IsFiniteBinary64(product)) return InvalidHighLow();
|
||||
const double error = std::fma(left, right, -product);
|
||||
if (!std::isfinite(error)) return InvalidHighLow();
|
||||
if (!IsFiniteBinary64(error)) return InvalidHighLow();
|
||||
return TwoSum(product, error);
|
||||
}
|
||||
/// @brief Retains all four component products in deterministic order.
|
||||
@@ -159,13 +168,13 @@ inline HighLow Divide(HighLow numerator, HighLow denominator) {
|
||||
return InvalidHighLow();
|
||||
if (Compare(numerator, HighLow{}) == 0) return {};
|
||||
const double first = numerator.high / denominator.high;
|
||||
if (!std::isfinite(first) || first == 0) return InvalidHighLow();
|
||||
if (!IsFiniteBinary64(first) || first == 0) return InvalidHighLow();
|
||||
auto quotient = HighLow{first};
|
||||
for (int iteration = 0; iteration < 2; ++iteration) {
|
||||
const auto residual = Subtract(numerator, Multiply(denominator, quotient));
|
||||
if (!IsNormalized(residual)) return InvalidHighLow();
|
||||
const double correction = residual.high / denominator.high;
|
||||
if (!std::isfinite(correction) || (residual.high != 0 && correction == 0))
|
||||
if (!IsFiniteBinary64(correction) || (residual.high != 0 && correction == 0))
|
||||
return InvalidHighLow();
|
||||
quotient = Add(quotient, HighLow{correction});
|
||||
}
|
||||
|
||||
@@ -144,6 +144,23 @@ double FromBits(const std::uint64_t bits) {
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST(HighLowArithmetic, FiniteClassificationMatchesBinary64Boundaries) {
|
||||
const auto check = [](const double value) {
|
||||
const bool expected = std::isfinite(value);
|
||||
EXPECT_EQ(IsFinite(HighLow{value, 0.0}), expected);
|
||||
EXPECT_EQ(IsFinite(HighLow{1.0, value}), expected);
|
||||
};
|
||||
for (std::uint64_t exponent = 0; exponent < 2048U; ++exponent) {
|
||||
for (const auto fraction : {0ULL, 1ULL, 1ULL << 51U,
|
||||
(1ULL << 52U) - 1ULL}) {
|
||||
for (const auto sign : {0ULL, 1ULL << 63U})
|
||||
check(FromBits(sign | (exponent << 52U) | fraction));
|
||||
}
|
||||
}
|
||||
std::mt19937_64 random{0x5800f17U};
|
||||
for (int sample = 0; sample < 10000; ++sample) check(FromBits(random()));
|
||||
}
|
||||
|
||||
void ExpectSamePair(const HighLow actual,
|
||||
const fesa::high_low_original::HighLow expected) {
|
||||
EXPECT_EQ(Bits(actual.high), Bits(expected.high));
|
||||
|
||||
Reference in New Issue
Block a user