Short math notation, casting, clarity of math expressions

I’m currently struggling to get Zig to agree numerically with a C reference implementation of a science app, so your concerns are relevant. I am working on a C compiler that writes opcodes directly in binary, and it has been agreeing bit-for-bit with Gcc from day one. The point is, this should be simple and obvious to do in a language for Floating Point data types, and yet I am currently struggling. I feel as though widening or narrowing are happening in unexpected ways in my expressions, but I am just guessing.

My hope would be that whatever the datatype is on the RHS of a const assignment would also be the data type assigned to the variable on the LHS, definitely for Floating Point, and that may well already be the case. I have yet to go though my code adding a specific type to the LHS, but that will be my last chance at aligning the numerics. I am at the end of my rope.

Update: I just found the following Zig issue, which seems to imply that it is generally known that FP does not produce IEEE754 controlled results for the general case:

That said, Zig is producing much higher performance executables than highly optimized GCC, so it would be in ZIg’s best interest to tie up the loose ends.

The integer ranges proposal will (probably) mean that for loop ranges have the exact type of their range, instead of usize. So it’s at least possible that we’ll get this.

5 Likes

Just out of interest, do we know how likely it is that ranged integer types make it into the language?

It seems to be one of the proposals that come up a lot in terms of making the language more ergonomic, so to me it’d be quite a high priority inclusion. But it seems like it’s been stalled for a long time now.

1 Like

I’m not really the person to ask. @mlugg has expressed interest in it, which is promising, but it’s not tagged accepted either, so it’s not on the roadmap.

Completing it raises serious semantic questions which would need to be answered. There’s prior art, it’s not the undiscovered country, but it’s tricky to get right.

To be a bit vague about it, I view Zig’s concept of a result location as the saving grace for the proposal. It has the potential to solve a lot of the conundrums, and perhaps, in the process, address the ergonomic issues which the status quo casting rule poses for arithmetic.

4 Likes

Just wanted to share one more related thing I noticed in the language.

When you are using plus/minus with integer types you get an automatic overflow check inserted so it seems that in the same way compiler could just automatically insert @intCast where needed without us having to specify it. Or am I missing something?

The whole point of those explicit casts is to not have potentially dangerous or unintended type conversions automatically inserted by the compiler :slight_smile:

There are currently situations where a type conversion is entirely safe but still requires an explicit cast (which I think the team is aware of and trying to find solutions), and then there’s a grey area where an automatic type cast might be potentially dangerous but very unlikely, and this is probably the point where the team will say “nope”.

2 Likes

Imagine that we cannot use plus operator and instead of writing a + b we had to write @safeAdd(a, b) which does exactly the same thing a + b currently does (inserts an overflow check besides addition).

Doesn’t you sentence then also mean: “The whole point of explicit @safeAdd is to not have potentially dangerous or unintended add operation automatically inserted by the compiler”? And yet we do have such add operation currently.

In other words, if at every place where @intCast is needed compiler automatically inserts checks that value is valid for the new type the code would be safe since it would error out if you make a mistake just like it would error out if you cause an overflow when doing addition.

I think the point of having to use an explicit operation is so that the programmer gains awareness over type changes and decides whether they will use @intCast or something else like @truncate (to throw away the overflowing bits). The compiler can’t really decide for you whether your intent is to convert it and get an error if that isn’t possible or just convert the part of the number that fits into the destination type.

Also these safety checks are only active in safe modes, not in ReleaseFast or ReleaseSmall.
So if @intCast were added automatically it wouldn’t do its job of triggering the programmer to consider what needs to happen at that point in the code, basically once you are done writing, testing and debugging the code, your code should work without those checks (at least for code you aren’t running exclusively in ReleaseSafe once it is considered production code).

7 Likes

I agree that it increases awareness but I am still not completely clear why is it important to increase it for cast and not for plus.

  • @intCast is “safety check + truncate” in debug builds and it just becomes truncate in release builds.
  • + is “safety check + addition” in debug builds and it just becomes addition in release builds.

And also:

  • If I just want truncate without safety check even in debug build I need to know I can use @truncate.
  • If I just want addition without safety check even in debug build I need to know to use +% instead of + (at least on all platforms I know of).

So when I put things like that they seem equivalent and so if + doesn’t need awareness increasing why @intCast does?

1 Like

Maybe superfluous reaction, but you can use 3 flavors too:
+ or +| or +%
depending on your situation.

The difference between casting and addition is that the alternative to @intCast is invisible in the code, whereas + is clearly visible. This is rather dangerous and hard to comprehend for the reader. If I see a function call fun(x) in a code review I want to see if x is being casted to another type or not.

Furthermore the intCast helps you make the right design decisions. And you can rid get most of intCasts if you just use the right types upfront. E.g. If you know you have a positive number that needs to be later converted to i32 then you can use a u31.

6 Likes

Also @intCast isn’t equivalent to @truncate in unsafe release modes. By using @intCast you’re telling the compiler to trust you that the value you’re casting will fit into the result type. The compiler will add a check for this in safe modes, but it won’t in unsafe modes so values that don’t fit into the result type will result in illegal behavior.
@truncate also works for values that don’t fit into the result type, they will just be truncated to n bits. This also happens in unsafe modes, no illegal behavior can occur.
Similiarly, + isn’t necessarily equivalent to +% in unsafe release modes. It can also cause illegal behavior in the same way.

Of course, these operations might still behave like you described, but this isn’t guaranteed and can vary for different targets and odd integer sizes.

This isn’t just about safety checks, it’s also about assumptions the compiler is allowed to make. And Zig forces you to specify those very explicitly. As @IntegratedQuantum has already pointed out, this necessitates every potentially ambiguous operation to be written out by the programmer, be that as a + or as an @intCast. Otherwise you get things like C implicitly converting basically everything to an integer.

2 Likes