{ }codelper
类型转换P0397

装进更小的类型

把一个整数依次装进大小固定的整数类型里。
类型有 i8、u8、i16、u16、i32、u32 六种。
以 i 开头的是有符号类型,以 u 开头的是无符号类型,后面的数 k 是位数。
k 位有符号类型能装 -2^(k-1) 到 2^(k-1) - 1 的整数。
k 位无符号类型能装 0 到 2^k - 1 的整数。
装的方法有两种。
截断只写类型名(i8)。
在该类型能装的整数中,与要装的值之差是 2^k 的倍数的数只有一个,这个数就是结果。
饱和在类型名后面加 !
来写(i8!)。
要装的值能装进该类型时结果就是它本身,比最小的数小时结果是最小的数,比最大的数大时结果是最大的数。
第一行给出行数 q。
接下来 q 行每行给出整数 v 和若干个步骤。
一个步骤就是一种装的方法。
第一个步骤装 v,从第二个步骤起装紧挨着的前一个步骤的结果。
对每一行,依次输出各步骤的结果,用一个空格隔开。
最后一行输出所有行合起来,结果与装之前的值不同的步骤共有多少个。

输入
1 ≤ q ≤ 1000。
-9000000000000000 ≤ v ≤ 9000000000000000。
一行有 1 到 8 个步骤。
v 和各步骤用一个空格隔开给出
输出
q 行,以及最后一行值发生变化的步骤数
样例 1 · stdin
4
200 i8 u16 u32 i8!
-1 u32 i8 u8 u8!
300 u8! i8 i16
128 i8 u8 i8!
stdout
-56 65480 65480 127
4294967295 -1 255 255
255 -1 -1
-128 128 127
11
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

把整数放进更小的类型时发生的事情,各语言不同。
C 用 (int8_t)v、C++ 用 static_cast<std::uint16_t>(v) 这样转换成大小固定的类型,只留下低位。
不过 C 标准把“装不进有符号类型的值”的转换结果交给编译器决定(是实现定义的行为,不是未定义行为);gcc 和 clang 只留下低位,从 C++20 起标准本身就这样规定。
Java 的 (byte)v、(short)v、(int)v 也一样,但无符号类型只有 16 位的 char,所以 u8 用 v & 0xFF、u32 用 v & 0xFFFFFFFFL 来截。
C# 有 sbyte、byte、short、ushort、int、uint 全部六种,在 unchecked 里像 (sbyte)v 这样转换。
JS 只有一种数,但位运算会先把值截成 32 位,所以写成 (v << 24) >> 24(i8)、v & 0xff(u8)、v | 0(i32)、v >>> 0(u32)。
TS 的解法把值放进 Int8Array 这类类型化数组的格子里再取出来。
Python 的整数没有大小限制,不会自己被截断,所以用 v % size 自己算(size 为正时 Python 的 % 总是不小于 0)。
Craykon 也没有单独的整数类型,用 % 来算。
饱和在每个解法里都是保持宽类型去和两端比较(C# Math.Clamp、C++ std::clamp)。
陷阱一:Craykon 的 % 跟随左边的数的符号,所以 -56 % 65536 是 -56。
是负数时必须加上 2^k。
漏掉的话示例第一行会变成 -56 -56 -56 -56。
陷阱二:在有符号类型里,恰好等于 2^(k-1) 的值在范围之外。
把 128 截断进 i8 得到 -128。
边界用 > 比较的话,示例第四行会变成 128 128 127。
陷阱三:步骤是连起来的。
200 装进 i8 得到 -56,再装进 u16 得到 65480。
每一步都装最初的值的话,示例第一行会变成 -56 200 200 127。
陷阱四:在 Java 里像 v & 0xFFFFFFFF 这样漏掉 L,掩码就是 int 的 -1,扩展成 long 后仍是 -1,什么也截不掉。
示例第二行会变成 -1 -1 255 255。
陷阱五:在 JS 里 v & 0xffffffff 的结果是有符号 32 位数,所以第二行同样变成 -1 -1 255 255。
只有 >>> 0 给出无符号的结果。
陷阱六:在 C++ 里把 std::int8_t、std::uint8_t 的值直接交给 std::cout,输出的不是数而是该编码的字符。
示例第一行 -56 的位置会出现一个字节。
要转回 long long 再输出。
陷阱七:C# 的 Convert.ToSByte(v) 不截断,超出范围就抛出 OverflowException。
在示例第一行的 200 处停止。

Pythonmain.py · 34 行
import sys  def convert(v, step):    signed = step[0] == "i"    bits = int(step[1:].rstrip("!"))    size = 1 << bits                      # 2^bits    lo = -(size // 2) if signed else 0    hi = lo + size - 1    if step.endswith("!"):                # 끝값에 맞춰 담기        return max(lo, min(hi, v))    r = v % size                          # Python의 %는 늘 0 이상 size 미만    if r > hi:                            # 부호 있는 타입의 위쪽 절반은 음수        r -= size    return r  lines = sys.stdin.read().split("\n")q = int(lines[0])out = []changed = 0for line in lines[1:q + 1]:    parts = line.split()    v = int(parts[0])    row = []    for step in parts[1:]:        nv = convert(v, step)             # 바로 앞 단계의 결과를 다시 옮겨 담는다        if nv != v:            changed += 1        v = nv        row.append(str(v))    out.append(" ".join(row))out.append(str(changed))print("\n".join(out))
JavaScriptmain.js · 43 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const q = Number(lines[0]); const RANGE = {  i8: [-128, 127], u8: [0, 255],  i16: [-32768, 32767], u16: [0, 65535],  i32: [-2147483648, 2147483647], u32: [0, 4294967295],}; // 비트 연산은 먼저 값을 32비트로 자른 뒤에 한다function wrap(v, type) {  switch (type) {    case "i8": return (v << 24) >> 24;   // 아래 8비트를 맨 위로 올렸다가 부호를 채우며 내린다    case "u8": return v & 0xff;    case "i16": return (v << 16) >> 16;    case "u16": return v & 0xffff;    case "i32": return v | 0;    default: return v >>> 0;             // u32: >>>만 결과를 부호 없는 수로 준다  }} const out = [];let changed = 0;for (let i = 1; i <= q; i++) {  const parts = lines[i].trim().split(" ");  let v = Number(parts[0]);  const row = [];  for (const step of parts.slice(1)) {    let nv;    if (step.endsWith("!")) {      const [lo, hi] = RANGE[step.slice(0, -1)];      nv = Math.max(lo, Math.min(hi, v));    } else {      nv = wrap(v, step);    }    if (nv !== v) changed++;    v = nv;    row.push(v);  }  out.push(row.join(" "));}out.push(changed);console.log(out.join("\n"));
TypeScriptmain.ts · 47 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const q = Number(lines[0]); type IntType = "i8" | "u8" | "i16" | "u16" | "i32" | "u32"; const RANGE: Record<IntType, [number, number]> = {  i8: [-128, 127], u8: [0, 255],  i16: [-32768, 32767], u16: [0, 65535],  i32: [-2147483648, 2147483647], u32: [0, 4294967295],}; // 타입 배열의 칸에 넣으면 그 칸의 크기에 맞게 잘려 들어간다const CELL: Record<IntType, Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array> = {  i8: new Int8Array(1), u8: new Uint8Array(1),  i16: new Int16Array(1), u16: new Uint16Array(1),  i32: new Int32Array(1), u32: new Uint32Array(1),}; function wrap(v: number, type: IntType): number {  const cell = CELL[type];  cell[0] = v;  return cell[0];} const out: string[] = [];let changed = 0;for (let i = 1; i <= q; i++) {  const parts: string[] = lines[i].trim().split(" ");  let v = Number(parts[0]);  const row: number[] = [];  for (const step of parts.slice(1)) {    let nv: number;    if (step.endsWith("!")) {      const [lo, hi] = RANGE[step.slice(0, -1) as IntType];      nv = Math.max(lo, Math.min(hi, v));    } else {      nv = wrap(v, step as IntType);    }    if (nv !== v) changed++;    v = nv;    row.push(v);  }  out.push(row.join(" "));}out.push(String(changed));console.log(out.join("\n"));
JavaMain.java · 44 行
import java.io.*; public class Main {    // 잘라 담기: Java의 정수 캐스트는 아래쪽 비트만 남긴다    static long wrap(long v, String type) {        switch (type) {            case "i8": return (byte) v;            case "u8": return v & 0xFF;              // Java에는 부호 없는 byte가 없다            case "i16": return (short) v;            case "u16": return (char) v;             // char는 부호 없는 16비트            case "i32": return (int) v;            default: return v & 0xFFFFFFFFL;         // u32: L을 빼면 0xFFFFFFFF는 int -1이다        }    }     static long clamp(long v, String type) {        int bits = Integer.parseInt(type.substring(1));        long lo = type.charAt(0) == 'i' ? -(1L << (bits - 1)) : 0;        long hi = lo + (1L << bits) - 1;        return Math.max(lo, Math.min(hi, v));    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int q = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        int changed = 0;        for (int i = 0; i < q; i++) {            String[] parts = in.readLine().trim().split(" ");            long v = Long.parseLong(parts[0]);            for (int j = 1; j < parts.length; j++) {                String step = parts[j];                long nv = step.endsWith("!") ? clamp(v, step.substring(0, step.length() - 1)) : wrap(v, step);                if (nv != v) changed++;                v = nv;                if (j > 1) out.append(' ');                out.append(v);            }            out.append('\n');        }        out.append(changed).append('\n');        System.out.print(out);    }}
C#Main.cs · 59 行
using System;using System.Text; class Program{    // 잘라 담기: unchecked 캐스트는 아래쪽 비트만 남긴다    static long Wrap(long v, string type)    {        unchecked        {            switch (type)            {                case "i8": return (sbyte)v;                case "u8": return (byte)v;                case "i16": return (short)v;                case "u16": return (ushort)v;                case "i32": return (int)v;                default: return (uint)v;            }        }    }     static long Clamp(long v, string type)    {        switch (type)        {            case "i8": return Math.Clamp(v, sbyte.MinValue, sbyte.MaxValue);            case "u8": return Math.Clamp(v, byte.MinValue, byte.MaxValue);            case "i16": return Math.Clamp(v, short.MinValue, short.MaxValue);            case "u16": return Math.Clamp(v, ushort.MinValue, ushort.MaxValue);            case "i32": return Math.Clamp(v, int.MinValue, int.MaxValue);            default: return Math.Clamp(v, uint.MinValue, uint.MaxValue);        }    }     static void Main()    {        int q = int.Parse(Console.ReadLine());        var output = new StringBuilder();        int changed = 0;        for (int i = 0; i < q; i++)        {            string[] parts = Console.ReadLine().Trim().Split(' ');            long v = long.Parse(parts[0]);            for (int j = 1; j < parts.Length; j++)            {                string step = parts[j];                long nv = step.EndsWith("!") ? Clamp(v, step.TrimEnd('!')) : Wrap(v, step);                if (nv != v) changed++;                v = nv;                if (j > 1) output.Append(' ');                output.Append(v);            }            output.Append('\n');        }        output.Append(changed).Append('\n');        Console.Write(output);    }}
C++main.cpp · 53 行
#include <algorithm>#include <cstdint>#include <iostream>#include <sstream>#include <string> // 잘라 담기: static_cast로 크기가 정해진 타입에 넣으면 아래쪽 비트만 남는다long long wrap(long long v, const std::string& type) {    if (type == "i8") return static_cast<std::int8_t>(v);    if (type == "u8") return static_cast<std::uint8_t>(v);    if (type == "i16") return static_cast<std::int16_t>(v);    if (type == "u16") return static_cast<std::uint16_t>(v);    if (type == "i32") return static_cast<std::int32_t>(v);    return static_cast<std::uint32_t>(v);} long long clampTo(long long v, const std::string& type) {    int bits = std::stoi(type.substr(1));    long long lo = type[0] == 'i' ? -(1LL << (bits - 1)) : 0;    long long hi = lo + (1LL << bits) - 1;    return std::clamp(v, lo, hi);} int main() {    std::string line;    std::getline(std::cin, line);    int q = std::stoi(line);    int changed = 0;    for (int i = 0; i < q; i++) {        std::getline(std::cin, line);        std::istringstream in(line);        long long v;        in >> v;        std::string step;        bool first = true;        while (in >> step) {            long long nv;            if (step.back() == '!') {                step.pop_back();                nv = clampTo(v, step);            } else {                nv = wrap(v, step);            }            if (nv != v) changed++;            v = nv;            if (!first) std::cout << ' ';            std::cout << v;   // long long으로 쓴다. int8_t를 그대로 쓰면 글자로 찍힌다            first = false;        }        std::cout << '\n';    }    std::cout << changed << '\n';}
Cmain.c · 51 行
#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h> /* 잘라 담기: 크기가 정해진 타입으로 캐스트하면 아래쪽 비트만 남는다 */static long long wrap(long long v, const char *type) {    if (strcmp(type, "i8") == 0) return (int8_t)v;    if (strcmp(type, "u8") == 0) return (uint8_t)v;    if (strcmp(type, "i16") == 0) return (int16_t)v;    if (strcmp(type, "u16") == 0) return (uint16_t)v;    if (strcmp(type, "i32") == 0) return (int32_t)v;    return (uint32_t)v;} static long long clamp(long long v, const char *type) {    int bits = atoi(type + 1);    long long lo = type[0] == 'i' ? -(1LL << (bits - 1)) : 0;    long long hi = lo + (1LL << bits) - 1;    return v < lo ? lo : v > hi ? hi : v;} int main(void) {    static char line[256];    int q, changed = 0;    if (!fgets(line, sizeof line, stdin)) return 0;    q = atoi(line);    for (int i = 0; i < q; i++) {        if (!fgets(line, sizeof line, stdin)) break;        char *tok = strtok(line, " \n");        long long v = atoll(tok);        int first = 1;        while ((tok = strtok(NULL, " \n")) != NULL) {            size_t len = strlen(tok);            long long nv;            if (tok[len - 1] == '!') {                tok[len - 1] = '\0';                nv = clamp(v, tok);            } else {                nv = wrap(v, tok);            }            if (nv != v) changed++;            v = nv;            printf(first ? "%lld" : " %lld", v);            first = 0;        }        putchar('\n');    }    printf("%d\n", changed);    return 0;}
Craykonmain.cray · 62 行
// 2를 bits번 곱한 수둘의제곱(bits){    결과 = 1;    循环 (k = 0; k < bits; k++)    {        결과 = 결과 * 2;    }    返回 결과;} 옮겨담기(v, 단계){    맞추기 = 단계.结尾是("!");    이름 = 단계;    如果 (맞추기)    {        이름 = 단계.截取(0, 단계.长度 - 1);    }    크기 = 둘의제곱(이름.截取(1).转数字());    lo = 0;    如果 (이름[0] == "i")    {        lo = 0 - 크기 / 2;    }    hi = lo + 크기 - 1;    如果 (맞추기)    {        返回 数学.较大值(lo, 数学.较小值(hi, v));    }    r = v % 크기; // %는 왼쪽 수의 부호를 따라서 음수가 나올 수 있다    如果 (r < 0)    {        r += 크기;    }    如果 (r > hi)    {        r -= 크기;    }    返回 r;} q = 控制台.读行().转数字();바뀐수 = 0;循环 (i = 0; i < q; i++){    parts = 控制台.读行().去空白().分割(" ");    v = parts[0].转数字();    줄 = [];    循环 (j = 1; j < parts.数量; j++)    {        nv = 옮겨담기(v, parts[j]);        如果 (nv != v)        {            바뀐수 += 1;        }        v = nv;        줄.添加(v);    }    控制台.输出(줄);}控制台.输出(바뀐수);