const std = @import( "std" ); const print = std.debug.print; const assert = std.debug.assert; // while inotifywait small-integers.zig ; zig test small-integers.zig ; printf \n; end pub fn main() !void { const N = 6; const writer = std.io.getStdOut().writer(); try printRanges( writer, N ); try writer.print( "\n", .{} ); try writer.print( "addition\n", .{} ); try printTables( writer, N, integer.add ); try writer.print( "subtraction\n", .{} ); try printTables( writer, N, integer.sub ); } fn printTables( writer :anytype, N :usize, comptime func :*const fn( integer, integer, ) integer ) !void { const X = N; const Y = N; const W = 3; inline for( [_][2]u8 { "uu".*, "us".*, "su".*, "ss".* } ) |us| { try printTable( writer, X, Y, W, true, struct { fn callback( w2 :anytype, x :?usize, y :?usize ) !void { if( x == null and y == null ) { return try w2.writeByteNTimes( ' ', 3 ); } const j = y orelse { return try w2.print( "{: >3}", .{ integer { .bits = @intCast( x.? ), .signed = ( us[0] == 's' ), } } ); }; const i = x orelse { return try w2.print( "{: >3}", .{ integer { .bits = @intCast( j ), // y.?, .signed = ( us[1] == 's' ), } } ); }; const a = integer { .bits = @intCast(i), .signed = ( us[0] == 's' ), }; const b = integer { .bits = @intCast(j), .signed = ( us[1] == 's' ), }; // const c = a.add( b ); return try w2.print( "{: >3}", .{ func( a, b ) } ); } }.callback ); } } const integer = struct { bits :u16, signed :bool, /// pub fn format(value: ?, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void pub fn format( value :integer, comptime fmt :[]const u8, options :std.fmt.FormatOptions, writer :anytype, ) !void { _ = fmt; // options.width, options.fill, options.alignment, options.precision if( options.fill < ' ' or '~' < options.fill ) { @panic( "non-ascii (utf-8) fill character in integer format\n" ); } const str_width = ( 1 ) + ( std.math.log10( @max( value.bits, 1 ) ) + 1 ); const width = ( options.width orelse 0 ) -| str_width; const fill_left, const fill_right = switch( options.alignment ) { .left => .{ 0, width }, .center => .{ width - ( width )/2, ( width )/2 }, .right => .{ width, 0 }, }; var buffer :[8]u8 = undefined; const written = try std.unicode.utf8Encode( options.fill, &buffer ); const fill_str = buffer[0..written]; for( 0..fill_left ) |_| { try writer.print( "{s}", .{ fill_str } ); // options.fill } try writer.print( "{c}{d}", .{ @as( u8, if( value.signed ) 's' else 'u' ), value.bits, } ); for( 0..fill_right ) |_| { try writer.print( "{s}", .{ fill_str } ); // options.fill } } fn min( i :@This() ) isize { if( !i.signed ) { return 0; } if( i.bits == 0 ) { return 0; } return -1 * ( @as( isize, 1 ) << @as( u6, @intCast( i.bits - 1 ) ) ); } fn max( i :@This() ) usize { const ubits :u6 = @intCast( i.bits -| @as( u1, if( i.signed ) ( 1 ) else ( 0 ) ) ); return ( @as( usize, 1 ) << ubits ) - 1; } fn str( i :@This() ) [2]u8 { std.debug.assert( i.bits < 10 ); return [2]u8 { if( i.signed ) ( 's' ) else ( 'u' ), @intCast( '0' + i.bits ), }; } /// training wheels version, but useful.. fn _add( a :integer, b :integer ) integer { const _min = a.min() + b.min(); const _max = a.max() + b.max(); var c = integer { .bits = 0, .signed = ( _min < 0 ), }; while( _min < c.min() or c.max() < _max ) { c.bits += 1; } return c; } /// integer addition meta-logic fn add( a :integer, b :integer ) integer { const c_signed = ( a.signed and a.bits != 0 ) or ( b.signed and b.bits != 0 ) ; const c_bits = if( a.bits < 2 or b.bits < 2 ) ( a.bits + b.bits ) else if( a.signed == b.signed ) ( @max( a.bits, b.bits ) + 1 ) else ( @max( a.bits + @as( u1, if( a.signed ) 0 else 1 ), b.bits + @as( u1, if( b.signed ) 0 else 1 ), ) + 1 ); const c = integer { .signed = c_signed, .bits = c_bits, }; assert( c.min() <= a.min() + b.min() ); assert( a.max() + b.max() <= c.max() ); const _c = a._add( b ); assert( c.bits == _c.bits ); assert( c.signed == _c.signed ); return c; } /// training wheels version, but useful.. fn sub( a :integer, b :integer ) integer { const _min = a.min() - @as( isize, @intCast( b.max() ) ); const _max = @as( isize, @intCast( a.max() ) ) - b.min(); var c = integer { .bits = 0, .signed = ( _min < 0 ), }; while( _min < c.min() or c.max() < _max ) { c.bits += 1; } return c; } }; fn printRanges( writer :anytype, bits :usize ) !void { for( "us".* ) |us| { for( 0..bits ) |i| { const int = integer { .bits = @intCast( i ), .signed = ( 's' == us ), }; try writer.print( "type {s} ( min = {d}, max = {d}, )\n", .{ &int.str(), int.min(), int.max(), } ); } } } fn printTable( writer :anytype, X :usize, Y :usize, W :usize, condensed :bool, func :*const fn( writer :anytype, x :?usize, y :?usize, ) anyerror!void ) !void { try writer.print( " ", .{} ); try func( writer, null, null ); try writer.print( " |", .{} ); for( 0..X ) |i| { try writer.print( " ", .{} ); try func( writer, i, null ); try writer.print( "{s}", .{ if( condensed ) "" else " |" } ); } try writer.print( "\n", .{} ); try writer.print( "-", .{} ); try writer.writeByteNTimes( '-', W ); try writer.print( "-+", .{} ); for( 0..X ) |_| { try writer.print( "-", .{} ); try writer.writeByteNTimes( '-', W ); try writer.print( "{s}", .{ if( condensed ) "" else "--" } ); } try writer.print( "\n", .{} ); for( 0..Y ) |j| { try writer.print( " ", .{} ); try func( writer, null, j ); try writer.print( " |", .{} ); for( 0..X ) |i| { try writer.print( " ", .{} ); try func( writer, i, j ); try writer.print( "{s}", .{ if( condensed ) "" else " |" } ); } try writer.print( "\n", .{} ); if( !condensed ) { try writer.print( "-", .{} ); try writer.writeByteNTimes( '-', W ); try writer.print( "-|", .{} ); for( 0..X ) |_| { try writer.print( "-", .{} ); try writer.writeByteNTimes( '-', W ); try writer.print( "-+", .{} ); } try writer.print( "\n", .{} ); } } try writer.print( "\n", .{} ); } const expect = std.testing.expect; const eql = std.mem.eql; fn expectWritten( callback :*const fn( writer :anytype ) anyerror!void, expected :[]const u8 ) !void { // XXX std.testing.allocator hard coded .. var list = std.ArrayList( u8 ).init( std.testing.allocator ); defer { list.deinit(); } try callback( list.writer() ); try std.testing.expectEqualStrings( expected, list.items ); } test "add" { const _u2 = integer { .bits = 2, .signed = false }; const _u3 = _u2.add( _u2 ); // try expect( _u2.add( _u2 ) == .{ .bits = 3, .signed = false } ); try expect( _u3.bits == 3 and _u3.signed == false ); } test "u0" { const _u0 = integer { .bits = 0, .signed = false, }; try expect( _u0.min() == 0 ); try expect( _u0.max() == 0 ); try expect( eql( u8, "u0", &_u0.str() ) ); } test "s0, u1, s1" { const _s0 = integer { .bits = 0, .signed = true, }; try expect( _s0.min() == 0 ); try expect( _s0.max() == 0 ); try expect( eql( u8, "s0", &_s0.str() ) ); const _u1 = integer { .bits = 1, .signed = false, }; try expect( _u1.min() == 0 ); try expect( _u1.max() == 1 ); try expect( eql( u8, "u1", &_u1.str() ) ); const _s1 = integer { .bits = 1, .signed = true, }; try expect( _s1.min() == -1 ); try expect( _s1.max() == 0 ); try expect( eql( u8, "s1", &_s1.str() ) ); } test "u4, s4" { const _u4 = integer { .bits = 4, .signed = false, }; try expect( _u4.min() == 0 ); try expect( _u4.max() == 15 ); try expect( eql( u8, "u4", &_u4.str() ) ); const _s4 = integer { .bits = 4, .signed = true, }; try expect( _s4.min() == -8 ); try expect( _s4.max() == 7 ); try expect( eql( u8, "s4", &_s4.str() ) ); } test "s1+s1" { const _s1 = integer { .bits = 1, .signed = true, }; try expect( eql( u8, "s2", &_s1.add( _s1 ).str() ) ); } test "s2+s3, s5+s1" { const _s1 = integer { .bits = 1, .signed = true, }; const _s2 = integer { .bits = 2, .signed = true, }; const _s3 = integer { .bits = 3, .signed = true, }; const _s5 = integer { .bits = 5, .signed = true, }; try expect( eql( u8, "s4", &_s2.add( _s3 ).str() ) ); try expect( eql( u8, "s6", &_s5.add( _s1 ).str() ) ); } test printRanges { const expected = \\type u0 ( min = 0, max = 0, ) \\type u1 ( min = 0, max = 1, ) \\type u2 ( min = 0, max = 3, ) \\type u3 ( min = 0, max = 7, ) \\type u4 ( min = 0, max = 15, ) \\type u5 ( min = 0, max = 31, ) \\type s0 ( min = 0, max = 0, ) \\type s1 ( min = -1, max = 0, ) \\type s2 ( min = -2, max = 1, ) \\type s3 ( min = -4, max = 3, ) \\type s4 ( min = -8, max = 7, ) \\type s5 ( min = -16, max = 15, ) \\ ; var buffer :[ expected.len ]u8 = undefined; var stream = std.io.fixedBufferStream( &buffer ); try printRanges( stream.writer(), 6 ); try std.testing.expectEqualStrings( expected, stream.getWritten() ); } test "printTables - addition" { const expected = \\ | u0 u1 u2 u3 u4 u5 \\-----+------------------------ \\ u0 | u0 u1 u2 u3 u4 u5 \\ u1 | u1 u2 u3 u4 u5 u6 \\ u2 | u2 u3 u3 u4 u5 u6 \\ u3 | u3 u4 u4 u4 u5 u6 \\ u4 | u4 u5 u5 u5 u5 u6 \\ u5 | u5 u6 u6 u6 u6 u6 \\ \\ | u0 u1 u2 u3 u4 u5 \\-----+------------------------ \\ s0 | u0 u1 u2 u3 u4 u5 \\ s1 | s1 s2 s3 s4 s5 s6 \\ s2 | s2 s3 s4 s5 s6 s7 \\ s3 | s3 s4 s4 s5 s6 s7 \\ s4 | s4 s5 s5 s5 s6 s7 \\ s5 | s5 s6 s6 s6 s6 s7 \\ \\ | s0 s1 s2 s3 s4 s5 \\-----+------------------------ \\ u0 | u0 s1 s2 s3 s4 s5 \\ u1 | u1 s2 s3 s4 s5 s6 \\ u2 | u2 s3 s4 s4 s5 s6 \\ u3 | u3 s4 s5 s5 s5 s6 \\ u4 | u4 s5 s6 s6 s6 s6 \\ u5 | u5 s6 s7 s7 s7 s7 \\ \\ | s0 s1 s2 s3 s4 s5 \\-----+------------------------ \\ s0 | u0 s1 s2 s3 s4 s5 \\ s1 | s1 s2 s3 s4 s5 s6 \\ s2 | s2 s3 s3 s4 s5 s6 \\ s3 | s3 s4 s4 s4 s5 s6 \\ s4 | s4 s5 s5 s5 s5 s6 \\ s5 | s5 s6 s6 s6 s6 s6 \\ \\ ; // var buffer :[ expected.len ]u8 = undefined; // var stream = std.io.fixedBufferStream( &buffer ); // try printTables( stream.writer(), 6, true, integer.add ); // try std.testing.expectEqualStrings( expected, stream.getWritten() ); try expectWritten( struct { fn callback( writer :anytype ) !void { try printTables( writer, 6, integer.add ); } }.callback, expected ); // try expectWritten( fn( writer :anytype ) !void { // try printTables( writer, 6, true, integer.add ); // }, expected ); // try expectWritten( .|writer| { // printTables( writer, 6, true, integer.add ).!; // }, expected ); } test "log10()" { // note: log10() asserts when passed zero.. // so use `log10( @max( 1, expr ) )` try expectWritten( struct { fn callback( writer :anytype ) !void { for( [_]usize { 9, 10, 11, 99, 100, 101, 999, 1000, 1001 } ) |x| { try writer.print( "log10({d}) == {d}, {d} chars\n", .{ x, std.math.log10(x), std.math.log10(x) + 1 } ); } } }.callback, \\log10(9) == 0, 1 chars \\log10(10) == 1, 2 chars \\log10(11) == 1, 2 chars \\log10(99) == 1, 2 chars \\log10(100) == 2, 3 chars \\log10(101) == 2, 3 chars \\log10(999) == 2, 3 chars \\log10(1000) == 3, 4 chars \\log10(1001) == 3, 4 chars \\ ); } test "format utf8" { try expectWritten( struct { fn callback( writer :anytype ) !void { // try writer.print( "my u8 <{d: ^7}>\n", .{ @as( u8, 128 ) } ); // "my u8 < 128 >\n" const int = integer { .bits = 31, .signed = true, }; try writer.print( "my int <{:_^7}>\n", .{ int } ); } }.callback, "my int <__s31__>\n" ); }