「実数値」から「float値の整数表現」を求める方法を調べてみた
float値は内部的には32個のビットで表現されます。Float.intBitsToFloat()を使うと、整数値で表現したビットに対応するfloat値を取得することができます。この整数表現はint型で行います。
たとえば、int型の整数値「0xC0599999(十六進数)」をFloat.intBitsToFloat()に渡すと、float値「-3.4」を取得できます。
今回は、実数値からfloat値の整数表現を求める方法を調べました。
「実数値 ⇒ 整数表現」を図解してみた
実数値から対応する整数表現を求める流れを図解してみました。この図解では、実数「-3.4」からfloat値の整数表現(十六進数)「0xC0599999」を求めています。
図解からコードを書いてみた
図解の中の○に当たる処理を、項番順に書いてみます。それぞれ処理を行うコードに続けて、各処理の結果を書いています。
2.仮数・指数を求める。
SignificandAndExponent sae = toSignificandAndExponent( absolute ); BigDecimal significand = sae.significand; BigInteger exponent = sae.exponent; System.out.println( "仮数: " + significand ); System.out.println( "指数: " + exponent ); . . // [構造体] 仮数と指数 private static class SignificandAndExponent { BigDecimal significand; // 仮数 BigInteger exponent; // 指数 } private static SignificandAndExponent toSignificandAndExponent( BigDecimal absolute ) { // BigInteger final BigInteger ZERO_INT = new BigInteger( "0" ); // 0 final BigInteger ONE_INT = new BigInteger( "1" ); // 1 final BigInteger TWO_INT = new BigInteger( "2" ); // 2 // BigDecimal final BigDecimal TWO_DEC = new BigDecimal( "2" ); // 2 BigDecimal significand = absolute; // 仮数 BigInteger exponent = ZERO_INT; // 指数 // 仮数が2より大きい間繰り返す。 while ( significand.compareTo( TWO_DEC ) > 0 ) { int scale = significand.scale(); // 計算結果のスケール // 仮数を2で割った時に、桁が増えないことを確認する。 // ※仮数の整数部分が奇数の場合、桁が増える。 if ( ONE_INT.equals( significand.unscaledValue().mod( TWO_INT ) ) ) { // 桁が増える: // 計算結果のスケールを1増やす。 ++scale; } // 仮数を2で割る。 // ※丸目が不要なように計算結果のスケールを指定している。 significand = significand.divide( TWO_DEC, scale, BigDecimal.ROUND_UNNECESSARY ); // 指数を1増やす。 exponent = exponent.add( BigInteger.ONE ); } // 戻り値の構造体を作成する。 SignificandAndExponent bak = new SignificandAndExponent(); bak.significand = significand; // 仮数 bak.exponent = exponent; // 指数 return bak; }
仮数: 1.7
指数: 1
3.仮数を補正する。
significand = adjustSignificand( significand ); System.out.println( "仮数: " + significand ); . . private static BigDecimal adjustSignificand( BigDecimal significand ) { // 仮数から1を引く。 return significand.subtract( new BigDecimal( 1 ) ); }
仮数: 0.7
4.仮数を二進数にする。
String significandBin = significandToBinary( significand ); System.out.println( "仮数(二進数) : " + significandBin ); . . private static String significandToBinary( BigDecimal significand ) { final BigDecimal ONE = new BigDecimal( 1 ); // 1 final BigDecimal TWO = new BigDecimal( 2 ); // 2 StringBuilder bak = new StringBuilder(); do { // 仮数を二倍する。 BigDecimal twice = significand.multiply( TWO ); // 仮数の二倍が0より小さいことを確認する。 if ( twice.compareTo( ONE ) < 0 ) { // 結果の末尾に0を加える。 bak.append( "0" ); // 仮数 = 仮数 × 2 significand = twice; } else { // 仮数の二倍が0以上である: // 結果の末尾に1を加える。 bak.append( "1" ); // 仮数 = 仮数 × 2 - 1 significand = twice.subtract( ONE ); } // 結果の長さが仮数の有効桁に達するまで繰り返す。 } while ( bak.length() < N ); return bak.toString(); }
仮数(二進数) : 10110011001100110011001
5.指数を補正する。
exponent = adjustExponent( exponent ); System.out.println( "指数: " + exponent ); . . private static BigInteger adjustExponent( BigInteger exponent ) { return exponent.add( E ); }
指数: 128
6.指数を二進数にする。
String exponentBin = exponentToBinary( exponent ); System.out.println( "指数(二進数) : " + exponentBin ); . . private static String exponentToBinary( BigInteger exponent ) { return exponent.toString( 2 ); }
指数(二進数) : 10000000
8.符号を二進数にする。
String signBin = signToBinary( sign ); System.out.println( "符号(二進数) : " + signBin ); . . private static String signToBinary( int sign ) { if ( sign < 0 ) { return "1"; } else { return "0"; } }
符号(二進数) : 1
9.二進数を連結する。
String binary = signBin + exponentBin + significandBin; System.out.println( "float値の整数表現(二進数): " + binary );
float値の整数表現(二進数): 11000000010110011001100110011001
10.二進数を十六進数にする
String hex = binaryToHex( binary ); System.out.println( "float値の整数表現(十六進数): " + hex ); . . private static String binaryToHex( String binary ) { StringBuffer bak = new StringBuffer(); for ( int i = 0; i < binary.length(); i += 4 ) { String substr = binary.substring( i, i + 4 ); int n = Integer.parseInt( substr, 2 ); bak.append( Integer.toHexString( n ) ); } return bak.toString(); }
float値の整数表現(十六進数): c0599999
求めた整数表現をFloat.intBitsToFloat()に渡してみた
実数値「-3.4」から求めた整数表現(十六進数)「c0599999」を、Float.intBitsToFloat()に渡してみました。
float f = Float.intBitsToFloat( 0xc0599999 ); System.out.println( "float値:" + f );
float値:-3.3999999
「-3.3999999」≒「-3.4」なのでOKです!
floatなので誤差が出ることがあります。
コードをすべて載せてみる
実数値から整数表現を求めるクラスのコードをすべて載せます。
ちなみに、float型ではなく、double型の整数表現を求めることもできます。このコードの最初の方に「floatの場合」と「doubleの場合」というコメントがあります。double型の整数表現を求めるには、「floatの場合」に続く定数宣言をコメント化し、代わりに「doubleの場合」に続く定数宣言のコメントをはずします。
import java.math.BigDecimal; import java.math.BigInteger; public class RealToIntBits { // floatの場合: private static final BigInteger E = new BigInteger( "127" ); // 指数の補正値 private static final int K = 8; // 指数の桁数 private static final int N = 23; // 仮数の桁数 // doubleの場合: // private static BigInteger E = new BigInteger( "1023" ); // 指数の補正値 // private static int K = 11; // 指数の桁数 // private static int N = 52; // 仮数の桁数 // [構造体] 仮数と指数 private static class SignificandAndExponent { BigDecimal significand; // 仮数 BigInteger exponent; // 指数 } /** * 実数からfloat値の整数表現(十六進数)を求める。 */ public static void main( String[] args ) { BigDecimal real = new BigDecimal( "-3.4" ); System.out.println( "実数: " + real ); System.out.println(); // 1.絶対値を求める。 System.out.println( "1.絶対値を求める。"); BigDecimal absolute = real.abs(); System.out.println( "絶対値: " + absolute ); System.out.println(); // 2.仮数・指数を求める。 System.out.println( "2.仮数・指数を求める。" ); SignificandAndExponent sae = toSignificandAndExponent( absolute ); BigDecimal significand = sae.significand; BigInteger exponent = sae.exponent; System.out.println( "仮数: " + significand ); System.out.println( "指数: " + exponent ); System.out.println(); // 3.仮数を補正する。 System.out.println( "3.仮数を補正する。" ); significand = adjustSignificand( significand ); System.out.println( "仮数: " + significand ); System.out.println(); // 4.仮数を二進数にする。 System.out.println( "4.仮数を二進数にする。" ); String significandBin = significandToBinary( significand ); System.out.println( "仮数(二進数) : " + significandBin ); System.out.println(); // 5.指数を補正する。 System.out.println( "5.指数を補正する。" ); exponent = adjustExponent( exponent ); System.out.println( "指数: " + exponent ); System.out.println(); // 6.指数を二進数にする。 System.out.println( "6.指数を二進数にする。" ); String exponentBin = exponentToBinary( exponent ); System.out.println( "指数(二進数) : " + exponentBin ); System.out.println(); // 7.符号を求める。 System.out.println( "7.符号を求める。" ); int sign = real.signum(); System.out.println( "符号 : " + sign ); System.out.println(); // 8.符号を二進数にする。 System.out.println( "8.符号を二進数にする。" ); String signBin = signToBinary( sign ); System.out.println( "符号(二進数) : " + signBin ); System.out.println(); // 9.二進数を連結する。 System.out.println( "9.二進数を連結する。" ); String binary = signBin + exponentBin + significandBin; System.out.println( "float値の整数表現(二進数): " + binary ); System.out.println(); // 10.二進数を十六進数にする System.out.println( "10.二進数を十六進数にする" ); String hex = binaryToHex( binary ); System.out.println( "float値の整数表現(十六進数): " + hex ); System.out.println(); } // 2.仮数・指数を求める。 private static SignificandAndExponent toSignificandAndExponent( BigDecimal absolute ) { // BigInteger final BigInteger ZERO_INT = new BigInteger( "0" ); // 0 final BigInteger ONE_INT = new BigInteger( "1" ); // 1 final BigInteger TWO_INT = new BigInteger( "2" ); // 2 // BigDecimal final BigDecimal TWO_DEC = new BigDecimal( "2" ); // 2 BigDecimal significand = absolute; // 仮数 BigInteger exponent = ZERO_INT; // 指数 // 仮数が2より大きい間繰り返す。 while ( significand.compareTo( TWO_DEC ) > 0 ) { int scale = significand.scale(); // 計算結果のスケール // 仮数を2で割った時に、桁が増えないことを確認する。 // ※仮数の整数部分が奇数の場合、桁が増える。 if ( ONE_INT.equals( significand.unscaledValue().mod( TWO_INT ) ) ) { // 桁が増える: // 計算結果のスケールを1増やす。 ++scale; } // 仮数を2で割る。 // ※丸目が不要なように計算結果のスケールを指定している。 significand = significand.divide( TWO_DEC, scale, BigDecimal.ROUND_UNNECESSARY ); // 指数を1増やす。 exponent = exponent.add( BigInteger.ONE ); } // 戻り値の構造体を作成する。 SignificandAndExponent bak = new SignificandAndExponent(); bak.significand = significand; // 仮数 bak.exponent = exponent; // 指数 return bak; } // 3.仮数を補正する。 private static BigDecimal adjustSignificand( BigDecimal significand ) { // 仮数から1を引く。 return significand.subtract( new BigDecimal( 1 ) ); } // 4.仮数を二進数にする。 private static String significandToBinary( BigDecimal significand ) { final BigDecimal ONE = new BigDecimal( 1 ); // 1 final BigDecimal TWO = new BigDecimal( 2 ); // 2 StringBuilder bak = new StringBuilder(); do { // 仮数を二倍する。 BigDecimal twice = significand.multiply( TWO ); // 仮数の二倍が0より小さいことを確認する。 if ( twice.compareTo( ONE ) < 0 ) { // 結果の末尾に0を加える。 bak.append( "0" ); // 仮数 = 仮数 × 2 significand = twice; } else { // 仮数の二倍が0以上である: // 結果の末尾に1を加える。 bak.append( "1" ); // 仮数 = 仮数 × 2 - 1 significand = twice.subtract( ONE ); } // 結果の長さが仮数の有効桁に達するまで繰り返す。 } while ( bak.length() < N ); return bak.toString(); } // 5.指数を補正する。 private static BigInteger adjustExponent( BigInteger exponent ) { return exponent.add( E ); } // 6.指数を二進数にする。 private static String exponentToBinary( BigInteger exponent ) { return exponent.toString( 2 ); } // 8.符号を二進数にする。 private static String signToBinary( int sign ) { if ( sign < 0 ) { return "1"; } else { return "0"; } } // 10.二進数を十六進数にする private static String binaryToHex( String binary ) { StringBuffer bak = new StringBuffer(); for ( int i = 0; i < binary.length(); i += 4 ) { String substr = binary.substring( i, i + 4 ); int n = Integer.parseInt( substr, 2 ); bak.append( Integer.toHexString( n ) ); } return bak.toString(); } }