Edited at

ナウい言語は暗黙の整数拡張を行わないか?


はじめに

@akif999 さんの書かれた記事『C から Go へコードを移植してハマった話 (そして言語仕様へ)』の中で


「あ、C のこの a << 8 は、int に拡張されるんやった」

「C のプログラムがうまくいくのは、整数の演算は暗黙に int へ拡張されるからや…」

「でも、ナウい Go にはそんな暗黙の動作なんてないんや…」


main.c

    c |= (unsigned short)(a << 8);

c |= (unsigned short)(b << 0);

そうです。

C 言語は、言語が誕生した時代背景や、

言語処理系が CPU へ依存した規格となっていることのせいで、

「未定義動作」や、暗黙の型キャスト、整数の拡張などを頻繁に行います。


との記述があり、「へえ、いまどきのナウい言語は C と違って暗黙の整数拡張を行わないのか」と思ったので検証してみることにしました。


方法

静的型付けを行い、サイズの異なる整数型を持ち、そこそこ使われてる雰囲気の言語/処理系に元記事のプログラムを移植して実行し確認してみました。本来的には言語仕様の書かれたドキュメントを参照すべきですが、面倒なので今回は行っておりません。


整数拡張を行う言語


Java

class Hogera

{
public static void main(String[] args)
{
byte a = 0x12;
byte b = 0x34;
short c = 0x0000;

c |= a << 8;
c |= b << 0;
System.out.println(String.format("c is 0x%x", c));
}
}

Wandbox で実行


  • Cと変わらん


D

import std.stdio;

void main()
{
ubyte a = 0x12;
ubyte b = 0x34;
ushort c = 0x0000;

c |= a << 8;
c |= b << 0;
writefln("c is 0x%x", c);
}

Wandbox で実行


  • Cと変わらん


C#

using System;

public class Hogera
{
public static void Main()
{
byte a = 0x12;
byte b = 0x34;
ushort c = 0x0000;

c |= (ushort)(a << 8);
c |= (ushort)(b << 0);
Console.WriteLine("c is 0x" + c.ToString("x"));
}
}

Wandbox で実行


  • 暗黙の整数拡張は行われる

  • サイズの異なる整数型のオブジェクトの代入は型変換が必要


Free Pascal

program Hogera(output);

uses sysutils;

var
a: byte = $12;
b: byte = $34;
c: word = $0000;

begin
c := c or a shl 8;
c := c or b shl 0;
writeln('c is $', format('%x', [c]))
end.

Wandbox で実行


  • Cと変わらん


Scala

object Hogera {

def main(args: Array[String]): Unit = {
var a: Byte = 0x12
var b: Byte = 0x34
var c: Short = 0x0000

c = (c | a << 8).asInstanceOf[Short]
c = (c | b << 0).asInstanceOf[Short]
println("c is 0x" + c.toHexString)
}
}

Wandbox で実行


  • 暗黙の整数拡張は行われる

  • 異なる整数型のオブジェクトの代入は型変換が必要


Groovy

Byte  a = 0x12

Byte b = 0x34
Short c = 0x0000

c |= a << 8
c |= b << 0
println "c is 0x" + Integer.toHexString(c)

Wandbox で実行


  • Cと変わらん


Perl6 改め Raku

#!/usr/bin/perl6

my uint8 $a = 0x12;
my uint8 $b = 0x34;
my uint16 $c = 0x0000;

$c = $c +| $a +< 8;
$c = $c +| $b +< 0;
say $c.fmt('$c is 0x%x');

Wandbox で実行


整数拡張を行わない言語


Swift

var a:UInt8  = 0x12

var b:UInt8 = 0x34
var c:UInt16 = 0x0000

c |= UInt16(a) << 8
c |= UInt16(b) << 0
print("c is 0x" + String(c, radix:16))

Wandbox で実行


  • 暗黙の整数拡張を行わないので型変換が必要


Rust

fn main() {

let mut a:u8 = 0x12;
let mut b:u8 = 0x34;
let mut c:u16 = 0x0000;

c |= (a as u16) << 8;
c |= (b as u16) << 0;
println!("c is 0x{:x}", c);
}

Wandbox で実行


  • 暗黙の整数拡張を行わないので型変換が必要

  • mut を付けて宣言されたオブジェクトに代入がされないと警告が出るので驚いた


Nim

import strutils

var
a: uint8 = 0x12
b: uint8 = 0x34
c: uint16 = 0x0000

c = c or uint16(a) shl 8
c = c or uint16(b) shl 0
echo "c is 0x", toHex(c)

Wandbox で実行


  • 暗黙の整数拡張を行わないので型変換が必要


Crystal

a = 0x12u8

b = 0x34u8
c = 0x0000u16

c = c | a.to_u16 << 8
c = c | b.to_u16 << 0
puts "c is 0x" + c.to_s(16)

Wandbox で実行


  • 暗黙の整数拡張を行わないので型変換が必要


Kotlin

import java.util.*

fun main(args: Array<String>) {
var a: Byte = 0x12
var b: Byte = 0x34
var c: Short = 0x0000

c = (c.toInt() or a.toInt() shl 8).toShort()
c = (c.toInt() or b.toInt() shl 0).toShort()
println("c is 0x" + c.toString(16))
}

ideone で実行


  • 暗黙の整数拡張を行わないので型変換が必要

  • Shortではシフト演算もビット和も許されないようなので一旦Intに変換した

  • 異なる整数型のオブジェクトの代入は型変換が必要


Ada

with Ada.Text_IO, Interfaces;

use Ada.Text_IO, Interfaces;

procedure test is
package Unsigned_16_IO is new Ada.Text_Io.Modular_IO(Unsigned_16);
a: Unsigned_8 := 16#12#;
b: Unsigned_8 := 16#34#;
c: Unsigned_16 := 16#0000#;

begin
c := c or Shift_Left(Interfaces.Unsigned_16(a), 8);
c := c or Shift_Left(Interfaces.Unsigned_16(b), 0);
Put("c is ");
Unsigned_16_IO.Put(Item => c, Base => 16);
New_Line;
end test;

ideone で実行


  • 暗黙の整数拡張を行わないので型変換が必要

  • 異なる整数型のオブジェクトの代入や演算は型変換が必要

  • 古いデザインにもかかわらず安全性を重視してデザインされた言語であることがわかる

  • 書式は少々古めかしい印象

  • 16進数の書き方が特に煩い


Visual Basic .NET

Imports System

Public Class Hogera
Public Shared Sub Main()
Dim a As Byte = &H12
Dim b As Byte = &H34
Dim c As UShort = &H0000

c = c Or CType(a, UShort) << 8
c = c Or CType(b, UShort) << 0
System.Console.WriteLine("c is &H" + Hex$(c))
End Sub
End Class

ideone で実行


  • 異なる整数型のオブジェクトの代入や演算に型変換は不要

  • 暗黙の整数拡張を行わず、Byte 型の変数のシフト幅は Byte 型のデータ幅である 8 の剰余が適用されるという誰得な仕様の為に型変換が必要

  • 一貫したポリシーがあるわけでもなく醜く拡張され続けた言語デザインにセンスを感じない


Fortran

      PROGRAM HOGERA

INTEGER(1) A
INTEGER(1) B
INTEGER(2) C
DATA A,B,C/Z'12',Z'34',Z'0000'/

C=INT(IOR(INT(C),ISHFT(INT(A),8)),2)
C=INT(IOR(INT(C),ISHFT(INT(B),0)),2)
WRITE (*,'("C IS Z''",Z4,"''")') C
STOP
END

ideone で実行


  • 暗黙の整数拡張を行わないので型変換が必要

  • シフト演算やビット和は小さい整数には許されないようなので一旦INTEGERに変換した


まとめ

以上の言語と CGo を登場年代順で表にまとめたものが以下です。

言語名
登場年
暗黙の整数拡張

Fortran
1957
×

C
1972

Ada
1980
×

Java
1995

Free Pascal
1997

C#
2000

D
2001

Visual Basic .NET
2001
×

Groovy
2003

Scala
2004

Nim
2008
×

Go
2009
×

Rust
2010
×

Kotlin
2011
×

Crystal
2014
×

Swift
2014
×

Raku
2015

(ここ 10年くらいの間に登場した)ナウい言語では暗黙の整数拡張は行わないことは概ね事実のようです。


おわりに

おわりです。