Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added API for reading and writing of floating point numbers #193

Merged
merged 2 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions core/api/kotlinx-io-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ public final class kotlinx/io/SinksJvmKt {

public final class kotlinx/io/SinksKt {
public static final fun writeDecimalLong (Lkotlinx/io/Sink;J)V
public static final fun writeDouble (Lkotlinx/io/Sink;D)V
public static final fun writeDoubleLe (Lkotlinx/io/Sink;D)V
public static final fun writeFloat (Lkotlinx/io/Sink;F)V
public static final fun writeFloatLe (Lkotlinx/io/Sink;F)V
public static final fun writeHexadecimalUnsignedLong (Lkotlinx/io/Sink;J)V
public static final fun writeIntLe (Lkotlinx/io/Sink;I)V
public static final fun writeLongLe (Lkotlinx/io/Sink;J)V
Expand Down Expand Up @@ -158,6 +162,10 @@ public final class kotlinx/io/SourcesKt {
public static final fun readByteArray (Lkotlinx/io/Source;)[B
public static final fun readByteArray (Lkotlinx/io/Source;I)[B
public static final fun readDecimalLong (Lkotlinx/io/Source;)J
public static final fun readDouble (Lkotlinx/io/Source;)D
public static final fun readDoubleLe (Lkotlinx/io/Source;)D
public static final fun readFloat (Lkotlinx/io/Source;)F
public static final fun readFloatLe (Lkotlinx/io/Source;)F
public static final fun readHexadecimalUnsignedLong (Lkotlinx/io/Source;)J
public static final fun readIntLe (Lkotlinx/io/Source;)I
public static final fun readLongLe (Lkotlinx/io/Source;)J
Expand Down
72 changes: 72 additions & 0 deletions core/common/src/Sinks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,78 @@ 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.
*
shanshin marked this conversation as resolved.
Show resolved Hide resolved
* 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.
*
* @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.
*
* 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.
*
* @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.
*
* 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.
*
* @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.
*
* 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.
*
* @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.
*
Expand Down
52 changes: 52 additions & 0 deletions core/common/src/Sources.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
28 changes: 28 additions & 0 deletions core/common/test/AbstractSinkTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
64 changes: 64 additions & 0 deletions core/common/test/AbstractSourceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<EOFException> { source.readFloat() }
sink.writeByte(0)
sink.flush()
assertFailsWith<EOFException> { source.readFloat() }
assertTrue(source.request(1))
}

@Test
fun readTooShortDoubleThrows() {
assertFailsWith<EOFException> { source.readDouble() }
sink.writeByte(0)
sink.flush()
assertFailsWith<EOFException> { source.readDouble() }
assertTrue(source.request(1))
}

@Test
fun readTooShortFloatLeThrows() {
assertFailsWith<EOFException> { source.readFloatLe() }
sink.writeByte(0)
sink.flush()
assertFailsWith<EOFException> { source.readFloatLe() }
assertTrue(source.request(1))
}

@Test
fun readTooShortDoubleLeThrows() {
assertFailsWith<EOFException> { source.readDoubleLe() }
sink.writeByte(0)
sink.flush()
assertFailsWith<EOFException> { source.readDoubleLe() }
assertTrue(source.request(1))
}

@Test
fun readTooShortUnsignedIntThrows() {
assertFailsWith<EOFException> { source.readUInt() }
Expand Down
61 changes: 61 additions & 0 deletions core/common/test/samples/samples.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand All @@ -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())
}
}