Calculate the absolute value of a float without using @abs

I want to make a piece of code compatible with both Zig 0.11.0 and 0.12.0. The following code proves to be a stumbling block:

pub fn abs(v: anytype) @TypeOf(v) {
    return if (@hasDecl(std.math, "fabs")) std.math.fabs(v) else @abs(v);
}

It wouldn’t compile in 0.11.0 because @abs() is missing (it was @fabs() before). I wonder if there’s a way to get the right CPU instruction in there without the use of the builtin. An undocumented internal function, perhaps? If not, I’m just going to use stick a if (v < 0) statement in there.

Could you use the builtins module to check the version of the compiler and then use the right builtin call?

This might not be the best solution for your case, but I came up with this in response to a similar question.

the sign on floats is just the most significant bit.

const bits: u64 = @bitCast(v);  // assuming a 64 bit float.
const mask = (1 << 63) - 1;
const abs_bits = bits & mask;
const abs_v: f64 = @bitCast(abs_bits);

That should basically be a single isstruction. And yo ucan do some comptime magic to get the proper bit size integer.

2 Likes

Can’t do that. This is for a source code translator. The output has to be a single file.

I think I’m just going to go with std.math.sign(v) * v for now. That works when v is a vector.

If you try to use @abs with 0.11, the lexer will complain, so you can’t even try to wrap this inside a comptime block. You need to prevent the lexer from seeing it. I think this would work:

File newAbs.zig:

 pub fn abs(v: anytype) @TypeOf(v){
    return @abs(v);
}

File oldAbs.zig:

pub fn abs(v: anytype) @TypeOf(v){
    return switch(@typeInfo(@TypeOf(v))){
        .Int, .ComptimeInt => std.math.abs(v),
        .Float, .ComptimeFloat => std.math.fabs(v),    
        else => unreachable,
    };
}

File abs.zig:

const builtin = @import("builtin");
pub const abs = if(builtin.zig_version.minor <= 11)
    @import("oldAbs.zig").abs
        else
    @import("newAbs.zig").abs
3 Likes

Are you sure that this works in both 0.12 and 0.11? Under 0.11.0 I get the following error:

newAbs.zig:2:12: error: invalid builtin function: '@abs'
    return @abs(v);

No, not sure. I didn’t download 0.11 to test it. I assumed imports were lazy.

The imports are lazy, but grammar is always checked.

I did not expect the parser to validate the builtin functions (in AstGen.zig).

Holy cow, I found a solution! @max(-v, v) amazingly compiles to the same code as @abs(v) and @fabs(v):

pub fn abs1(n: f32) f32 {
    return @max(-n, n);
}

pub fn abs2(n: f32) f32 {
    return @abs(n);
}

export const exports = [_]*const anyopaque{
    @ptrCast(&abs1),
    @ptrCast(&abs2),
};
.LCPI0_0:
        .long   0x7fffffff
example.abs1:
        vandps  xmm0, xmm0, dword ptr [rip + .LCPI0_0]{1to4}
        ret

exports:
        .quad   example.abs1
        .quad   example.abs1

God bless godbolt :smiley:

6 Likes

cool. I never thought about it. i wonder if there is a peephole pass in llvm for this specifically? There is no neg instruction for floats, so you would think the straight forward gen would be x1 = x0 ^ 0x80000000 then MAXSS. I wonder how it gets from that to the bitmask.