Creating and modifying an array of random floats

I’d like to create an array of 10 floats. Populate them with random numbers (from 0 → < 1.0), then do some computation to mutate each one, and finally print out all ten of them. Here’s what I’ve got so far:

const std = @import("std");
const print = std.debug.print;

pub fn main() void {
    // An array of 10 floats initialized to 0.
    const arr = [_]f64{0} ** 10;

    // Populate `arr` with random numbers.
    var i: u32 = 0;
    while (i < arr.len) {
        arr[i] = std.Random.float();
        i += 1;
    }

    // Iterate over `arr`, modifying each elem as I go.
    i = 0;
    while (i < arr.len) {
        arr[i] *= 10;
        i += 1;
    }

    // Finally, print them all out.
    for (arr) |x| {
        print("{}\n", .{x});
    }
}

How to I properly call std.Random.float()? zig tells me I must pass 2 args, but I’m not sure what to put in that function call.

Can I modify the elements of arr in-place like that?

Any other tips most appreciated!

BTW, the zig error messages have been very helpful so far!

Here you go :

const std = @import("std");
const Random = std.Random;

pub fn main() !void {
    const bytes: [32]u8 = @splat(0);
    var cprng: Random.DefaultCsprng = .init(bytes);
    const random = cprng.random();

    var arr = [_]f64{0} ** 10;
    var i: usize = 0;

    while (i < arr.len) {
        arr[i] = random.float(f64);
        i += 1;
    }

    i = 0;
    while (i < arr.len) {
        arr[i] *= 10;
        i += 1;
    }

    for (arr) |x| {
        std.debug.print("{}\n", .{x});
    }
}

std.Random is the interface you need an instance of a random number generator, and get the interface out of it :slight_smile:

pollivie@f6r2s6 ~/w/temp> zig run main.zig
4.86433374583791
0.8760484478559015
0.04034856847413824
4.694859320206208
2.7575160374793226
6.857916952215745
5.92937980494689
2.8752650233199772
8.366371261377683
4.1452296677398595
1 Like

std.Random is an interface, you need to create an implementation then get the interface from it.

var prng = prng: {
    var seed: u64 = undefined;
    // syscall to get random seed.
    try std.posix.getrandom(std.mem.asBytes(&seed));
    // syscalls are slow, so implementations that calculate it in userspace are faster
    break :prng std.Random.DefaultPrng.init(seed);
};
// get interface
const rand = prng.random();

// the first parameter is itself, passed implicitly by zig.
const random_float = rand.float(f64);
1 Like

a hardcoded seed will give deterministic results, meaning it will be the same every time.

@pierrelgol also used a cryptographic rng implementation which is more secure but generally slower than a non cryptographic implementation that I showed.

the std.posix.getrandom is cryptographically secure.

if you are on master the above function doesnt exist anymore, instead you have io.random and io.randomSecure. you can get a premade io (and other goodies) via giving main a prameter of type std.process.Init.

1 Like

Ah! Wow. Good nuggets in there. Thank you! Aside from using Random:

  • I’d thought that const arr = [_]f64{0} ** 10; would mean that arr refers only to that array (and no other array), but that I could change elements within that array. It appears I was wrong: if you want to modify elements in an array, the array must be var arr = [_]f64{0} ** 10; instead of const.

  • That @splat built-in is handy. I see that const bytes: [32]u8 = @splat(0); results in a 32-element array of zeros.

As for making the random number:

  • Why do you say that std.Random is an interface? The docs for Random say it’s a struct. How can I tell that it’s an interface?

  • I’m somewhat familiar with OO languages. To make an instance of an interface, you create a class that implements the interface, then instantiate that class (make an instance of it). But Zig is not OO, correct? Is var cprng: Random.DefaultCsprng = .init(bytes); making an instance of an object that implements the Random interface? How does it work, and what does the .init(bytes) syntax mean?

There are no keywords or help to make interface, so this is a spiritual interface, in the sense that you can have a type expose that “interface” if you look at what csprng.random rives you it’s a std.Random which is the “interface” with all the utilities.

Thanks, @vulpesx . When I try that though, zig errors out, telling me:

main.zig:10:18: error: root source file struct 'posix' has no member named 'getrandom'
    try std.posix.getrandom(std.mem.asBytes(&seed));
        ~~~~~~~~~^~~~~~~~~~
/home/john/opt/zig-x86_64-linux-0.16.0-dev.2565+684032671/lib/std/posix.zig:1:1: note: struct declared here
//! POSIX API layer.

My main.zig file is:

const std = @import("std");
const Random = std.Random;
const print = std.debug.print;

pub fn main() void {

    var prng = prng: {
        var seed: u64 = undefined;
        // syscall to get random seed.
        try std.posix.getrandom(std.mem.asBytes(&seed));
        // syscalls are slow, so implementations that calculate it in userspace are faster
        break :prng std.Random.DefaultPrng.init(seed);
    };
    
    // get interface
    const rand = prng.random();

    // An array of 10 floats initialized to 0.
    var arr = [_]f64{0} ** 10;

    // Populate `arr` with random numbers.
    var i: usize = 0;
    while (i < arr.len) {
        arr[i] = rand.float(f64);
        i += 1;
    }

    // Iterate over `arr`, modifying each elem as I go.
    i = 0;
    while (i < arr.len) {
        arr[i] *= 10;
        i += 1;
    }

    // Finally, print them all out.
    for (arr) |x| {
        print("{}\n", .{x});
    }
}

Am I missing an @import?

I’m using:

$ zig version
0.16.0-dev.2565+684032671

Oof! And I just realized you also wrote in your reply to me:

if you are on master the above function doesnt exist anymore, instead you have io.random and io.randomSecure. you can get a premade io (and other goodies) via giving main a prameter of type std.process.Init.

Trying that…

I see in the lang ref there’s an example that goes like:

pub fn main(init: std.process.Init) !void {
    try std.Io.File.stdout().writeStreamingAll(init.io, "Hello, World!\n");
}

Can you please show me how to get a pre-made io?

zig has no language level runtime interface feature, instead you just store function pointers and usually a type erased pointer to implementation data. You can see both of those things in the docs you linked.

.init(bytes) is just calling the init function from an infered type, which you can see is Random.DefaultCsprng. That function returns an instance of the type.

to get the interface you call another function which returns an instance of the Random struct with pointers to the instance you just created and the functions it needs to implement for the interface.

I did mention on master (0.16.0-dev…) that the function was removed and that it has 2 replacements io.random and io.randomSecure. Io here is yet another interface, as you can imagine it deals with any i/o operations which is basically all OS interactions including getting entropy. It also does async!!!

you can get an Io instance very easily on master

const std = @import("std");
const Init = std.process.Init;
pub fn main(init: Init) !void {
    _ = init.io;
    // there are also other goodies
   init.gpa;
   init.arena;
   init.environ_map; // premade hashmap of environment
   // you can use `Init.Minimal` as the parameter type if you only want the following
   init.minimal.args;
   init.minimal.environ; // raw unprocessed environment
}

I would recomend sticking with 0.15.2, master is going through very big changes due to the introduction of the Io interface i mentioned.


Other examples of interfaces are std.mem.Allocator which is very similar, the difference being it stores a pointer to a struct of function pointers (called a vtable), instead of storing them directly. That makes the interface struct smaller, and the const vtable pointer is good for optimisations.

There is also another style of interface you can see with std.Io.Reader and std.Io.Writer. First you’ll notice it has its own state in addition to the vtable, then you might notice it doesn’t contain any pointer to the implementation state.

That kind of interface is called “intrusive”, because the implementations are supposed to contain the interface struct as a field in their own type. They do some pointer math to convert a pointer to that field into a pointer to their own state, this means you must never copy the interface out of the field otherwise you will get undefined behaviour.

thats a big footgun, but there is work to catch it with a safety check. There is also the big benefit that its easy to extend the interface, remember the interface has its own state that may need to be updated, if it didnt have their own state then being “intrusive” would have no benefits.

1 Like

If I’m still using zig 0.16.0-dev, and I want to use that init.io.random that you mentioned, can you please show me where I’m going wrong here? I see from the docs for io.random that it wants me to pass in a []u8, so I made the same one that @pierrelgol provided above:

const std = @import("std");
const Random = std.Random;
const print = std.debug.print;

pub fn main(init: std.process.Init) void {
    const bytes: [32]u8 = @splat(0);
    const rand = init.io.random(bytes);

    var arr = [_]f64{0} ** 10;

    var i: usize = 0;
    while (i < arr.len) {
        arr[i] = rand.float(f64);
        i += 1;
    }

    for (arr) |x| {
        print("{}\n", .{x});
    }
}

bytes is an array, but the function wants a slice (ptr + length). You can coerce an array to a slice with just &bytes.

The next issue is it needs a mutable (non const) slice in order to mutate the data. To do that you just need to make bytes a var instead of const.

since bytes will be overwritten imediately anyway, there is no point filling it with any values. zig lets you set values to undefined to explicitly not initialise them.

Zig should tell you at least the first, but it might not since it sees bytes is const so it couldn’t become a mutable slice.

I really do suggest switching to 0.15.2 if you are this new to zig, master is just in a lot of turmoil and any resources may go out of date in a matter of days.
0.15.2 or any older “stable” release will have much more useful resources, and there will be more people able to help too. Though up-to-date resources will still be rarer than most other languages since zig changes so often.

Thanks, @vulpesx . Installed 0.15.2. Now this works great:

const std = @import("std");
const Random = std.Random;
const print = std.debug.print;

pub fn main() !void {
    var prng = prng: {
        var seed: u64 = undefined;
        // syscall to get random seed.
        try std.posix.getrandom(std.mem.asBytes(&seed));
        // syscalls are slow, so implementations that calculate
        // it in userspace are faster.
        break :prng std.Random.DefaultPrng.init(seed);
    };
    // get interface
    const rand = prng.random();

    // An array of 10 floats initialized to 0.
    var arr = [_]f64{0} ** 10;

    // Populate `arr` with random numbers.
    var i: usize = 0;
    while (i < arr.len) {
        arr[i] = rand.float(f64);
        i += 1;
    }

    // Iterate over `arr`, modifying each elem as I go.
    i = 0;
    while (i < arr.len) {
        arr[i] *= 10;
        i += 1;
    }

    // Finally, print them all out.
    for (arr) |x| {
        print("{}\n", .{x});
    }
}