• 締切済み

byte配列→float型の値を読みlongに変換

UTF-8 encodingで書かれたテキスト分とfloat型 (IEEE-754/octet-stream)で書かれたバイナリ分混ざったファイルがあります。そのファイルをdataInputStreamを使って読んで、byte配列に保存されています。 FileInputStream fis = new FileInputStream(ファイル); DataInputStream dis = new DataInputStream(fis); byte[] bs = new byte[適切なサイズ]; //読み込み dis.read(bs); byte配列「bs」のindex 0 から 5までテキスト情報(XML式のヘッダー情報)あると仮定します。 UTF-8の分は for loop で "new String(bs, loop index, 1, "ASCII")"すると読み取れます。 問題は以下: index 6 から 17まではfloat型のバイナリデータ保存されているとします。 たとえば、index 6 から 順番に以下のHex string があります。 01 2A 5F 4E   B4 F2 45 4E   92 6A 5F 4E (4つのグループにしたのは分かりやすくするため) 上記float型のHex String をlong intに変換して、別途用意された配列に保存したい。 ネット検索でByteBufferにwrapメソッドを使って、getFloat(index) 又は getFloat()でfloat型取得できると書いていますがその先はどうやってlong int変換できるのでしょうか? 上記のHex string の変換後の値(取扱いしたい値)は19554126, 3035776334, 2456444750です。 今までのコード: long [] data = new long [既知のサイズ]; ByteBuffer b = ByteBuffer.wrap(bs).order(ByteOrder.LITTLE_ENDIAN); //なんらかのloop 又は ByteBuffer b の最後まで読める仕組み(remainingというメソッド?){ Float f = b.getFloat(); 又は Float f = b.getFloat(loop index); data[index] = fの longに変換した値 // つまり 19554126, 3035776334 .... } ネットで探していますが、やり方まだわからない。 どなたか、教えてください。 よろしくお願いします。

みんなの回答

  • f272
  • ベストアンサー率46% (8477/18146)
回答No.7

01 2A 5F 4E (A) このビットパターンを16ビットの整数(LITTLE_ENDIAN)として解釈すれば「1314859521」です。 (B) このビットパターンを16ビットの浮動小数(LITTLE_ENDIAN)として解釈すれば「936017984.0」です。 > floatToIntBitsの仕様でIEEE-754準拠の様で、「1314859521」が正しいのではないですか? Float.floatToIntBits(float)関数は「返される値は整数型であり」と書いてあるとおり「1314859521」を返すのであって,「IEEE 754 浮動小数点「単精度」ビット配列に従った、浮動小数点表現の引数」を整数として解釈したらどうなるかを見ているのです。もし浮動小数点値として解釈すれば「936017984.0」が返ってくるはずです。float.intBitsToFloat(1314859521)を計算してください。 また, (C) このビットパターンを16ビットの整数(BIG_ENDIAN)として解釈すれば「19554126」です。 (D) このビットパターンを16ビットの浮動小数(BIG_ENDIAN)として解釈すれば「3.12924e-038」です。 さらに言えば勉強のために行っておくと 4E 5F 2A 01 (E) このビットパターンを16ビットの整数(LITTLE_ENDIAN)として解釈すれば「19554126」です。 (F) このビットパターンを16ビットの浮動小数(LITTLE_ENDIAN)として解釈すれば「3.12924e-038」です。 またエクセルのHex2Dec関数はBIG_ENDIANで解釈しますからHEX2DEC("012A5F4E")=19554126ですね。 整理します。 > ソースファイルをHexエディターで読むと以前記述した通り、012A5F4E B4F2454E 926A5F4E...のように表示されます。 これはエンディアンに関係なくデータがこういう値だということを示しています。 次に > 仕様: > IEEE-754 32ビット float型 little-endian式 負のデータはない、すべて 「正」 です。値は数値の10~11桁ぐらいです。 これにしたがってデータを解釈します。 すると (B) これを16ビットの浮動小数(LITTLE_ENDIAN)として解釈すれば「936017984.0」です。 の解釈が正しいことがわかるのです。

x201s-goo
質問者

お礼

f272さん 返事遅れましたが、コメット、説明ありがとうございます。 みなさんにお世話になりました。 ちなみに、f272さんは「16ビット」書いていますが「32ビット」のことでしょうね? この問題で取扱いしているファイルの仕様は32ビット"Single Precision IEEE754 Float"ですので。

  • salsberry
  • ベストアンサー率69% (495/711)
回答No.6

> little-endian 選択された状態で、012A5F4Eの10進数の値が「1314859521」表示します これは4バイト(32ビット)の16進数0x4E5F2A01を整数値と解釈して10進で表示しただけです。このときIEEE 754は考慮されていません。 > ExcelにHex2Decといつ関数ありました。試してみたら結果は「19554126」。 これも16進で表記された値を整数値と解釈して10進表示しているだけですね。やはりIEEE 754は考慮されていません。また、データをビッグエンディアンで扱っています。 > サイトの"Hexa representation"のところに「012A5F4E」入力してENTER押すとDecimalの値に「3.1292446E-38」表示されました!試しているデータは「正」のみのはずですので何かおかしいと思います。 3.1292446E-38は正の値です。変換自体も正しいです。 データがリトルエンディアンであるならば、012A5F4Eではなく4E5F2A01を入力すべきでした。 float x = Float.intBitsToFloat(0x4E5F2A01); long y = (long)x; System.out.println(y);

x201s-goo
質問者

お礼

salsberryさん コメントありがとうございます。 以下、kmeeさんのところに書きましたがみなさんのコメントで大分わかってきました。 ツール(Hexエディターのこと)があると言って、使い方又は裏側の動きをわからないと誤解した結果出ますね。危ない! 勉強になっています。

  • kmee
  • ベストアンサー率55% (1857/3366)
回答No.5

○エンディアン http://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%B3%E3%83%87%E3%82%A3%E3%82%A2%E3%83%B3 01 2A 5F 4E という4バイトがあったとき、これを32bitにするときに 0x012A5F4E という順番にするのがビッグエンディアン 0x4E5F2A01 という順番にするのがリトルエンディアンです この32bitの列がどんな意味を持つかは、解釈次第です。 エンディアンだけでは判断できません。 i.Hex等のほとんどのバイナリエディタでは、これをそのまま整数として解釈します。 IEEE754としての値を表示するものは、あまり無いと思います。 ....って、先日質問して解決したのでは?なぜこれで確認しないのですか? http://okwave.jp/qa/q8231380.html 同じバイト列/32bit列でも、整数として解釈するのと、IEEE754として解釈するのとでは、値が違います。 同じ数値でも、整数で表現するのと、floatで表現するのとでは、バイト列が違います。 例で見ましょう。 1 を考えます。 32bit整数なら 0x00000001 です。 リトルエンディアンなら 01 00 00 00 です。 IEEE754で考えます。 1=1.0 = 1.0 * 2の0乗 です。これを分解すると 符号: 正→0 仮数: 1.0 の小数部 0.0 → 000..0 (2進23ビット) 指数: 0 → 127を加えた 127= 01111111(2進8ビット) これらを仕様通りに並べて32bitにすると 0x3f800000 です。 リトルエンディアンなら 00 00 80 3f です。 このバイト列をリトルエンディアンでgetFloatすれば 1.0 になり、intにキャストすれば 1 になります。 intの1とfloatの1.0が== だということはいいですよね? ですが、バイト列にすると 01 00 00 00 と 00 00 80 3f とまったく別なものになります。 あなたの確認方法と言うのは、この0x3f800000 を整数として10進表記した1065353216 が「正解」だと思い込み、getFloatで出た 1.0 を「間違いだ」と言っているのです。 > Hex文字列の10進数の値(私が欲しい値はこれです。) あなたが欲しいのは「float型 (IEEE-754/octet-stream)」ですよね? そのi.Hexで表示されているのは、32bit整数として処理した10進数であって、IEEE754として処理した10進数ではありません。 > 「3.1292446E-38」表示されました!試しているデータは「正」のみのはずですので何かおかしいと思います。 これは、正の値です。 このEを使った表記は、コンピュータでよく使われるもので、 3.1292446E-38 = 3.1292446 * 10の(-38)乗 の意味です。 通常のテキストでは、数学のように上付き文字で表現するは困難です。そのため、このような e という文字で10のn乗を表現します。 > floatToIntBits このメソッドは、getFloat の逆変換のようなものです。 「バイト列→エンディアンに従って32bit→ floatとして解釈した数値 (getFloat)→ floatの中部表現の32bit(getFloatの逆)」 としたのですから getFloat→floatToBitsで変換が打ち消されて 「バイト列→エンディアンに従って32bit整数に」 と同じ結果になります。i.Hexでの10進表示の整数と同じになります。 ですが、意味の無い比較です。

x201s-goo
質問者

お礼

kmeeさん すこしづつ理解しつつあります。 とくに、今まで見ていたHexエディターは32ビット整数で、IEEE754 floatの値を表示していなかったのがショックです。 どうしてというとデータがIEEE754 floatで書かれたのでそのツールは読むときに書かれた式で読んでほしかったからです。ツールの仕様読めばどこかには書いていたんでしょうね。 ちなみに昨日別のツール(FlexHex)見つけてそれも使っています。このツールは四つのHex文字列選択するとDecimal値、Flaot値表示してくれます。IEEE754のFloatかどうか、これから確認します。 >あなたが欲しいのは「float型 (IEEE-754/octet-stream)」ですよね? これは私の書き方と考えていることが違かったかな。 ストリームから32ビットバイナリー("IEEE754 Float Single Format")を読み取ってバイト配列に格納します。複数ファイルからのデータ集めて、いろいろ計算してグラフ作ります。その時に縦軸に値を表示します。その値は毎日扱っている10進法の数値にしたいです。実際は科学雑誌のようにEを使った表記で表示します。 ここで、そのEを使った表記の件、それぐらいは分かっていましたが、あわてたというか、圧迫感を感じたか、うまく整理できず「マイナス」の値と思いました。 みなさん丁寧に書いてくださって前に進む材料になっています。 ありがとうございます。 大変混乱していて、かつ、急にほかの作業も入ったので返事遅くなります。すみません。

  • kmee
  • ベストアンサー率55% (1857/3366)
回答No.4

http://ja.wikipedia.org/wiki/IEEE_754#32.E3.83.93.E3.83.83.E3.83.88.E5.8D.98.E7.B2.BE.E5.BA.A6 float(32bit)のバイナリ、というのは↑のようなことです。16進数整数をそのまま使うのではなく、ビット毎に意味があるので、それに合せた計算をします。 > Long.parseLong("012A5F4E", 16)すると long型の値「19554126」 これは、16進整数を求めただけで、IEEE 754形式の計算はしていません。 しかも、ビッグエンディアンになっていて「リトルエンディアンになっている」という仕様と矛盾しています。 01 2A 5F 4E をリトルエンディアンとして当てはめると 32bit: 0x4E5F2A01 ↓ 符号: 0 指数: 0x9C -127 = 156 -127 = 29 ※ 0x9cは ( (0x4E<<1) | ((5 & 8)>>3) ) から 仮数: ( ( 0x5F2A01 & 0x7fffff) | 0x800000 ) * 2^(-23 = 0xDF2A01 * 2^(-23) = 14625281 * 2^(-23) ※ | 0x800000 で仮数部の最上位(23ビット目)を1にする。これが1の位なので、2^23で割る ↓ (14625281 * 2^(-23)) * 2^29 = 1425281 * 2^6 = 1425281 * 64 = 91217984

x201s-goo
質問者

お礼

kmeeさん ご丁寧なコメントありがとうございます。 どこからはじめよかな。私は何も分かってないことを実感しています。 ネット調べて見てもまだ「正解」わかっていないのでかなり迷っています。 説明させてください。最初からきちんと書けなかったこと、申し訳ありません。 仕様: IEEE-754 32ビット float型 little-endian式 負のデータはない、すべて 「正」 です。値は数値の10~11桁ぐらいです。 せっかく計算例まで書いていただきましたが、今の時点では理解できない。勉強します! それとは別、説明+疑問点整理します。 ソースファイルをHexエディターで読むと以前記述した通り、012A5F4E B4F2454E 926A5F4E...のように表示されます。 Hex EditorNeoというフリーソフトではそのHex文字列の10進数の値(私が欲しい値はこれです。)は表示できず、別のフリーエディター(i.Hex)使いました。このi.Hexはbig/little endian 選択できて、10進数の値も表示してくれます。 little-endian 選択された状態で、012A5F4Eの10進数の値が「1314859521」表示します。 今度、java側でFloat.floatToIntBits(float)関数見つかりました。 API見ると「単精度 float 値のビット表現を返します。結果は、IEEE 754 浮動小数点「単精度」ビット配列に従った、浮動小数点表現の引数になります。」と書いてあります。 ※docs.oracle.com/javase/jp/1.3/api/java/lang/Float.html#floatToIntBits(float) で、その関数使いました。 (1) ※以下、bsはバイト配列。dataInputStream使って、ファイルを読んで、格納してバイト配列 ByteBuffer b = ByteBuffer.wrap(bs).order(ByteOrder.BIG_ENDIAN); Float f = b.getFloat(0); System.out.println("Value = " + Float.floatToIntBits(f)); 結果は「19554126」です。 (2)ByteOrder.BIG_ENDIANの代わりにByteOrder.LITTLE_ENDIANすると結果は 「1314859521」になります。 i.Hexエディターが表示してくれた値と同じです。 floatToIntBitsの仕様でIEEE-754準拠の様で、「1314859521」が正しいのではないですか? そこで、オンラインでIEEE-754変換サイト見つけて試して見ましたが、混乱する値でした。(又は私の理解不足) サイト:h-schmidt.net/FloatConverter/ サイトの"Hexa representation"のところに「012A5F4E」入力してENTER押すとDecimalの値に「3.1292446E-38」表示されました!試しているデータは「正」のみのはずですので何かおかしいと思います。 ちなみに、ExcelにHex2Decといつ関数ありました。試してみたら結果は「19554126」。 まさにkmeeさんが書いた通り数値の「かたち」している16進整数ですね。 長くなりましたが、よろしくお願いします。

  • f272
  • ベストアンサー率46% (8477/18146)
回答No.3

> 私はもらった資料ではリトル・エンディアンになっていて > データの設計書にデータはfloat型(IEEE-754)で格納されていること書かれて を前提にすれば 01 2A 5F 4E は936017984.0だからそれをlong intにすれば936017984で正しいよ。 ビッグエンディアンではと言ったのは01 2A 5F 4Eを19554126にしたいと言ってたからです。

x201s-goo
質問者

補足

f272さん コメントありがとうございます。 kmeeさんに書いた通り、float型(IEEE-754)で書かれたバイナリデータの10進値で表示したい、理解不足で困っています。 >(私)データは間違えないのでgetIntでなく、getFloatでよい お詫びします。 必ずしもそうではないかもと思いました。 理由は、 ByteBuffer b = ByteBuffer.wrap(bs).order(ByteOrder.LITTLE_ENDIAN); System.out.println("value: " +b.getInt(0)); の結果が 「1314859521」で kmeeさんのところに書いた通りFloat.floatToIntBits(b.getFloat(0))の結果と同じです。 ※getFloatでもgetIntでも同じ結果?これでいいかどうか、それもわからないですが。 今度、(long)b.getFloat(0)値(=936017984)ですが正しくないように見えますが、何が正しいか分かりません。 10~11桁の値、50~60個あって、それらを足したり、いろいろ計算する必要があるのでlong int 型 使おうと思って、(long)b.getFloat(0)したのです。 >ビッグエンディアンではと言ったのは01 2A 5F 4Eを19554126にしたいと言ってたからです。 19554126は、ビッグエンディアンの場合の値であること今日いろいろ試して理解しました。 ありがとうございました。

  • f272
  • ベストアンサー率46% (8477/18146)
回答No.2

何だかよくわからないが, (1) > 順番に以下のHex string があります。 > 01 2A 5F 4E   B4 F2 45 4E   92 6A 5F 4E (4つのグループにしたのは分かりやすくするため) この順序になっているのならビッグエンディアンなのでは? (2) 4バイトで区切って読むのならgetIntで読むのでは? (3) long intにするのは単にキャストするだけでは?

x201s-goo
質問者

お礼

f272さん コメントありがとうございます。 エンディアンについて:私はもらった資料ではリトル・エンディアンになっていて、私がサンプルデータ作った時にそのような順番になった可能性があります。私はビットごとデータの値を見るだけでビッグかリトルか、分かりません。 データの設計書にデータはfloat型(IEEE-754)で格納されていること書かれて、データは間違えないのでgetIntでなく、getFloatでよいとおもいますが。。 ByteBufferのAPIを見るとどの場合でも(getIntでもgetFloatでも)4バイト読めます。ただし、リターン値はfloatかintかの違いだけのようです。 キャスト: ByteBufferにgetFloat(index)でいくつかfloat値ゲットしてキャストしてみましたが正しい値が出ません。 正しくないの説明: floatデータファイルをバイナリエディター(例)Hexeditorで読み込ませてHex stringの値を見ると012A5F4E B4F2454E 926A5F4E...のように表示されます。 Long.parseLong("012A5F4E", 16)すると long型の値「19554126」です。 尚、 ByteBuffer b = ByteBuffer.wrap(bs).order(ByteOrder.LITTLE_ENDIAN); System.out.println("float value: " +(long)b.getFloat(0));すると「936017984」が表示されます。違いますよね。 なにかおかしいけど、理由は分からないです。 他の案あれば聞かせてください。 よろしくお願いします。

  • hitomura
  • ベストアンサー率48% (325/664)
回答No.1
x201s-goo
質問者

お礼

hitomuraさん コメントありがとうございます。 前の方に返事しました通り、キャストうまくいかないです。 何かアイディアありましたらアドバイスお願いします。

関連するQ&A