Skip to content

Commit 8a0ee2c

Browse files
authored
Added API for reading and writing of floating point numbers (#193)
Implemented extension functions for reading and writing values with types Float and Double Resolves #167
1 parent f338e2d commit 8a0ee2c

File tree

6 files changed

+285
-0
lines changed

6 files changed

+285
-0
lines changed

core/api/kotlinx-io-core.api

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ public final class kotlinx/io/SinksJvmKt {
115115

116116
public final class kotlinx/io/SinksKt {
117117
public static final fun writeDecimalLong (Lkotlinx/io/Sink;J)V
118+
public static final fun writeDouble (Lkotlinx/io/Sink;D)V
119+
public static final fun writeDoubleLe (Lkotlinx/io/Sink;D)V
120+
public static final fun writeFloat (Lkotlinx/io/Sink;F)V
121+
public static final fun writeFloatLe (Lkotlinx/io/Sink;F)V
118122
public static final fun writeHexadecimalUnsignedLong (Lkotlinx/io/Sink;J)V
119123
public static final fun writeIntLe (Lkotlinx/io/Sink;I)V
120124
public static final fun writeLongLe (Lkotlinx/io/Sink;J)V
@@ -160,6 +164,10 @@ public final class kotlinx/io/SourcesKt {
160164
public static final fun readByteArray (Lkotlinx/io/Source;)[B
161165
public static final fun readByteArray (Lkotlinx/io/Source;I)[B
162166
public static final fun readDecimalLong (Lkotlinx/io/Source;)J
167+
public static final fun readDouble (Lkotlinx/io/Source;)D
168+
public static final fun readDoubleLe (Lkotlinx/io/Source;)D
169+
public static final fun readFloat (Lkotlinx/io/Source;)F
170+
public static final fun readFloatLe (Lkotlinx/io/Source;)F
163171
public static final fun readHexadecimalUnsignedLong (Lkotlinx/io/Source;)J
164172
public static final fun readIntLe (Lkotlinx/io/Source;)I
165173
public static final fun readLongLe (Lkotlinx/io/Source;)J

core/common/src/Sinks.kt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,78 @@ public fun Sink.writeULongLe(long: ULong) {
275275
writeLongLe(long.toLong())
276276
}
277277

278+
/**
279+
* Writes four bytes of a bit representation of [float], in the big-endian order, to this sink.
280+
* Bit representation of the [float] corresponds to the IEEE 754 floating-point "single format" bit layout.
281+
*
282+
* To obtain a bit representation, the [Float.toBits] function is used.
283+
*
284+
* Should be used with care when working with special values (like `NaN`) as bit patterns obtained for [Float.NaN] may vary depending on a platform.
285+
*
286+
* @param float the floating point number to be written.
287+
*
288+
* @throws IllegalStateException when the sink is closed.
289+
*
290+
* @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.writeFloat
291+
*/
292+
public fun Sink.writeFloat(float: Float) {
293+
writeInt(float.toBits())
294+
}
295+
296+
/**
297+
* Writes eight bytes of a bit representation of [double], in the big-endian order, to this sink.
298+
* Bit representation of the [double] corresponds to the IEEE 754 floating-point "double format" bit layout.
299+
*
300+
* To obtain a bit representation, the [Double.toBits] function is used.
301+
*
302+
* Should be used with care when working with special values (like `NaN`) as bit patterns obtained for [Double.NaN] may vary depending on a platform.
303+
*
304+
* @param double the floating point number to be written.
305+
*
306+
* @throws IllegalStateException when the sink is closed.
307+
*
308+
* @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.writeDouble
309+
*/
310+
public fun Sink.writeDouble(double: Double) {
311+
writeLong(double.toBits())
312+
}
313+
314+
/**
315+
* Writes four bytes of a bit representation of [float], in the little-endian order, to this sink.
316+
* Bit representation of the [float] corresponds to the IEEE 754 floating-point "single format" bit layout.
317+
*
318+
* To obtain a bit representation, the [Float.toBits] function is used.
319+
*
320+
* Should be used with care when working with special values (like `NaN`) as bit patterns obtained for [Float.NaN] may vary depending on a platform.
321+
*
322+
* @param float the floating point number to be written.
323+
*
324+
* @throws IllegalStateException when the sink is closed.
325+
*
326+
* @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.writeFloatLe
327+
*/
328+
public fun Sink.writeFloatLe(float: Float) {
329+
writeIntLe(float.toBits())
330+
}
331+
332+
/**
333+
* Writes eight bytes of a bit representation of [double], in the little-endian order, to this sink.
334+
* Bit representation of the [double] corresponds to the IEEE 754 floating-point "double format" bit layout.
335+
*
336+
* To obtain a bit representation, the [Double.toBits] function is used.
337+
*
338+
* Should be used with care when working with special values (like `NaN`) as bit patterns obtained for [Double.NaN] may vary depending on a platform.
339+
*
340+
* @param double the floating point number to be written.
341+
*
342+
* @throws IllegalStateException when the sink is closed.
343+
*
344+
* @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.writeDoubleLe
345+
*/
346+
public fun Sink.writeDoubleLe(double: Double) {
347+
writeLongLe(double.toBits())
348+
}
349+
278350
/**
279351
* Provides direct access to the sink's internal buffer and hints its emit before exit.
280352
*

core/common/src/Sources.kt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,58 @@ public fun Source.readUIntLe(): UInt = readIntLe().toUInt()
355355
*/
356356
public fun Source.readULongLe(): ULong = readLongLe().toULong()
357357

358+
/**
359+
* Removes four bytes from this source and returns a floating point number with type [Float] composed of it
360+
* according to the big-endian order.
361+
*
362+
* The [Float.Companion.fromBits] function is used for decoding bytes into [Float].
363+
*
364+
* @throws EOFException when there are not enough data to read an unsigned int value.
365+
* @throws IllegalStateException when the source is closed.
366+
*
367+
* @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.readFloat
368+
*/
369+
public fun Source.readFloat(): Float = Float.fromBits(readInt())
370+
371+
/**
372+
* Removes eight bytes from this source and returns a floating point number with type [Double] composed of it
373+
* according to the big-endian order.
374+
*
375+
* The [Double.Companion.fromBits] function is used for decoding bytes into [Double].
376+
*
377+
* @throws EOFException when there are not enough data to read an unsigned int value.
378+
* @throws IllegalStateException when the source is closed.
379+
*
380+
* @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.readDouble
381+
*/
382+
public fun Source.readDouble(): Double = Double.fromBits(readLong())
383+
384+
/**
385+
* Removes four bytes from this source and returns a floating point number with type [Float] composed of it
386+
* according to the little-endian order.
387+
*
388+
* The [Float.Companion.fromBits] function is used for decoding bytes into [Float].
389+
*
390+
* @throws EOFException when there are not enough data to read an unsigned int value.
391+
* @throws IllegalStateException when the source is closed.
392+
*
393+
* @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.readFloatLe
394+
*/
395+
public fun Source.readFloatLe(): Float = Float.fromBits(readIntLe())
396+
397+
/**
398+
* Removes eight bytes from this source and returns a floating point number with type [Double] composed of it
399+
* according to the little-endian order.
400+
*
401+
* The [Double.Companion.fromBits] function is used for decoding bytes into [Double].
402+
*
403+
* @throws EOFException when there are not enough data to read an unsigned int value.
404+
* @throws IllegalStateException when the source is closed.
405+
*
406+
* @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.readDoubleLe
407+
*/
408+
public fun Source.readDoubleLe(): Double = Double.fromBits(readLongLe())
409+
358410
/**
359411
* Return `true` if the next byte to be consumed from this source is equal to [byte].
360412
* Otherwise, return `false` as well as when the source is exhausted.

core/common/test/AbstractSinkTest.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,34 @@ abstract class AbstractSinkTest internal constructor(
449449
assertEquals("Buffer(size=8 hex=efcdab9078563412)", data.toString())
450450
}
451451

452+
@Test
453+
fun writeFloat() {
454+
sink.writeFloat(12345.678F)
455+
sink.flush()
456+
assertEquals(12345.678F.toBits(), data.readInt())
457+
}
458+
459+
@Test
460+
fun writeFloatLe() {
461+
sink.writeFloatLe(12345.678F)
462+
sink.flush()
463+
assertEquals(12345.678F.toBits(), data.readIntLe())
464+
}
465+
466+
@Test
467+
fun writeDouble() {
468+
sink.writeDouble(123456.78901)
469+
sink.flush()
470+
assertEquals(123456.78901.toBits(), data.readLong())
471+
}
472+
473+
@Test
474+
fun writeDoubleLe() {
475+
sink.writeDoubleLe(123456.78901)
476+
sink.flush()
477+
assertEquals(123456.78901.toBits(), data.readLongLe())
478+
}
479+
452480
@Test
453481
fun writeByteString() {
454482
sink.write("təˈranəˌsôr".encodeToByteString())

core/common/test/AbstractSourceTest.kt

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,6 +1464,70 @@ abstract class AbstractBufferedSourceTest internal constructor(
14641464
assertEquals(0x78563412u, source.readUIntLe())
14651465
}
14661466

1467+
@Test
1468+
fun readFloat() {
1469+
sink.write(byteArrayOf(70, 64, -26, -74))
1470+
sink.flush()
1471+
assertEquals(12345.678F.toBits(), source.readFloat().toBits())
1472+
}
1473+
1474+
@Test
1475+
fun readDouble() {
1476+
sink.write(byteArrayOf(64, -2, 36, 12, -97, -56, -13, 35))
1477+
sink.flush()
1478+
assertEquals(123456.78901, source.readDouble())
1479+
}
1480+
1481+
@Test
1482+
fun readFloatLe() {
1483+
sink.write(byteArrayOf(-74, -26, 64, 70))
1484+
sink.flush()
1485+
assertEquals(12345.678F.toBits(), source.readFloatLe().toBits())
1486+
}
1487+
1488+
@Test
1489+
fun readDoubleLe() {
1490+
sink.write(byteArrayOf(35, -13, -56, -97, 12, 36, -2, 64))
1491+
sink.flush()
1492+
assertEquals(123456.78901, source.readDoubleLe())
1493+
}
1494+
1495+
@Test
1496+
fun readTooShortFloatThrows() {
1497+
assertFailsWith<EOFException> { source.readFloat() }
1498+
sink.writeByte(0)
1499+
sink.flush()
1500+
assertFailsWith<EOFException> { source.readFloat() }
1501+
assertTrue(source.request(1))
1502+
}
1503+
1504+
@Test
1505+
fun readTooShortDoubleThrows() {
1506+
assertFailsWith<EOFException> { source.readDouble() }
1507+
sink.writeByte(0)
1508+
sink.flush()
1509+
assertFailsWith<EOFException> { source.readDouble() }
1510+
assertTrue(source.request(1))
1511+
}
1512+
1513+
@Test
1514+
fun readTooShortFloatLeThrows() {
1515+
assertFailsWith<EOFException> { source.readFloatLe() }
1516+
sink.writeByte(0)
1517+
sink.flush()
1518+
assertFailsWith<EOFException> { source.readFloatLe() }
1519+
assertTrue(source.request(1))
1520+
}
1521+
1522+
@Test
1523+
fun readTooShortDoubleLeThrows() {
1524+
assertFailsWith<EOFException> { source.readDoubleLe() }
1525+
sink.writeByte(0)
1526+
sink.flush()
1527+
assertFailsWith<EOFException> { source.readDoubleLe() }
1528+
assertTrue(source.request(1))
1529+
}
1530+
14671531
@Test
14681532
fun readTooShortUnsignedIntThrows() {
14691533
assertFailsWith<EOFException> { source.readUInt() }

core/common/test/samples/samples.kt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,21 @@ class KotlinxIoCoreCommonSamples {
488488
assertEquals(18446744073709551615UL, buffer.readULong())
489489
}
490490

491+
@Test
492+
fun readFloat() {
493+
val buffer = Buffer()
494+
buffer.write(byteArrayOf(70, 64, -26, -74))
495+
assertEquals(12345.678F.toBits(), buffer.readFloat().toBits())
496+
}
497+
498+
@Test
499+
fun readDouble() {
500+
val buffer = Buffer()
501+
buffer.write(byteArrayOf(64, -2, 36, 12, -97, -56, -13, 35))
502+
503+
assertEquals(123456.78901, buffer.readDouble())
504+
}
505+
491506
@Test
492507
fun writeUByte() {
493508
val buffer = Buffer()
@@ -520,6 +535,22 @@ class KotlinxIoCoreCommonSamples {
520535
assertContentEquals(byteArrayOf(-1, -1, -1, -1, -1, -1, -1, -1), buffer.readByteArray())
521536
}
522537

538+
@Test
539+
fun writeFloat() {
540+
val buffer = Buffer()
541+
buffer.writeFloat(12345.678F)
542+
543+
assertContentEquals(byteArrayOf(70, 64, -26, -74), buffer.readByteArray())
544+
}
545+
546+
@Test
547+
fun writeDouble() {
548+
val buffer = Buffer()
549+
buffer.writeDouble(123456.78901)
550+
551+
assertContentEquals(byteArrayOf(64, -2, 36, 12, -97, -56, -13, 35), buffer.readByteArray())
552+
}
553+
523554
@Test
524555
fun flush() {
525556
val rawSink = object : RawSink {
@@ -650,6 +681,20 @@ class KotlinxIoCoreCommonSamples {
650681
assertEquals(0xF0DEBC9A78563412U, buffer.readULongLe())
651682
}
652683

684+
@Test
685+
fun readFloatLe() {
686+
val buffer = Buffer()
687+
buffer.write(byteArrayOf(-74, -26, 64, 70))
688+
assertEquals(12345.678F.toBits(), buffer.readFloatLe().toBits())
689+
}
690+
691+
@Test
692+
fun readDoubleLe() {
693+
val buffer = Buffer()
694+
buffer.write(byteArrayOf(35, -13, -56, -97, 12, 36, -2, 64))
695+
assertEquals(123456.78901, buffer.readDoubleLe())
696+
}
697+
653698
@Test
654699
fun writeUShortLe() {
655700
val buffer = Buffer()
@@ -670,4 +715,20 @@ class KotlinxIoCoreCommonSamples {
670715
buffer.writeULongLe(0x123456789ABCDEF0U)
671716
assertEquals(0xF0DEBC9A78563412U, buffer.readULong())
672717
}
718+
719+
@Test
720+
fun writeFloatLe() {
721+
val buffer = Buffer()
722+
buffer.writeFloatLe(12345.678F)
723+
724+
assertContentEquals(byteArrayOf(-74, -26, 64, 70), buffer.readByteArray())
725+
}
726+
727+
@Test
728+
fun writeDoubleLe() {
729+
val buffer = Buffer()
730+
buffer.writeDoubleLe(123456.78901)
731+
732+
assertContentEquals(byteArrayOf(35, -13, -56, -97, 12, 36, -2, 64), buffer.readByteArray())
733+
}
673734
}

0 commit comments

Comments
 (0)