Skip to content

Fix Log precision and Pow returning 1 for a zero base - #10

Merged
Auburn merged 1 commit into
masterfrom
fix/pow-log
Oct 14, 2026
Merged

Auburn merged 1 commit into
masterfrom
fix/pow-log

Conversation

@Auburn

@Auburn Auburn commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Problem

Two bugs in the generic FS::Log and FS::Pow (ToolSet/Generic/Functions.h). Every feature set uses this code except relaxed SCALAR, which calls std::log and std::pow.

  1. Log multiplied a term in instead of adding it. The Cephes logf step is y += e * -2.12194440e-4f, but the code had y *= .... That scaled away the polynomial correction, so the result was roughly x - x²/2 + e·ln2. The error is worst for inputs near 1.
  2. Pow( 0, p ) returned 1. Pow is Exp( pow * Log( value ) ), and Log masks inputs ≤ 0 to 0, so a zero base gave Exp( 0 ) = 1 instead of 0.

FastNoise2's Minkowski distance hit both bugs. A zero distance component added 1 instead of 0, and P=2 differed from Euclidean by up to 0.7% even after that was worked around. This change lets Auburn/FastNoise2#193 drop its local PowNonNegative workaround after a pin bump.

Fix

  • Log: y *= e * -2.12194440e-4f → y += e * -2.12194440e-4f, matching Cephes logf and sse_mathfun's log_ps.
  • Pow: Masked( value > 0, Exp( pow * Log( value ) ) ), so value <= 0 returns 0. This is correct for 0^p when p > 0.

Behaviour change

  • Log output changes for all inputs, towards the correct value.
  • Pow with value <= 0 now returns 0 instead of 1. This also makes 0^0 and 0^p with p < 0 return 0, where std::pow gives 1 and inf. Previously every value <= 0 case returned 1, which was wrong for p > 0.

Testing

I added an f32 pow zero base test. It sets half the bases to 0, uses p in [0.1, 8], and keeps ranges inside Exp's clamp. Its relaxed scalar variant uses std::pow, which is what catches the bug.

I ran the test suite on a Ryzen 9 7950X (SCALAR, SSE2, SSE4.1, AVX2, AVX512, plus RELAXED variants) with MSVC 17.14 and clang-cl. The relaxed scalar build uses std::log and std::pow; this table compares it against the generic code (MSVC):

Test Before After
f32 log max 615,826 ULP, 527k values out of tolerance max 1 ULP, 0 out of tolerance
f32 pow zero base (new) 12.5M of 16.7M values out of tolerance max 96 ULP, 0 out of tolerance
f32 pow 1.34M finite values out of tolerance 2.1k, all at the Exp clamp

The non-relaxed SIMD feature sets match scalar exactly, before and after.

Not changed

These failures already exist on master and are unrelated to this PR:

  • f32 pow still fails where results go past e^±88.38. Exp clamps there, while std::pow returns inf or a denormal. Every remaining f32 pow failure is in that region.
  • f32 log2 (the Trig.h implementation, which is separate) is inaccurate near 1.
  • Various trig and f32 modulus failures.

- Log multiplied y by the e * -2.12194440e-4 term instead of adding it, which discarded the polynomial correction. Max error vs std::log drops from 615826 ULP to 1 ULP
- Log masks inputs <= 0 to 0, so Pow( 0, p ) returned Exp( 0 ) = 1. Pow now returns 0 for value <= 0
- Add "f32 pow zero base" test
@Auburn
Auburn merged commit 685ab00 into master Oct 14, 2026
8 checks passed
Auburn added a commit to Auburn/FastNoise2 that referenced this pull request Oct 14, 2026
FS::Pow now returns 0 for a zero base (Auburn/FastSIMD#10), so the local PowNonNegative workaround is no longer needed. The Log fix in the same bump also removes most of the remaining Minkowski error.
Auburn added a commit to Auburn/FastNoise2 that referenced this pull request Oct 14, 2026
* Fix Minkowski distance P sampling and zero components

Minkowski P was sampled at the per-cell offset vector rather than the input position, so a connected generator lost its spatial variation and was evaluated for every cell distance calculation. Sample it once at the input position, only when Minkowski is selected. FS::Pow( 0, p ) returns 1, so zero distance components contributed 1 instead of 0, mask those out.

* Bump FastSIMD for the Pow and Log fixes, drop PowNonNegative

FS::Pow now returns 0 for a zero base (Auburn/FastSIMD#10), so the local PowNonNegative workaround is no longer needed. The Log fix in the same bump also removes most of the remaining Minkowski error.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant