LoginSignup
2

More than 5 years have passed since last update.

NaN や INF を int に変換したらどうなるか調べてみた

Last updated at Posted at 2018-12-15

浮動小数点の非数や無限を int に変換したらどうなるのかを、いろいろな処理系で。

環境は主に macOS Mojave 10.14.2。
cl.exe(C言語) と C# だけは windows 10(64bit)。
いずれも CPU は x64。

ARM でどうなるのかも興味あるけど手元に環境がない。
だれか記事書きませんか?

C言語

ソースコードはこんな

c99
#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

ソースコードはこんな

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

ソースコードはこんな

python2or3
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

ソースコードはこんな

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#

ソースコードはこんな

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

ソースコードはこんな

Java1.8
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)

node-v11.3.0
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

ソースコードはこんな

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

ソースコードはこんな

php7
<?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

ソースコードはこんな

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ぐらいなのかな。よくわからないまま書いてみた。こんな

fortran
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 を書いてみた。

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

という具合かな。

とまあ

とまあいろいろ試してみた。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2