I'm struggling with a floating point to hex conversion in Lua. My application communicates with an old Akai S2000 sampler. The sampler codes two byte messages into four nibble values. The nibbles are in reverse order, hence the most significant nibble is last. There is one parameter that uses a binary encoding of the fraction part of the value. The two MS nibbles are used to encode the integral part of the value and the LS nibbles are used to encode the binary fraction.
Based on this discussion https://bytes.com/topic/c/answers/219928-how-convert-float-hex I have started to implement a Lua algorithm to generate these nibble values from a given parameter value. As I am not that strong with bit calculations I think I am doing many things the wrong way. There should be an easier way to compute these values and avoid a lot of my silly if/else hacks.
My code (pasted below) works for the positive numbers but it is a lot harder with the negative fractions.
In my tests I have added to tables with expected values. Each table works like this:
key = give value * 100
value = expected outcome from my algorithm
I.e. the first entry in the positiveNumbers table represents an input value of 0.01 and the expected output for that value is a four byte MemoryBlock containing 02 00 00 00 (The first two bytes represent the fraction and the last two the integral part).
Currently my algorithm fails at -0.94 and I can't hack around it without breaking for some other value.
Is there anybody who is string with bit calculations and that easily can see any noob mistake I have made especially with converting the negative values? Any help or pointers would be appreciated!
Lua Code:
function float2nibbles(value)
local nibbles = MemoryBlock(4, true)
-- Retreive integral and fraction parts of the given value to be converted
local integ, fract = math.modf(math.abs(value))
-- Calculate the values of the integral part (last two nibbles)
local bi = BigInteger(integ)
if value < 0 then
-- This variable is sometimes added in the negative conversion of the MS nibbles
local lsAdd = 1
if integ == 0 then
lsAdd = 0
end
nibbles:setByte(2, bit.band(bit.bnot(bi:getBitRangeAsInt(0,4)) + lsAdd, 0xF))
nibbles:setByte(3, bit.band(bit.bnot(bi:getBitRangeAsInt(4,4)), 0xF))
else
nibbles:setByte(2, bit.band(bi:getBitRangeAsInt(0,4), 0xF))
nibbles:setByte(3, bit.band(bi:getBitRangeAsInt(4,4), 0xF))
end
-- Calculate the values of the fraction (first two nibbles)
local remainder = fract
local prevRemain = 0
for i = 1,2 do
remainder = remainder * 16
-- Integral part of the remainder
local d = math.modf(remainder)
if value < 0 and fract ~= 0 then
local lsAdd = 1
if fract == 0 or i == 1 then
lsAdd = 0
end
console(string.format("lsAdd %d", lsAdd))
nibbles:setByte(2 - i, bit.band(bit.bnot(d) + lsAdd, 0xF))
else
nibbles:setByte(2 - i, bit.band(d, 0xF))
end
console(string.format("fract %d = %d, %.2f", i, d, remainder))
prevRemain = remainder
remainder = remainder - d
end
-- For some reason this increment helps when the LS nibble should increment the value of the second nibble
if nibbles:getByte(0) == 0 and nibbles:getByte(1) ~= 0 and value < 0 then
console(string.format("weird increment { %d %d }", nibbles:getByte(0), nibbles:getByte(1)))
nibbles:setByte(1, nibbles:getByte(1) + 1)
end
-- The precision of this data is one byte but apparently they seem to use a third increment to check for rounding
remainder = remainder * 16
console(string.format("final remainder %.2f", remainder))
if math.abs(remainder - prevRemain) > 0.001 and remainder > 14 then
console(string.format("overflow -> %.2f (%.2f)", remainder, prevRemain))
if value < 0 then
nibbles:setByte(0, nibbles:getByte(0) - 1)
else
nibbles:setByte(0, nibbles:getByte(0) + 1)
end
end
console(string.format("%.2f : integral part %s (%s), fract %.2f", value, bit.tohex(integ, 2), nibbles:toHexString(1), fract))
return nibbles
end
local positiveNumbers = {
"02 00 00 00",
"05 00 00 00",
"07 00 00 00",
"0A 00 00 00",
"0C 00 00 00",
"0F 00 00 00",
"02 01 00 00",
"04 01 00 00",
"07 01 00 00",
"09 01 00 00",
"0C 01 00 00",
"0E 01 00 00",
"01 02 00 00",
"03 02 00 00",
"06 02 00 00",
"09 02 00 00",
"0B 02 00 00",
"0E 02 00 00",
"00 03 00 00",
"03 03 00 00",
"05 03 00 00",
"08 03 00 00",
"0B 03 00 00",
"0D 03 00 00",
"00 04 00 00",
"02 04 00 00",
"05 04 00 00",
"07 04 00 00",
"0A 04 00 00",
"0C 04 00 00",
"0F 04 00 00",
"02 05 00 00",
"04 05 00 00",
"07 05 00 00",
"09 05 00 00",
"0C 05 00 00",
"0E 05 00 00",
"01 06 00 00",
"03 06 00 00",
"06 06 00 00",
"09 06 00 00",
"0B 06 00 00",
"0E 06 00 00",
"00 07 00 00",
"03 07 00 00",
"05 07 00 00",
"08 07 00 00",
"0B 07 00 00",
"0D 07 00 00",
"00 08 00 00",
"02 08 00 00",
"05 08 00 00",
"07 08 00 00",
"0A 08 00 00",
"0C 08 00 00",
"0F 08 00 00",
"02 09 00 00",
"04 09 00 00",
"07 09 00 00",
"09 09 00 00",
"0C 09 00 00",
"0E 09 00 00",
"01 0A 00 00",
"03 0A 00 00",
"06 0A 00 00",
"09 0A 00 00",
"0B 0A 00 00",
"0E 0A 00 00",
"00 0B 00 00",
"03 0B 00 00",
"05 0B 00 00",
"08 0B 00 00",
"0B 0B 00 00",
"0D 0B 00 00",
"00 0C 00 00",
"02 0C 00 00",
"05 0C 00 00",
"07 0C 00 00",
"0A 0C 00 00",
"0C 0C 00 00",
"0F 0C 00 00",
"02 0D 00 00",
"04 0D 00 00",
"07 0D 00 00",
"09 0D 00 00",
"0C 0D 00 00",
"0E 0D 00 00",
"01 0E 00 00",
"03 0E 00 00",
"06 0E 00 00",
"09 0E 00 00",
"0B 0E 00 00",
"0E 0E 00 00",
"00 0F 00 00",
"03 0F 00 00",
"05 0F 00 00",
"08 0F 00 00",
"0B 0F 00 00",
"0D 0F 00 00",
"00 00 01 00"
}
local negativeNumbers = {
"0E 0F 0F 0F",
"0B 0F 0F 0F",
"09 0F 0F 0F",
"06 0F 0F 0F",
"04 0F 0F 0F",
"01 0F 0F 0F",
"0E 0E 0F 0F",
"0C 0E 0F 0F",
"09 0E 0F 0F",
"07 0E 0F 0F",
"04 0E 0F 0F",
"02 0E 0F 0F",
"0F 0D 0F 0F",
"0D 0D 0F 0F",
"0A 0D 0F 0F",
"07 0D 0F 0F",
"05 0D 0F 0F",
"02 0D 0F 0F",
"00 0D 0F 0F",
"0D 0C 0F 0F",
"0B 0C 0F 0F",
"08 0C 0F 0F",
"05 0C 0F 0F",
"03 0C 0F 0F",
"00 0C 0F 0F",
"0E 0B 0F 0F",
"0B 0B 0F 0F",
"09 0B 0F 0F",
"06 0B 0F 0F",
"04 0B 0F 0F",
"01 0B 0F 0F",
"0E 0A 0F 0F",
"0C 0A 0F 0F",
"09 0A 0F 0F",
"07 0A 0F 0F",
"04 0A 0F 0F",
"02 0A 0F 0F",
"0F 09 0F 0F",
"0D 09 0F 0F",
"0A 09 0F 0F",
"07 09 0F 0F",
"05 09 0F 0F",
"02 09 0F 0F",
"00 09 0F 0F",
"0D 08 0F 0F",
"0B 08 0F 0F",
"08 08 0F 0F",
"05 08 0F 0F",
"03 08 0F 0F",
"00 08 0F 0F",
"0E 07 0F 0F",
"0B 07 0F 0F",
"09 07 0F 0F",
"06 07 0F 0F",
"04 07 0F 0F",
"01 07 0F 0F",
"0E 06 0F 0F",
"0C 06 0F 0F",
"09 06 0F 0F",
"07 06 0F 0F",
"04 06 0F 0F",
"02 06 0F 0F",
"0F 05 0F 0F",
"0D 05 0F 0F",
"0A 05 0F 0F",
"07 05 0F 0F",
"05 05 0F 0F",
"02 05 0F 0F",
"00 05 0F 0F",
"0D 04 0F 0F",
"0B 04 0F 0F",
"08 04 0F 0F",
"05 04 0F 0F",
"03 04 0F 0F",
"00 04 0F 0F",
"0E 03 0F 0F",
"0B 03 0F 0F",
"09 03 0F 0F",
"06 03 0F 0F",
"04 03 0F 0F",
"01 03 0F 0F",
"0E 02 0F 0F",
"0C 02 0F 0F",
"09 02 0F 0F",
"07 02 0F 0F",
"04 02 0F 0F",
"02 02 0F 0F",
"0F 01 0F 0F",
"0D 01 0F 0F",
"0A 01 0F 0F",
"07 01 0F 0F",
"05 01 0F 0F",
"02 01 0F 0F",
"00 01 0F 0F",
"0D 00 0F 0F",
"0B 00 0F 0F",
"08 00 0F 0F",
"05 00 0F 0F",
"03 00 0F 0F",
"00 00 0F 0F"
}
function verifyFloat2Nibbles(value, expectedMemBlock)
local temp = string.upper(float2nibbles(value):toHexString(1))
assert(expectedMemBlock == temp,
string.format("Incorrect result for %.2f, expected %s, got %s", value, expectedMemBlock, temp))
end
for k,v in pairs(positiveNumbers) do
verifyFloat2Nibbles(k / 100, v)
end
for k,v in pairs(negativeNumbers) do
verifyFloat2Nibbles((k / 100) * -1, v)
end
function float2nibbles(value)
local nibbles = MemoryBlock(4, true)
local n = math.floor(math.abs(value)*256 + 0.13)
n = value < 0 and 0x10000 - n or n
for pos = 0, 3 do
nibbles:setByte(pos, n%16)
n = math.floor(n/16)
end
return nibbles
end