Skip to content

Implement num_traits::Float trait for Df64 - #15

Merged
mwallerb merged 11 commits into
tuwien-cms:mainlinefrom
sakuraba07:implement-num-traits-float
Dec 13, 2025
Merged

Implement num_traits::Float trait for Df64#15
mwallerb merged 11 commits into
tuwien-cms:mainlinefrom
sakuraba07:implement-num-traits-float

Conversation

@sakuraba07

Copy link
Copy Markdown
Contributor

This PR implements #2.

Summary

This PR implements the num_traits::Float trait for Df64, enabling compatibility with generic numeric code that relies on this standard trait. Additionally, the NumCast trait is implemented to support generic numeric type conversions.

Problem

As requested in #2, the Df64 type lacked the num_traits::Float trait implementation, which prevented access to essential floating-point inspection methods like is_infinite(), epsilon(), is_nan(), and classify(). This limited interoperability with Rust's numeric ecosystem and generic code that depends on these trait bounds.

Solution

traits.rs - Float trait implementation

  • Implemented all 58 required num_traits::Float methods for Df64
  • Adopted a delegation design pattern for consistency:
    • Constants (8 methods): Return existing Df64 constants (NAN, INFINITY, EPSILON, etc.)
    • Checks (6 methods): Delegate to checks module functions
    • Basic operations (5 methods): Delegate to Signed trait
    • Rounding (7 methods): Delegate to ComplexField trait
    • Comparison (3 methods): Delegate to RealField trait
    • Exponential/logarithmic (11 methods): Delegate to ComplexField trait
    • Trigonometric (7 methods): Delegate to ComplexField trait
    • Hyperbolic (6 methods): Delegate to ComplexField trait
    • Conversion (2 methods): to_degrees() and to_radians() implemented directly using PI constant
    • TODO (2 methods): cbrt() and integer_decode() marked as todo!() with explanatory comments
  • Updated 4 locations to use explicit type syntax <Df64 as From<f64>>::from() instead of Df64::from() for consistency
  • Added 5 new tests:
    • test_float_constants() - Verify Float constant methods
    • test_float_neg_zero() - Verify neg_zero() implementation
    • test_float_checks() - Verify classification methods (is_nan(), is_infinite(), etc.)
    • test_float_classify() - Verify classify() method
    • test_float_conversions() - Verify to_degrees() and to_radians()

convert.rs - NumCast trait implementation

  • Implemented num_traits::NumCast trait to enable generic numeric type conversions
  • Leverages existing FromPrimitive::from_f64() implementation

Changes

  • src/traits.rs:
    • Add complete Float trait implementation (+326 lines)
    • Add 5 focused tests for Float trait methods
    • Update 4 locations to use explicit From<f64> syntax
  • src/convert.rs:
    • Add NumCast trait implementation (+6 lines)

Testing

  • All existing tests pass
  • New tests in traits.rs verify:
    • Float constant methods (nan(), infinity(), epsilon(), etc.)
    • Float classification methods (is_nan(), is_infinite(), is_finite(), etc.)
    • Special value handling (neg_zero(), classify())
    • Angle conversions (to_degrees(), to_radians())

Future Work

  • Implement ComplexField::cbrt() and subsequently Float::cbrt() using Newton iteration or other stable algorithms
  • Design and implement Df64::integer_decode() with appropriate representation for double-double mantissa (possibly using extended mantissa format or custom representation)

Implement the num_traits::Float trait to provide standard floating point
operations interface for Df64 type.

Changes:
- traits.rs: Add Float trait implementation with 58 methods using delegation
  design pattern. Add 5 new tests for Float trait methods. Update 4 locations
  to use explicit From<f64> type syntax.
- convert.rs: Add NumCast trait implementation.

Note: cbrt() and integer_decode() are marked as todo!() due to requiring
additional design for double-double representation.

Closes tuwien-cms#2
@shinaoka

Copy link
Copy Markdown
Collaborator

Thank you for the contribution. I have a few comments:

  • Float trait is more basic than ComplexField trait. It is better not to use ComplexField to define Float trait.
  • Could you add unit tests for the new trait?

This reverts commit ee2aba0.

Rolling back the previous Float trait implementation to address PR review
feedback and implement a better design approach.

Reasons for revert:
- Float trait should not depend on ComplexField trait (more basic trait
  should not depend on more complex trait)
- Need to refactor implementation to use only basic operations

Will re-implement Float trait without ComplexField dependency in the next
commit.

Related to PR tuwien-cms#15
Implement the num_traits::Float trait to provide standard floating-point
operations for Df64 without depending on higher-level traits.

Changes: - traits.rs: Add Float trait implementation with 58 methods
delegating directly to low-level modules (checks, round, exp, circular,
hyperbolic, funcs, arith, roots, consts) instead of ComplexField -
convert.rs: Add NumCast trait implementation to support generic numeric
type conversions

Design: - Float and ComplexField are now independent, both delegating to
the same low-level modules - All methods use #[inline(always)] for
performance - cbrt() and integer_decode() marked as todo!() pending
double-double specific design

Addresses PR tuwien-cms#15 review feedback from @shinaoka
Add 56 unit tests covering all 58 methods of the num_traits::Float trait
implementation for Df64, ensuring API correctness and mathematical
properties.

Test coverage: - Constants (8 methods): nan, infinity, epsilon, min/max
values - Classification (7 methods): is_nan, is_infinite, is_finite,
is_normal, classify - Basic arithmetic (3 methods): abs, signum, recip -
Rounding (5 methods): floor, ceil, round, trunc, fract - Comparison (5
methods): min, max, abs_sub, mul_add, copysign - Powers & roots (3
methods): powi, powf, sqrt, hypot - Exponential & logarithmic (8
methods): exp, exp2, ln, log, log2, log10, exp_m1, ln_1p - Trigonometric
(8 methods): sin, cos, tan, asin, acos, atan, atan2, sin_cos -
Hyperbolic (6 methods): sinh, cosh, tanh, asinh, acosh, atanh - Angle
conversion (2 methods): to_degrees, to_radians - Not yet implemented (2
methods): cbrt, integer_decode (panic tests)

Test characteristics: - Each test covers positive, negative, and edge
cases (zero, infinity, NaN) - Validates mathematical properties (e.g.,
odd/even functions, identities) - Uses high precision tolerance (1e-30
typical, relaxed to 1e-20~1e-29 where needed) - Special validations:
tan(π/2) divergence, atan(∞) convergence

All 94 tests pass (57 Float trait tests + 37 existing implementation
tests).

Related to PR tuwien-cms#15
@sakuraba07

Copy link
Copy Markdown
Contributor Author

@shinaoka Thank you for the review. I've addressed both of your comments:

1. Removed ComplexField dependency

The Float trait implementation now delegates directly to low-level modules instead of ComplexField:

  • Before: Float::sin()ComplexField::sin()circular::sin()
  • After: Float::sin()circular::sin()

See commit: b0db211

2. Added comprehensive unit tests

Added 56 unit tests covering all 58 Float trait methods:

  • Each test validates basic functionality, edge cases (zero, infinity, NaN), and mathematical properties
  • Tests cover positive and negative values, odd/even function properties, special cases like tan(π/2) divergence and atan(∞) convergence
  • High precision validation (1e-30 typical tolerance, relaxed to 1e-20~1e-29 where needed)

All 94 tests pass (57 new Float trait tests + 37 existing tests).

See commit: 2163c11

Summary of changes

  • traits.rs: Refactored Float implementation to be independent of ComplexField (+871 lines with tests)
  • convert.rs: Added NumCast trait implementation
  • All methods use #[inline(always)] for performance
  • cbrt() and integer_decode() remain as todo!() (will be addressed in future work)

Ready for re-review. Please let me know if you have any other concerns.

Integrate +0.0/-0.0 behavior tests into existing Float trait tests and
add dedicated tests for signed zero arithmetic and copysign operations.
@sakuraba07

Copy link
Copy Markdown
Contributor Author

@shinaoka I've added two more commits addressing additional issues found during review:

1. Fix abs_sub implementation (6fd2583)

The previous implementation returned abs(self - other), but the correct behavior per num_traits::Float specification is max(self - other, 0). This has been fixed along with the corresponding tests.

2. Add signed zero behavior tests (070a5b1)

Added comprehensive tests for +0.0 and -0.0 behavior across Float trait methods. These tests:

  • Document current implementation behavior for signed zeros
  • Cover arithmetic, comparison, trigonometric, and other operations
  • Note where behavior differs from IEEE 754 (e.g., 1/0 returns NaN instead of Infinity)

The signed zero tests are integrated into existing test functions where appropriate, with dedicated tests for arithmetic operations and copysign.

All 94 tests pass.

Ready for re-review.

@mwallerb mwallerb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Looks good, but the tests need work.

  • Please do not use check against an absolute threshold. Most functions satisfy a relative accuracy goal
  • Please do not use magic numbers (1e-29 etc.) in the threshold. Use a small multiple of the machine epsilon
  • Please reuse the functions in test_utils rather rolling your own tests whenever useful. Otherwise, please make use of the macros inside the approx crate, in particular approx::assert_relative_eq and assert_ulps_eq.

In general, most of the tests seem to duplicate the unit tests in the respective modules, for example, exp2 and so forth. Why test again and in a separate place?

Comment thread src/traits.rs
Comment on lines +447 to +454
#[inline(always)]
fn mul_add(self, a: Self, b: Self) -> Self {
// There are two requirements that one has with fma: (1) it must be
// accurate without intermediate rounding and (2) it must be at least
// as fast as (a*b)+c. We have no way of satisfying both, so we go
// for performance.
(self * a) + b
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This duplicates an existing method, please unify.

Comment thread src/traits.rs
Comment on lines +611 to +619
#[inline(always)]
fn to_degrees(self) -> Self {
self * consts::ONE_OVER_PI * <Df64 as From<f64>>::from(180.0)
}

#[inline(always)]
fn to_radians(self) -> Self {
self * consts::PI / <Df64 as From<f64>>::from(180.0)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use precomputed values for PI/180 and its reciprocal here.

…an conversion

- Add mul_add_qq function to arith.rs for fused multiply-add on Df64
- Add RADIANS_PER_DEGREE and DEGREES_PER_RADIAN constants to consts.rs
- Update Float::mul_add and ComplexField::mul_add to delegate to arith::mul_add_qq
- Update to_degrees/to_radians to use precomputed constants instead of runtime computation

Addresses PR review feedback from mwallerb
…agic numbers

- Replace absolute thresholds (1e-30, 1e-29, etc.) with assert_ulps_eq! macro
- Use Df64::EPSILON-based comparisons for near-zero checks
- Tests now use relative precision based on machine epsilon
- Add comments clarifying that detailed precision tests are in respective modules
- Float trait tests focus on verifying correct delegation to low-level modules

Addresses PR review feedback from mwallerb regarding test quality
   - Add is_subnormal() method delegating to checks::is_subnormal() -
   Add clamp() method delegating to funcs::clamp() - Add corresponding
   unit tests
   - Fix Basic arithmetic comment: 5 -> 3 methods - Fix Trigonometric
   functions comment: 7 -> 8 methods - Fix test section name: "special
   constants" -> "not yet implemented" - Move test_float_powi and
   test_float_powf to exponential section
@sakuraba07

Copy link
Copy Markdown
Contributor Author

@mwallerb Thank you for the detailed review. I've addressed all your feedback in the following commits:

1. Test improvements (1c4a7cb)

  • Replaced all absolute thresholds (1e-30, 1e-29, etc.) with assert_ulps_eq! macro from the approx crate
  • Use Df64::EPSILON-based comparisons for near-zero checks
  • Added comments clarifying that Float trait tests focus on verifying correct delegation, while detailed precision tests remain in their respective modules

Regarding the test duplication concern: you're right that some tests appear similar to those in the underlying modules. The Float trait tests are intentionally minimal now - they verify that delegation works correctly rather than re-testing mathematical accuracy.

2. Unified mul_add implementation (a1dcb0c)

  • Added mul_add_qq function to arith.rs
  • Both Float::mul_add and ComplexField::mul_add now delegate to this shared implementation

3. Precomputed constants for angle conversion (a1dcb0c)

  • Added RADIANS_PER_DEGREE and DEGREES_PER_RADIAN to consts.rs
  • to_degrees() and to_radians() now use these precomputed values instead of runtime computation

Additional improvements

  • 4f5b4c9: Implemented is_subnormal() and clamp() methods for Float trait
  • b802248: Fixed method count comments and reorganized test sections

All 96 tests pass.

Ready for re-review. Please let me know if there are any other concerns.

@shinaoka

Copy link
Copy Markdown
Collaborator

@mwallerb
Ready to merge?

@mwallerb
mwallerb merged commit 1193025 into tuwien-cms:mainline Dec 13, 2025
2 checks passed
@sakuraba07
sakuraba07 deleted the implement-num-traits-float branch December 15, 2025 04:07
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.

3 participants