Skip to content

Disallow NaN and inf for comptime_float #21205

@ianprime0509

Description

@ianprime0509

Accepted Proposal


The motivation behind this proposal is the analysis done in #21198 (comment), with the goal of defining exactly how comptime_float should behave given conflicting implementation details and documentation.

Background

The language reference has this to say about comptime_float:

Float literals have type comptime_float which is guaranteed to have the same precision and operations of the largest other floating point type, which is f128.

As explained in #2794 (comment), one reason for comptime_float to exist rather than just defining all floating-point literals to be comptime-known f128s is that comptime_float may implicitly cast to other floating point types even if precision is lost. #5780 (comment) also solidified the connection between the implementation of comptime_float and the capabilities of f128 by reaffirming the above quote from the langref.

Despite this, there are some other differences in today's Zig between comptime_float and f128 which are not limited to implicit casting behavior (test remarks below are as of Zig 0.14.0-dev.1304+7d54c62c8):

const std = @import("std");
const expect = std.testing.expect;

test {
    // https://github.com/ziglang/zig/issues/21198
    try expect(0.0 / 0.0 == 0.0);
    try expect(std.math.isNan(@as(f128, 0.0) / @as(f128, 0.0)));

    // try expect(std.math.isInf(@as(f128, 1.0 / 0.0))); // error: division by zero here causes undefined behavior
    try expect(std.math.isInf(@as(f128, 1.0) / @as(f128, 0.0)));

    try expect(std.math.isPositiveZero(@as(f128, -0.0 / 1.0)));
    try expect(std.math.isNegativeZero(@as(f128, -0.0) / @as(f128, 1.0)));
}

The first two sets of discrepancies are included in the current compiler test suite (0.0 / 0.0, comptime_float / 0.0 error), which is why this is a proposal rather than a bug fix; evidently, some other behavior was intended at some point.

Proposed behavior

All arithmetic operations with comptime_float should behave as if they were done with comptime-known f128. That is, the test above should be updated to the following:

const std = @import("std");
const expect = std.testing.expect;

test {
    try expect(std.math.isNan(0.0 / 0.0));
    try expect(std.math.isNan(@as(f128, 0.0) / @as(f128, 0.0)));

    try expect(std.math.isInf(1.0 / 0.0));
    try expect(std.math.isInf(@as(f128, 1.0) / @as(f128, 0.0)));

    try expect(std.math.isNegativeZero(-0.0 / 1.0));
    try expect(std.math.isNegativeZero(@as(f128, -0.0) / @as(f128, 1.0)));
}

As the updated test suggests, accepting this proposal could also allow the float functions std.math (such as std.math.isInf) to be expanded to work with comptime_float by internally casting to/from f128, without concern that the semantics might be different. The formatted float printing logic already does this today:

// comptime_float internally is a f128; this preserves precision.
comptime_float => @as(f128, v_),

Alternatives and follow-up changes

Signaling NaNs

This proposal makes no comment on the behavior of "signaling NaN", since as far as I can tell, Zig is currently lacking any builtins or functions to interact with the floating point environment (and frankly I lack the expertise to make an informed proposal on what such support should look like). If this proposal is accepted, then any future proposal to clarify or expand the behavior of signaling NaNs in Zig should comment on the behavior of signaling NaNs at comptime, and the behavior of comptime-known f128 and comptime_float should be identical.

Restricted comptime_float

The existing behavior of treating 1.0 / 0.0 as a compile error suggests that comptime_float may have originally been intended as not just f128, but f128 with no infinities or NaNs. If this is true, and if this still intended going forward, it will be necessary to clarify and fix implementation of comptime_float in that direction, and document it as the expected behavior. For example, as it is now, despite 1.0 / 0.0 being banned, it is trivial to create comptime_float infinities and NaNs by implicitly casting from f128 or another float type.

I can't think of any reason for 0.0 / 0.0 == 0.0, though; in a universe where comptime_float does not include infinities or NaNs, 0.0 / 0.0 should be a compile error just like 1.0 / 0.0.

std.math support for comptime_float

As mentioned in the proposed behavior section, if this is accepted and implemented, every std.math function which supports f128 could easily be updated to support comptime_float. For example, comptime_float NaNs could be produced using std.math.nan(comptime_float).

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedThis proposal is planned.proposalThis issue suggests modifications. If it also has the "accepted" label then it is planned.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions