Hi.
It is quite common situation when a user of a program/system
may want to specify how many digits after decimal point should be seen.
In C this can be achieved by at least 3 ways:
#include <stdio.h>
#include <math.h>
void print_float_with_given_precision_v1(float x, int dp)
{
char fmt[16];
sprintf(fmt, "x = %%.%df\n", dp);
printf(fmt, x);
}
void print_float_with_given_precision_v2(float x, int dp)
{
printf("x = %.*f\n", dp, x);
}
void print_float_with_given_precision_v3(float x, int dp)
{
printf("x = %1$.*2$f\n", x, dp);
}
int main(int argc, char *argv[])
{
if (argc != 2) {
printf("usage: %s <number>\n", argv[0]);
return 0;
}
int dp = 3;
sscanf(argv[1], "%d", &dp); // ignore errors
float x = M_PI;
print_float_with_given_precision_v1(x, dp);
print_float_with_given_precision_v2(x, dp);
print_float_with_given_precision_v3(x, dp);
}
$ ./a.out 5
x = 3.14159
x = 3.14159
x = 3.14159
However, in Zig format string for std.debug.print()
and alike must be compile time known.
Are there any ways to deal with runtime known precision (number of digits after decimal point) in ZIg?..
4 Likes
Cloudef
December 21, 2023, 10:07am
2
See std.fmt, there’s formatValue, formatFloat, etc… that take FormatOptions struct
pub const FormatOptions = struct {
precision: ?usize = null,
width: ?usize = null,
alignment: Alignment = .right,
fill: u8 = ' ',
};
3 Likes
Thanks, I will take a look at this.
Yes, that’s all right.
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
if (std.os.argv.len != 2) {
try stdout.print("usage: {s} <precision>\n", .{std.os.argv[0]});
return;
}
const p = std.fmt.parseInt(usize, std.mem.sliceTo(std.os.argv[1], 0), 10) catch 3;
try std.fmt.formatFloatDecimal(std.math.pi, .{.precision = p}, stdout);
try stdout.print("\n", .{});
}
$ ./dp
usage: ./dp <precision>
$ ./dp 4
3.1416
In a more similar fashion to the C examples you provided, you can use a runtime value in the args tuple or struct to fill in parts of the format specifier. You put the index or field name in square brackets []
. In this example, I use the second arg of the tuple (index 1) as the precision:
std.debug.print("{d:.[1]}\n", .{ 3.1415, 2 });
12 Likes
Oh, great, thanks! I did not know about this possibility.
Less words and no writers as arguments, good.
Both variants in one place, just to compare:
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
if (std.os.argv.len != 2) {
try stdout.print("usage: {s} <precision>\n", .{std.os.argv[0]});
return;
}
const arg1 = std.mem.sliceTo(std.os.argv[1], 0);
const p = std.fmt.parseInt(usize, arg1, 10) catch 2;
// variant I
try stdout.print("π = ", .{});
try std.fmt.formatFloatDecimal(std.math.pi, .{.precision = p}, stdout);
try stdout.print("\n", .{});
// variant II
try stdout.print("π = {d:.[1]}\n", .{std.math.pi, p});
}
2 Likes
A third, a bit more explicit, variant:
// variant III
try stdout.print("π = {[x]d:.[precision]}\n", .{.x = std.math.pi, .precision = p});
5 Likes
Also good. The score was even, 3:3
I’ve marked @dude_the_builder ’s variant as solution,
because I like this variant more than the other two.
Anyway, thanks to @Cloudef and @slonik-az too.
1 Like