Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default rounding mode for primitive floating-point operations #15192

Open
HertzDevil opened this issue Nov 14, 2024 · 3 comments
Open

Default rounding mode for primitive floating-point operations #15192

HertzDevil opened this issue Nov 14, 2024 · 3 comments

Comments

@HertzDevil
Copy link
Contributor

HertzDevil commented Nov 14, 2024

The default rounding mode for Number#round is TIES_EVEN since #10508; it rounds 0.5 to 0.0, rounds 1.5 to 2.0, rounds 2.5 also to 2.0, and rounds 3.5 to 4.0. #11097 added a way to change this default but it was rejected.

There is, however, a closely related concept of a default rounding mode for primitive floating-point operations other than #round, being applied to the mantissa bits rather than the integral part when that operation's result is inexact. This is defined in Section 4.3, "Rounding-direction attributes", of IEEE 754-2008. The C interface for this is fegetround and fesetround, plus the four rounding mode constants:

lib LibC
  {% if flag?(:win32) %}
    FE_TONEAREST  = 0x00000000
    FE_UPWARD     = 0x00000200
    FE_DOWNWARD   = 0x00000100
    FE_TOWARDZERO = 0x00000300
  {% else %}
    FE_TONEAREST  = 0x00000000
    FE_UPWARD     = 0x00000800
    FE_DOWNWARD   = 0x00000400
    FE_TOWARDZERO = 0x00000C00
  {% end %}

  fun fegetround : Int
  fun fesetround(round : Int) : Int
end

{LibC::FE_TONEAREST, LibC::FE_UPWARD, LibC::FE_DOWNWARD, LibC::FE_TOWARDZERO}.each do |round|
  LibC.fesetround(round)
  x = 3.0
  p [1.0 / x, -1.0 / x]
end

This prints:

[0.3333333333333333, -0.3333333333333333]
[0.33333333333333337, -0.3333333333333333]
[0.3333333333333333, -0.33333333333333337]
[0.3333333333333333, -0.3333333333333333]

For Crystal to fully conform to IEEE 754, we could expose this functionality as Float.rounding_mode : Number::RoundingMode and .rounding_mode=(Number::RoundingMode). Personally I'm not aware of this part of the standard's relevance in real code.

Note the use of x as a separate variable in the above example. LLVM assumes round-to-nearest and it is technically undefined behavior to simply change the rounding mode like that; it is free to replace 1.0 / 3.0 with the round-to-nearest result as an optimization. Instead, a series of constrained floating-point intrinsics are available. This requires compiler support since the rounding mode is taken as an LLVM metadata node and no Crystal expression evaluates to one.

@Sija
Copy link
Contributor

Sija commented Nov 14, 2024

For Crystal to fully conform to IEEE 754, we could expose this functionality as Float.rounding_mode : Number::RoundingMode and .rounding_mode=(Number::RoundingMode).

@HertzDevil this applies to BigDecimal as well.

@oprypin

This comment was marked as resolved.

@HertzDevil HertzDevil changed the title Default rounding mode for floating-point operations Default rounding mode for primitive floating-point operations Nov 15, 2024
@HertzDevil
Copy link
Contributor Author

HertzDevil commented Nov 15, 2024

this applies to BigDecimal as well

Updated to clarify this issue is for primitive floats only. Note that neither BigFloat nor BigDecimal claims IEEE 754 conformance (for the former we need #11410)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants