crystal icon indicating copy to clipboard operation
crystal copied to clipboard

Floating-point manipulation operations should not allow arbitrary numbers

Open HertzDevil opened this issue 4 years ago • 2 comments
trafficstars

The functions Math.ilogb, logb, ldexp, scalbn, scalbln, frexp, and copysign are only meaningful when their arguments are already the matching floating-point types; it is meaningless to talk about the "exponent" of 42_i32, because it is very clearly a fixed-point number. Thus I propose that we deprecate the restriction-less overloads of these methods and remove them in 2.0. What happens afterwards is that any such calls with incompatible number types will be an error:

module Math
  def ldexp(value : Float32, exp : Int32); end
  def ldexp(value : Float64, exp : Int32); end

  def ldexp(value : Float32, exp : Int); ldexp(value, exp.to_i32); end
  def ldexp(value : Float64, exp : Int); ldexp(value, exp.to_i32); end
end

Math.ldexp(1, 5)        # Error: ambiguous call, implicit cast of 1 matches all of Float32, Float64
Math.ldexp("1".to_i, 5) # Error: no overload matches 'Math.ldexp' with types Int32, Int32

Any necessary floating-point conversions, when desired, must then occur at the call site. (In this example, the exp parameter is not a floating-point value being manipulated, so it may still perform type conversions.)

HertzDevil avatar Jul 08 '21 15:07 HertzDevil

For reference, these methods currently have overloads with unrestricted parameters implicitly casted to float:

https://github.com/crystal-lang/crystal/blob/dd40a2442fa186add8a82b74edb14a90aa1dae05/src/math/math.cr#L602-L604

straight-shoota avatar Jul 09 '21 07:07 straight-shoota

I think this is a bug because calling the function Math.ldexp with a floating-point exponent will return a wrong result:

# Math.ldexp(value, exp) is equivalent to:
value * 2 ** exp

# With an integer exponent the results match:
pp! Math.ldexp(1.2, 4) # => 19.2
pp! 1.2 * 2 ** 4       # => 19.2

# But not with a floating-point exponent:
pp! Math.ldexp(1.2, 4.5) # => 19.2
pp! 1.2 * 2 ** 4.5       # => 27.152900397563425

Math.besselj(order, value) and Math.bessely(order, value) have the same problem:

pp! Math.besselj(4  , 1.2) # => 0.005022666277311586
pp! Math.besselj(4.5, 1.2) # => 0.005022666277311586
# Correct result from https://www.wolframalpha.com/input?i=besselj(4.5,+1.2)
# 0.00179578...

pp! Math.bessely(4  , 1.2) # => -16.68618734525732
pp! Math.bessely(4.5, 1.2) # => -16.68618734525732
# Correct result from https://www.wolframalpha.com/input?i=bessely(4.5,+1.2)
# -40.9739...

cristian-lsdb avatar Jul 13 '22 15:07 cristian-lsdb