{ }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);    }    コンソール.書く(줄);}コンソール.書く(바뀐수);