diff --git a/core/common/src/Sinks.kt b/core/common/src/Sinks.kt index de9387752..f8bb80519 100644 --- a/core/common/src/Sinks.kt +++ b/core/common/src/Sinks.kt @@ -275,6 +275,70 @@ public fun Sink.writeULongLe(long: ULong) { writeLongLe(long.toLong()) } +/** + * Writes four bytes of a bit representation of [float], in the big-endian order, to this sink. + * Bit representation of the [float] corresponds to the IEEE 754 floating-point "single format" bit layout. + * + * To obtain a bit representation, the [Float.toBits] function is used. + * + * @param float the floating point number to be written. + * + * @throws IllegalStateException when the sink is closed. + * + * @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.writeFloat + */ +public fun Sink.writeFloat(float: Float) { + writeInt(float.toBits()) +} + +/** + * Writes eight bytes of a bit representation of [double], in the big-endian order, to this sink. + * Bit representation of the [double] corresponds to the IEEE 754 floating-point "double format" bit layout. + * + * To obtain a bit representation, the [Double.toBits] function is used. + * + * @param double the floating point number to be written. + * + * @throws IllegalStateException when the sink is closed. + * + * @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.writeDouble + */ +public fun Sink.writeDouble(double: Double) { + writeLong(double.toBits()) +} + +/** + * Writes four bytes of a bit representation of [float], in the little-endian order, to this sink. + * Bit representation of the [float] corresponds to the IEEE 754 floating-point "single format" bit layout. + * + * To obtain a bit representation, the [Float.toBits] function is used. + * + * @param float the floating point number to be written. + * + * @throws IllegalStateException when the sink is closed. + * + * @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.writeFloatLe + */ +public fun Sink.writeFloatLe(float: Float) { + writeIntLe(float.toBits()) +} + +/** + * Writes eight bytes of a bit representation of [double], in the little-endian order, to this sink. + * Bit representation of the [double] corresponds to the IEEE 754 floating-point "double format" bit layout. + * + * To obtain a bit representation, the [Double.toBits] function is used. + * + * @param double the floating point number to be written. + * + * @throws IllegalStateException when the sink is closed. + * + * @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.writeDoubleLe + */ +public fun Sink.writeDoubleLe(double: Double) { + writeLongLe(double.toBits()) +} + /** * Provides direct access to the sink's internal buffer and hints its emit before exit. * diff --git a/core/common/src/Sources.kt b/core/common/src/Sources.kt index 76b5df53a..3a748714a 100644 --- a/core/common/src/Sources.kt +++ b/core/common/src/Sources.kt @@ -357,6 +357,58 @@ public fun Source.readUIntLe(): UInt = readIntLe().toUInt() */ public fun Source.readULongLe(): ULong = readLongLe().toULong() +/** + * Removes four bytes from this source and returns a floating point number with type [Float] composed of it + * according to the big-endian order. + * + * The [Float.Companion.fromBits] function is used for decoding bytes into [Float]. + * + * @throws EOFException when there are not enough data to read an unsigned int value. + * @throws IllegalStateException when the source is closed. + * + * @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.readFloat + */ +public fun Source.readFloat(): Float = Float.fromBits(readInt()) + +/** + * Removes eight bytes from this source and returns a floating point number with type [Double] composed of it + * according to the big-endian order. + * + * The [Double.Companion.fromBits] function is used for decoding bytes into [Double]. + * + * @throws EOFException when there are not enough data to read an unsigned int value. + * @throws IllegalStateException when the source is closed. + * + * @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.readDouble + */ +public fun Source.readDouble(): Double = Double.fromBits(readLong()) + +/** + * Removes four bytes from this source and returns a floating point number with type [Float] composed of it + * according to the little-endian order. + * + * The [Float.Companion.fromBits] function is used for decoding bytes into [Float]. + * + * @throws EOFException when there are not enough data to read an unsigned int value. + * @throws IllegalStateException when the source is closed. + * + * @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.readFloatLe + */ +public fun Source.readFloatLe(): Float = Float.fromBits(readIntLe()) + +/** + * Removes eight bytes from this source and returns a floating point number with type [Double] composed of it + * according to the little-endian order. + * + * The [Double.Companion.fromBits] function is used for decoding bytes into [Double]. + * + * @throws EOFException when there are not enough data to read an unsigned int value. + * @throws IllegalStateException when the source is closed. + * + * @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.readDoubleLe + */ +public fun Source.readDoubleLe(): Double = Double.fromBits(readLongLe()) + /** * Return `true` if the next byte to be consumed from this source is equal to [byte]. * Otherwise, return `false` as well as when the source is exhausted. diff --git a/core/common/test/AbstractSinkTest.kt b/core/common/test/AbstractSinkTest.kt index 22e048d60..1c2aaa4fe 100644 --- a/core/common/test/AbstractSinkTest.kt +++ b/core/common/test/AbstractSinkTest.kt @@ -449,6 +449,34 @@ abstract class AbstractSinkTest internal constructor( assertEquals("Buffer(size=8 hex=efcdab9078563412)", data.toString()) } + @Test + fun writeFloat() { + sink.writeFloat(12345.678F) + sink.flush() + assertEquals(12345.678F.toBits(), data.readInt()) + } + + @Test + fun writeFloatLe() { + sink.writeFloatLe(12345.678F) + sink.flush() + assertEquals(12345.678F.toBits(), data.readIntLe()) + } + + @Test + fun writeDouble() { + sink.writeDouble(123456.78901) + sink.flush() + assertEquals(123456.78901.toBits(), data.readLong()) + } + + @Test + fun writeDoubleLe() { + sink.writeDoubleLe(123456.78901) + sink.flush() + assertEquals(123456.78901.toBits(), data.readLongLe()) + } + @Test fun writeByteString() { sink.write("təˈranəˌsôr".encodeToByteString()) diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index c27f665f6..da9dbe53c 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -1464,6 +1464,70 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals(0x78563412u, source.readUIntLe()) } + @Test + fun readFloat() { + sink.write(byteArrayOf(70, 64, -26, -74)) + sink.flush() + assertEquals(12345.678F.toBits(), source.readFloat().toBits()) + } + + @Test + fun readDouble() { + sink.write(byteArrayOf(64, -2, 36, 12, -97, -56, -13, 35)) + sink.flush() + assertEquals(123456.78901, source.readDouble()) + } + + @Test + fun readFloatLe() { + sink.write(byteArrayOf(-74, -26, 64, 70)) + sink.flush() + assertEquals(12345.678F.toBits(), source.readFloatLe().toBits()) + } + + @Test + fun readDoubleLe() { + sink.write(byteArrayOf(35, -13, -56, -97, 12, 36, -2, 64)) + sink.flush() + assertEquals(123456.78901, source.readDoubleLe()) + } + + @Test + fun readTooShortFloatThrows() { + assertFailsWith { source.readFloat() } + sink.writeByte(0) + sink.flush() + assertFailsWith { source.readFloat() } + assertTrue(source.request(1)) + } + + @Test + fun readTooShortDoubleThrows() { + assertFailsWith { source.readDouble() } + sink.writeByte(0) + sink.flush() + assertFailsWith { source.readDouble() } + assertTrue(source.request(1)) + } + + @Test + fun readTooShortFloatLeThrows() { + assertFailsWith { source.readFloatLe() } + sink.writeByte(0) + sink.flush() + assertFailsWith { source.readFloatLe() } + assertTrue(source.request(1)) + } + + @Test + fun readTooShortDoubleLeThrows() { + assertFailsWith { source.readDoubleLe() } + sink.writeByte(0) + sink.flush() + assertFailsWith { source.readDoubleLe() } + assertTrue(source.request(1)) + } + @Test fun readTooShortUnsignedIntThrows() { assertFailsWith { source.readUInt() } diff --git a/core/common/test/samples/samples.kt b/core/common/test/samples/samples.kt index 988670e3e..b3f04ee78 100644 --- a/core/common/test/samples/samples.kt +++ b/core/common/test/samples/samples.kt @@ -488,6 +488,21 @@ class KotlinxIoCoreCommonSamples { assertEquals(18446744073709551615UL, buffer.readULong()) } + @Test + fun readFloat() { + val buffer = Buffer() + buffer.write(byteArrayOf(70, 64, -26, -74)) + assertEquals(12345.678F.toBits(), buffer.readFloat().toBits()) + } + + @Test + fun readDouble() { + val buffer = Buffer() + buffer.write(byteArrayOf(64, -2, 36, 12, -97, -56, -13, 35)) + + assertEquals(123456.78901, buffer.readDouble()) + } + @Test fun writeUByte() { val buffer = Buffer() @@ -520,6 +535,22 @@ class KotlinxIoCoreCommonSamples { assertContentEquals(byteArrayOf(-1, -1, -1, -1, -1, -1, -1, -1), buffer.readByteArray()) } + @Test + fun writeFloat() { + val buffer = Buffer() + buffer.writeFloat(12345.678F) + + assertContentEquals(byteArrayOf(70, 64, -26, -74), buffer.readByteArray()) + } + + @Test + fun writeDouble() { + val buffer = Buffer() + buffer.writeDouble(123456.78901) + + assertContentEquals(byteArrayOf(64, -2, 36, 12, -97, -56, -13, 35), buffer.readByteArray()) + } + @Test fun flush() { val rawSink = object : RawSink { @@ -650,6 +681,20 @@ class KotlinxIoCoreCommonSamples { assertEquals(0xF0DEBC9A78563412U, buffer.readULongLe()) } + @Test + fun readFloatLe() { + val buffer = Buffer() + buffer.write(byteArrayOf(-74, -26, 64, 70)) + assertEquals(12345.678F.toBits(), buffer.readFloatLe().toBits()) + } + + @Test + fun readDoubleLe() { + val buffer = Buffer() + buffer.write(byteArrayOf(35, -13, -56, -97, 12, 36, -2, 64)) + assertEquals(123456.78901, buffer.readDoubleLe()) + } + @Test fun writeUShortLe() { val buffer = Buffer() @@ -670,4 +715,20 @@ class KotlinxIoCoreCommonSamples { buffer.writeULongLe(0x123456789ABCDEF0U) assertEquals(0xF0DEBC9A78563412U, buffer.readULong()) } + + @Test + fun writeFloatLe() { + val buffer = Buffer() + buffer.writeFloatLe(12345.678F) + + assertContentEquals(byteArrayOf(-74, -26, 64, 70), buffer.readByteArray()) + } + + @Test + fun writeDoubleLe() { + val buffer = Buffer() + buffer.writeDoubleLe(123456.78901) + + assertContentEquals(byteArrayOf(35, -13, -56, -97, 12, 36, -2, 64), buffer.readByteArray()) + } }