Proposal: Better lossy casting for int-to-float

I’d like that too, but the reality of floating point means that int -> float is a completely different beast from float -> int.

When you cast from float to int, the width of the integer does not matter. The value you get will be the same - the only difference is the threshold at which you exceed the integers capacity and trigger illegal behaviour.

// Always == 7
const a: u8 = @trunc(7.5);
const b: u16 = @trunc(7.5);
const c: u32 = @trunc(7.5);

The opposite is true for floating point: you can’t trigger illegal behaviour, because it can map any integer value to a floating point value. However, the width of the float does matter.

// Not equal
const a: f64 = 1.0 / @as(f32, @floatFromInt(3));
const b: f64 = 1.0 / @as(f64, @floatFromInt(3));

They are different operations, with different problems. It is logical for Zig to provide different solutions to them.

This really should be:

const a: f64 = 1.0 / @floatFromInt(3);
const b: f64 = 1.0 / @floatFromInt(3);

What is confusing is mixing different floating point precisions or integer sizes in the same arithmetic expression.

If you specifiy that once at the beginning of the line it’s much clearer

I’m still failing to see your point. The width of the float matters for precision. Yet again, how does that relate to my point at all?

Explain how, semantically, these are different?

const a: u8 = @intFromFloat(7.5); // equivalent to @trunc
const b: f32 = @floatFromInt(30);

I’d also like to point out this

@floatFromInt(int: anytype) anytype // current float cast function
@float(int: anytype) anytype        // proposed shortening of name???

Again, I’m not talking about changing the way casting works, or hiding any semantics or anything like that.

I think you’re confused on what the point is as you’re arguing a completely different thing than what the topic is about. Unless you’re intentionally trolling, in which case I suppose I’m the fool.

1 Like

Oh, I think I see the problem…

You are under the impression that @intFromFloat() was replaced with @trunc because it’s shorter and more ergonomic?

That’s not why they did it. It was removed because it became redundant after the improvements to @trunc and the other builtins. The fact that it is shorter and less ambiguous is a happy bonus, but was not what drove the change.

Commit that deprecated @intFromFloat():
https://codeberg.org/ziglang/zig/commit/75457202d4d808da72015bf1c942255883236416

… which references the pull request that made it redundant:
https://codeberg.org/ziglang/zig/pulls/30906

Sorry for not picking up on that sooner.

@floatFromInt() is not redundant. I don’t see any other built-ins with similar enough behaviour to potentially make it redundant, either. @round() is close, but it rounds away from zero, rather than the “round to nearest, ties to even” behaviour of @floatFromInt().

Renaming it could still be a good idea, though just @float() on it’s own is not precise enough:

@floatFromInt(int: anytype) anytype
@floatCast(value: anytype) anytype
@float(a: anytype) anytype   // Which one?

Perhaps you could combine the two? Their behaviour is pretty similar… but I think it’s better to have them separate, so that the programmer gets a compiler error when they think they’re working with int, but they’re actually working with float (or vice-versa).

Relevant Zen: Communicate intent precisely.

@intFromFloat(float: anytype) anytype
Deprecated. Equivalent to [@trunc](https://ziglang.org/documentation/0.16.0/#trunc).

With all this context that is already here on the thread, hopefully the picture is clear. @intFromFloat was deprecated because due to changes with Sema (as seen in the PR you shared) the other functions can naturally ascertain what return type is expected (probably due to changes specifically involving result location semantics?).

You’ll see that I specifically mentioned that @trunc and the others being shorter/more ergonomic was probably not the goal, but a happy side-effect of these changes. That’s not relevant to the point here. The point is that @intFromFloat is deprecated, and now we have these short-and-sweet replacements. Additionally, @trunc and @intFromFloat are equivalent in behavior.

What I proposed, and have repeatedly mentioned, is that @floatFromInt should receive the same treatment semantically. I also pointed out that @float might not be a descriptive enough name, and something more descriptive but just as ergonomic would be nice.

This would deprecate or replace @floatFromInt, as they would be identical in behavior.

@float would only accept an integer (or vector of integers), just like how @trunc, @round, etc only accept a float or vector of floats as input. You cannot use @round to convert an int to a float, putting anything accept a float/vector of floats as the argument causes a compile error.

anytype doesn’t mean literally ANY type in these contexts, it’s just language semantics to allow the function to accept any size of a float (or vector of floats).

1 Like