浮動小数点の非数や無限を int に変換したらどうなるのかを、いろいろな処理系で。
環境は主に macOS Mojave 10.14.2。
cl.exe(C言語) と C# だけは windows 10(64bit)。
いずれも CPU は x64。
ARM でどうなるのかも興味あるけど手元に環境がない。
だれか記事書きませんか?
C言語
ソースコードはこんな
# include <stdio.h>
double div(double x, double y ){
return x/y;
}
int main()
{
double nan = div(0.0,0.0);
double pinf = div(1.0, 0.0);
double minf = -pinf;
printf("%f | %f | %f |\n", nan, pinf, minf);
printf("%d | %d | %d |\n", (int)nan, (int)pinf, (int)minf);
return 0;
}
感じ。
処理系 | NaN | +INF | -INF |
---|---|---|---|
clang-1000.11.45.5 | -2147483648 | -2147483648 | -2147483648 |
gcc-8 (Homebrew GCC 8.2.0) 8.2.0 | -2147483648 | -2147483648 | -2147483648 |
cl.exe 19.16.27025.1 | -2147483648 | -2147483648 | -2147483648 |
C言語的には「未定義動作」だと思う。
clang と gcc については 2015年の MacBookPro なので、x64。
cl.exe(VisualStudio)も x64。x86 ではビルドしなかった。
ちなみに div
が関数になっているのは double nan = 0.0/0.0;
は cl.exe でコンパイルエラーになるから。コンパイル時にはエラーになるけど実行時には nan になる。
ruby
ソースコードはこんな
def toi(f)
begin
f.to_i
rescue => e
e.inspect.gsub( /\W+/, " " ).strip
end
end
values = [Float::NAN, Float::INFINITY, -Float::INFINITY]
puts values.map{ |f| toi(f).inspect }.join("|")
感じ。
処理系 | NaN | +INF | -INF |
---|---|---|---|
ruby 2.5.3p105 | "FloatDomainError NaN" | "FloatDomainError Infinity" | "FloatDomainError Infinity" |
jruby 9.2.0.0 (2.5.0) | "FloatDomainError NaN" | "FloatDomainError Infinity" | "FloatDomainError Infinity" |
意外と例外になる。 |
jruby では、当然 ruby 合わせで、java(後述)とは違う結果になる。当たり前か。
python
ソースコードはこんな
import sys
import re
import numpy as np
def toi(f):
try:
return int(f)
except:
return re.sub( r"\W+", " ", str(sys.exc_info()[0]) )
nan = float("nan")
pinf = float("inf")
minf = -pinf
ints = [ toi(x) for x in [ nan, pinf, minf ] ]
print( "|".join( ints ) )
npa = np.array([nan, pinf, minf])
print( npa.astype("int32"))
感じ。
処理系 | 手段 | NaN | +INF | -INF |
---|---|---|---|---|
Python 3.7.1 | int() | class ValueError | class OverflowError | class OverflowError |
Python 2.7.15 | int() | type exceptions ValueError | type exceptions OverflowError | type exceptions OverflowError |
Python 3.7.1 | .astype("int32") | -2147483648 | -2147483648 | -2147483648 |
Python 2.7.15 | .astype("int32") | -2147483648 | -2147483648 | -2147483648 |
int() で作ると例外。まあそうだよね。
numpy だと、例外なんて投げているヒマがない。C言語と同じになった。
go
ソースコードはこんな
package main
import (
"fmt"
"math"
)
func main() {
nan := math.NaN()
pinf := math.Inf(1)
minf := math.Inf(-1)
fmt.Printf("%d|%d|%d|\n", int32(nan), int32(pinf), int32(minf))
}
感じ。
処理系 | NaN | +INF | -INF |
---|---|---|---|
go1.11.2 darwin/amd64 | -2147483648 | -2147483648 | -2147483648 |
panic にするのはやりすぎだし、例外はないので C言語みたいに変な値にするしかない。
Go プログラミング言語仕様 によると
浮動小数点値または複素数を含んでいるすべての非定数変換において、結果となる型が値を表現することができなければ、変換自体は成功しますが、結果となる値は実装依存となります。
ということで、実装依存らしい。
C#
ソースコードはこんな
using System;
namespace NanToInt
{
class M
{
static void Main()
{
double nan = double.NaN;
double pinf = double.PositiveInfinity;
double minf = double.NegativeInfinity;
int inan = (int)nan;
int ipinf = (int)pinf;
int iminf = (int)minf;
Console.WriteLine( "{0}|{1}|{2}|", inan, ipinf, iminf );
}
}
}
感じ。
処理系 | NaN | +INF | -INF |
---|---|---|---|
csc 2.10.0.0 | -2147483648 | -2147483648 | -2147483648 |
例外にはならずに、clang や gcc と同じ値になる。
ちなみに
(decimal)double.NaN
とやると、やや不適切なメッセージの
「System.OverflowException: Decimal 型の値が大きすぎるか、または小さすぎます。
」
という例外になる。
Java
ソースコードはこんな
class NanToIntTest {
public static void main( String[] args ){
int inan = (int)Double.NaN;
int ipinf = (int)Double.POSITIVE_INFINITY;
int iminf = (int)Double.NEGATIVE_INFINITY;
System.out.printf("%d|%d|%d\n", inan, ipinf, iminf);
}
}
感じ。
久々に「public static void main
」って書いた。
処理系 | NaN | +INF | -INF |
---|---|---|---|
java version "1.8.0_60" | 0 | 2147483647 | -2147483648 |
Java は例外にはならない。
clang, gcc や go とは違う値になる。
ちなみに new BigDecimal(Double.NaN);
は「java.lang.NumberFormatException: Infinite or NaN
」という例外になる。
groovy
ソースコードはこんな
int inan = (int)Double.NaN;
int ipinf = (int)Double.POSITIVE_INFINITY;
int iminf = (int)Double.NEGATIVE_INFINITY;
printf("%d|%d|%d\n", inan, ipinf, iminf);
感じ。
処理系 | NaN | +INF | -INF |
---|---|---|---|
Groovy Version: 2.5.4 JVM: 1.8.0_60 | 0 | 2147483647 | -2147483648 |
やはり Java と同じ結果になる。
JavaScript(nodejs)
var values = [0.0 / 0.0, 1.0 / 0.0, -1.0 / 0.0]
console.log(["or op."].concat(values.map(x => x | 0)).join("|"))
console.log(["and op."].concat(values.map(x => x & 0xffffffff)).join("|"))
function u16a(x){
a = new Uint16Array(1);
a[0] = x;
return a[0];
}
function i16a(x){
a = new Int16Array(1);
a[0] = x;
return a[0];
}
console.log(["u16a"].concat(values.map(x=>u16a(x))).join("|"))
console.log(["i16a"].concat(values.map(x=>i16a(x))).join("|"))
処理系は node.js の v11.3.0。
手段 | NaN | +INF | -INF |
---|---|---|---|
or op. | 0 | 0 | 0 |
and op. | 0 | 0 | 0 |
u16a | 0 | 0 | 0 |
i16a | 0 | 0 | 0 |
32bit の整数にしようとしたり、ビット数の限られた整数型に突っ込んだりすると、ゼロになる。
math.floor(x)
みたいなことをすると NaN
とか Infinity
になるので面白くない。
Rust
ソースコードはこんな
fn main() {
let nan = std::f64::NAN;
let pinf = std::f64::INFINITY;
let minf = -std::f64::INFINITY;
println!("{}|{}|{}", nan as i32, pinf as i32, minf as i32);
}
感じ。
意外と例外にならない。結果は
処理系 | NaN | +INF | -INF |
---|---|---|---|
rustc 1.30.1 | -2147483648 | -2147483648 | -2147483648 |
なんだけど、型間のキャスト によると
注意: 現在、丸められた値がキャスト先の整数型で扱えない場合、このキャストは未定義動作を引き起こします。 これには Inf や NaN も含まれます。 これはバグであり、修正される予定です。
とのことで、修正 されるらしい。
PHP
ソースコードはこんな
<?php
$nan = 0.0/0.0;
$pinf = 1.0/0.0;
$minf = -1.0/0.0;
echo( join("|", [ (int)$nan, (int)$pinf, (int)$minf ])."\n" );
echo( join("|", [ intval($nan), intval($pinf), intval($minf) ])."\n" );
?>
感じでいいのかしら。わかりません。
結果は意外にも
処理系 | 手段 | NaN | +INF | -INF |
---|---|---|---|---|
PHP 7.1.19 | (int) | 0 | 0 | 0 |
PHP 7.1.19 | intval() | 0 | 0 | 0 |
と、全部ゼロになる。
perl
ソースコードはこんな
use strict;
use warnings;
my $pinf = 9**9**9;
my $nan = $pinf - $pinf;
my $minf = -$pinf;
my $inan = 0|$nan;
my $ipinf = 0|$pinf;
my $iminf = 0|$minf;
printf "%x|%x|%x\n", $inan, $ipinf, $iminf;
感じかな。
結果は下表の通り:
処理系 | NaN | +INF | -INF |
---|---|---|---|
perl v5.18.2 | 0 | ffffffffffffffff | 8000000000000000 |
わかりにくい値になっている。
int x
だと nan のままになったりするので面白くない。
Swift
生まれて初めて Swift を書いてみたものの、うまく書けず敗北。
print(
Int(Double.nan), "|",
Int(Double.infinity), "|",
Int(-Double.infinity), "|")
実行すると
Fatal error: Double value cannot be converted to Int because it is either infinite or NaN
となる。(先頭の1行のみ出したけど、本当は10行以上のエラーメッセージが出る)
このエラーをキャッチしてエラーの種別を出力したかったんだけど、キャッチする方法がわからず敗退。
Swift 難しい。
とにかく。Swift の場合は NaN や INF を Int()
で整数にしようとすると「Fatal error
」になるらしい。恐ろしい。
なお。
Swift は xcrun swift
で実行していて、 xcrun swift --version
で出てくるバージョンは
Apple Swift version 4.2.1 (swiftlang-1000.11.42 clang-1000.11.45.1)
Target: x86_64-apple-darwin18.2.0
となっている。
fortran
fortran2003ぐらいなのかな。よくわからないまま書いてみた。こんな
function div(a,b)
real a, b
div = a/b
end function
program main
real :: nan
real :: pinf
real :: minf
nan = div(0.0,0.0)
pinf = div(1.0,0.0)
minf = -pinf
print *, int(nan), "|", int(pinf), "|", int(minf)
end
感じ。合ってる?
環境は GNU Fortran (Homebrew GCC 8.2.0) 8.2.0。
結果は
処理系 | NaN | +INF | -INF |
---|---|---|---|
gfortran8.2 | -2147483648 | -2147483648 | -2147483648 |
と、C言語と同じ。CPU 依存なのかなぁ。
Dart
生まれて初めて Dart を書いてみた。
import 'dart:io';
toIntStr( n ){
try{
return n.toInt().toString();
}
catch(e){
return "exception";
}
}
void main() {
stdout.write(toIntStr(double.nan));
stdout.write("|");
stdout.write(toIntStr(double.infinity));
stdout.write("|");
stdout.write(toIntStr(double.negativeInfinity));
print("|");
}
こんな感じ。
結果は
処理系 | NaN | +INF | -INF |
---|---|---|---|
Dart VM version: 2.1.0 | exception | exception | exception |
例外は
「Unsupported operation: Infinity or NaN toInt
」
という内容。
Haskell
ソースコードはこんな
nan = 0.0/0.0
pinf = 1.0/0.0
minf = -pinf
toint :: (Double->Int)->Double->Int
toint f x = f(x)
table t f =
"|"++t++"|"++
(show $ toint f nan)++"|"++
(show $ toint f pinf)++"|"++
(show $ toint f minf)++"|"
main = do
putStrLn $ table "round" round
putStrLn $ table "truncate" truncate
putStrLn $ table "ceiling" ceiling
putStrLn $ table "floor" floor
感じ。
処理系は The Glorious Glasgow Haskell Compilation System, version 8.4.4
。
実行すると
関数 | NaN | +INF | -INF |
---|---|---|---|
round | 0 | 0 | 0 |
truncate | 0 | 0 | 0 |
ceiling | 0 | 0 | 0 |
floor | 0 | 0 | 0 |
と、意外とゼロになる。
例外とかはないんだっけ。
まとめ
表にした。
処理系 | 手段 | NaN | +INF | -INF |
---|---|---|---|---|
C99 on amd64 | キャスト | -2147483648 | -2147483648 | -2147483648 |
ruby 2.5 | .to_i | 例外 | 例外 | 例外 |
python | int() | 例外 | 例外 | 例外 |
python | numpy の .astype | -2147483648 | -2147483648 | -2147483648 |
go on amd64 | int32() | -2147483648 | -2147483648 | -2147483648 |
C# | (int) | -2147483648 | -2147483648 | -2147483648 |
Java1.8 | (int) | 0 | 2147483647 | -2147483648 |
Groovy(JVM1.8) | (int) | 0 | 2147483647 | -2147483648 |
JavaScript(nodejs) | or op. | 0 | 0 | 0 |
JavaScript(nodejs) | Uint16Array | 0 | 0 | 0 |
Rust on amd64 | as i32 | -2147483648 | -2147483648 | -2147483648 |
PHP 7.1.19 | (int), intval() | 0 | 0 | 0 |
perl5.18 | or op. | 0 | ffffffffffffffff | 8000000000000000 |
Swift4.2.1 | Int() | Fatal error | Fatal error | Fatal error |
gfortran8.2 | int() | -2147483648 | -2147483648 | -2147483648 |
Dart2.1 | .toInt() | 例外 | 例外 | 例外 |
Haskell(GHC8.4) | round等 | 0 | 0 | 0 |
結果 | 処理系 |
---|---|
-2147483648 になる | C99, numpy, go ,Rust, gfortran (すべて amd64) |
例外になる | ruby, python, Dart |
0 になる | JavaScript, PHP7, Haskell |
Fatal error になって死ぬ | Swift4 |
その他 | Java, Groovy, Perl5 |
という具合かな。
とまあ
とまあいろいろ試してみた。