How are big integer literals treated?

Hi there,

I just played with u128 and stumbled across a behavior that seems odd to me, when I was using numbers bigger than u128.

This is the code:

const std = @import("std");

pub fn main() void {
    std.debug.print("Big number: '{}'\n", .{ 100_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000 });
}

I get the following output:

Big number: '100000000000000000000000000000000000000000000000000000000000000'

I would have expected a compile time error or at least a wrong number to be displayed.

So why does this code work?


I just checked with the following code:

const std = @import("std");

pub fn main() void {
    const big_number = 100_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000;

    std.debug.print("Big number: '{}'\n", .{ big_number });
    std.debug.print("TypeOf big number: '{}'\n", .{ @TypeOf(big_number) });
}

Output:

Big number: '100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
TypeOf big number: 'comptime_int'

It can be found here in the documentation: link

I am still interested how this works under the hood, as there is not a lot of explanation there. Thanks in advance :slight_smile:

Hello,
this works because the number is not u128.
All number literals in Zig are comptime_int (comptime_float also exists, but has different behavior). This data type can hold almost any number.

The max limit of bit width for signed and unsigned integers in Zig is 65535 bits.

EDIT: I see you modified your question, what part of it do you want to know how works?

1 Like

In Zig all integer literals are of type comptime_int, behind the scenes this is using the big int implementation from the standard library.
Zig also tries to evaluate things at compile time as much as possible, so if you create a const and initialize it with a compile-time known value, then the constant will also be compile time known.

If you explicitly want to use u128 then you have to tell it the type:

const big_number: u128 = ...;

And even that will remain compile-time known by the way if you initialize it with e.g. an integer literal.

1 Like

Thanks for the replies :smiley:

I just started learning Zig and I like it more and more :smiling_face_with_sunglasses:

I think I will take a look at the big int implementation.

1 Like

Of course a brief explanation on how big ints are handled would be great :sweat_smile:

Also interesting place to look at is here.
It may not be the current implementation, but generally describes how print works.

If you want a quick run down, most languages have formatted printing hard coded into compiler (even new ones like Rust), because if it were implemented in user space it would either be slower or would lack nice error messages.
Zig is able to describe it fully in user land with compile time reflections. (its a bit harder for new commer, but really cool)

If you want to get a usefull information about the language and try to learn basics in coordinated way, there is Ziglings which is popular to get the know-how about the language, has really nice descriptive comments. (it targets people who know how to program, but are new to Zig)

1 Like

Thank’s for the links, I will have a look.

I do Ziglings at the moment.
I also read some posts here in the forum.

I really like the zig community. Alone the Ziglings repo is great and everyone seems to be very helpful.

I will close this thread now.

2 Likes

I don’t believe that’s true, Rust’s println! is implemented with macros; which can’t really be called a part of the compiler any more than Zig’s comptime is.

It used to be 100% that way, the macros after some indirection used to call exposed special compiler functions. I dont know whether its still the case, but i dont think they changed it.

It is true that println! is implemented with macros but if you try to go and look in actual macro implementation you will find this

#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "print_macro")]
#[allow_internal_unstable(print_internals)]
macro_rules! print {
    ($($arg:tt)*) => {{
        $crate::io::_print($crate::format_args!($($arg)*));
    }};
}
#[allow_internal_unsafe]
#[allow_internal_unstable(fmt_internals)]
#[rustc_builtin_macro]
#[macro_export]
macro_rules! format_args {
($fmt:expr) => {{ /* compiler built-in */ }};
($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
}

So no it just looks like a macro and is infact part of the compiler

2 Likes

Here is actual implementation of format_args

1 Like