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

Add NaN, inf, is_nan and is_infinite #407

Merged
merged 5 commits into from
Mar 25, 2024
Merged

Conversation

Bzero
Copy link
Contributor

@Bzero Bzero commented Mar 23, 2024

This pull request introduces the 'NaN' keyword such that such that NaN values may be used directly in numbat.

As far as I am aware, NaN can currently only be accessed by calling an FFI function that returns NaN (e.g. ln(-1) or sqrt(-1)) but there is no direct way to declare a NaN value. With this change it becomes possible to use NaN directly, e.g:

>>> let foo = NaN

  let foo: Scalar = NaN

To check if a value is NaN, a is_nan() function is added (see also #144).

I stumbled over this limitation when trying to implement a function with a limited domain that can handle input outside of the domain in a meaningful way in numbat. Using the NaN keyword it is possible to write:

fn triangular_number(n: Scalar) -> Scalar=
	if n>=0
	then n*(n+1)/2
	else NaN

@sharkdp
Copy link
Owner

sharkdp commented Mar 23, 2024

Thank you. I thought about this in the past, but didn't get around to implementing it — thanks.

I'm not sure if we really need NaN as a special token in the language, though? Could we perhaps define it in the prelude? For now, we could use something like ln(-1) and later we could maybe replace that by something cleaner. Like a from_bits(0xFFC00000) call, where from_bits would act like a bitlevel cast from int=>float.

I stumbled over this limitation when trying to implement a function with a limited domain that can handle input outside of the domain in a meaningful way in numbat.

Note that you can use error("…") for those cases:

fn triangular_number(n: Scalar) -> Scalar=
	if n>=0
	then n*(n+1)/2
	else error("n needs to be non-negative")

@Bzero
Copy link
Contributor Author

Bzero commented Mar 23, 2024

I'm not sure if we really need NaN as a special token in the language, though? Could we perhaps define it in the prelude?

I figured having 'NaN' as a reserved keyword would make it available even with the '--no-prelude' option (just as any other value) and it would protect it from being user-redefined which could lead to unexpected behavior. On the other hand also π and all other constants can be overwritten so I am happy to turn it into a constant defined in prelude.

For now, we could use something like ln(-1) and later we could maybe replace that by something cleaner. Like a from_bits(0xFFC00000) call, where from_bits would act like a bitlevel cast from int=>float.

Sounds good to me!

@Bzero
Copy link
Contributor Author

Bzero commented Mar 23, 2024

Note that you can use error("…") for those cases:

fn triangular_number(n: Scalar) -> Scalar=
	if n>=0
	then n*(n+1)/2
	else error("n needs to be non-negative")

If I understood it correctly that currently only works for functions with Scalar return values, right?

@triallax
Copy link
Contributor

Do we also want to add "constants" for positive and negative infinity as well?

@Bzero
Copy link
Contributor Author

Bzero commented Mar 24, 2024

I think that could be handy. Positive infinity inf should be enough though as one can just use -inf for negative infinity.

On that occasion it would probably make sense to add f64::is_infinite() to the FFI.

@sharkdp
Copy link
Owner

sharkdp commented Mar 24, 2024

I figured having 'NaN' as a reserved keyword would make it available even with the '--no-prelude' option (just as any other value) and it would protect it from being user-redefined which could lead to unexpected behavior.

I thought about this some more and I think I actually agree with you on this. Would you mind reverting that change again and making NaN/inf a proper builtin? (Sorry)

If I understood it correctly that currently only works for functions with Scalar return values, right?

Yes. I thought about adding something like

fn error_d<D>(x: D, msg: String) -> D = error(msg) * x

which could be used like 4.3 km + error_d(m, "message"). That would only be a workaround solution, though. And it only works if you have an actual value of type D around (which is maybe not the case in all generic function scenarios). The proper solution would be to extend the type system to allow for ! return types (functions that never return). That might actually not be too hard, since the typing rules are quite simple (everything combined with ! yields !)

@Bzero
Copy link
Contributor Author

Bzero commented Mar 24, 2024

I thought about this some more and I think I actually agree with you on this. Would you mind reverting that change again and making NaN/inf a proper builtin? (Sorry)

Of course not! That's on me, I was changing it a bit too quickly before waiting for your reply.

@@ -1724,6 +1730,12 @@ mod tests {
should_fail(&["0x1.2", "0b1.0", "0o1.0", "0x.1", "0b.0", "0o.1"]);
}

#[test]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had some trouble writing a proper test for NaN since NAN == NAN is false.

Copy link
Contributor

Choose a reason for hiding this comment

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

You could add a test for this somewhere in the interpreter tests, and check that NaN == NaN evaluates to false in Numbat.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is a test in the interpreter tests already which works fine but I thought it might be good to have a parser test too?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I missed that, but we can have something like NaN != NaN as a test too I suppose.

Copy link
Owner

Choose a reason for hiding this comment

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

I would think we're good with the inf test here. The code for NaN is the same.

@sharkdp sharkdp changed the title Direct access to NaN Add NaN, inf, is_nan and is_infinite Mar 25, 2024
@sharkdp sharkdp merged commit 8808808 into sharkdp:master Mar 25, 2024
15 checks passed
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.

None yet

3 participants