Qalculate / libqalculate

Qalculate! library and CLI
https://qalculate.github.io/
GNU General Public License v2.0
1.86k stars 148 forks source link

Bit field function #615

Closed Jookia closed 3 weeks ago

Jookia commented 9 months ago

This might be an odd request, but I spent a LOT of time digging through datasheets that have things like 'bits 8 through 16 of this 32-bit word are X value', and calculating that is something like '((0x12ABCDEF) & ((1<<16)-1)) >> 8)' to get 0xCD. These are not always nibble or byte aligned so it's a rather annoying task.

It would be nice to have something like 'bits(0x12ABCDEF, 15, 8)' to get bits 8 through 15 inclusive, with bits starting at 0. This is similar to what you would see written in a datasheet for a register field.

If this is wanted I can try and do a PR for it.

hanna-kn commented 9 months ago

It should be simple to add a function with the expression \x&((1<<\y)-1))>>\z (or my own variant \x%2^\y>>\z). You can do this locally using qalc with the command function bits x&((1<<y)-1))>>z.

I would prefer a less generic function name, and to me it seems more logical to specify the lowest bit first, as the 2nd argument (e.g. bits(0x12ABCDEF, 8, 15)).

Jookia commented 9 months ago

This works: function bits \x%2^(\y+1)>>\z otherwise it cuts off bit 15.

Maybe bitfield would be a better name? Flipping the arguments is fine too. Maybe even more so to make the input easier to change: So something like bits_at(8, 15, 0x12ABCDEF)? Then you can do function myfield bits_at(8, 15, x) easily or something.

To add to feature creep it would be nice to also have bits_set that would replace the bits at a position with a value. Maybe bits_mask too but I can't think of a time I'd need that in my personal calculations.

hanna-kn commented 9 months ago

I will add the following functions:

bitget(number, bit[, number of bits]) (default value for 3rd argument is 1), or bitget(number, bit[, last bit]) (default value for 3rd argument is 0, indicating that only one bit will be returned)

bitset(number, bit[, value]) (default value for 3rd argument is 1, multiple bits can be specified using a vector in 2nd argument), or bitset(number, bit[, bit 2, ...]) and bitclear(number, bit[, bit 2, ...])

Jookia commented 9 months ago

That sounds cool, though how will the first two be differentiated? If there's a PR I will test it

hanna-kn commented 9 months ago

That sounds cool, though how will the first two be differentiated?

Only one of the two alternatives, for each function, will implemented.

Jookia commented 9 months ago

Specifying bits by offset not length is the only usable one for me. I guess I could macro things if the other solution is implemented.

hanna-kn commented 9 months ago

Done.

> info bitget

Function: Get Bit

bitget(Number, Position[, Last Position])

Returns the binary bit at the specified position. The index of the least significant bit is 1. If last index (third argument) is higher then (first) index, the bits from index to, and
including, last index is returned as a new binary number.

Example: bitget(12, 3) = 1; bitget(0b01011100, 2; 4) = 0b00000110 = 6

Arguments
Number: an integer
Position: an integer ≥ 1 and ≤ 18446744073709551615
Last Position: an integer ≥ 0 and ≤ 18446744073709551615 (optional, default: 0)

> info bitset

Function: Set Bit

bitset(Number, Position[, Value][, Bit Width][, Signed Integer])

Set binary bit at specified position. The index of the least significant bit is 1. Multiple bits get be set using a vector. Specify bit width and signedness to allow sign changes when
the most significant bit is set.

Example: bitset(8, 3) = 12; bitset(0b11111111, 2...8, 0) = 1

Arguments
Number: an integer
Position: an integer ≥ 1 and ≤ 18446744073709551615
Value: a boolean (0 or 1) (optional, default: 1)
Bit Width: an integer ≥ 0 and ≤ 4294967295 (optional, default: 0)
Signed Integer: a boolean (0 or 1) (optional, default: 0)
Jookia commented 9 months ago

I tested these changes. I have a few questions:

  1. Re bitget, what happens if the last index is smaller than the the first index?
  2. The example 'bitset(0b11111111, 2...8, 0) = 1' gives this error:

    bitset(0b11111111, 2...8, 0) = 1

warning: Misplaced decimal separator ignored
bitset(255, 2.8, 0) = 1 (base: 10)
bitset(255, 2.8, 0) − 1 = 0
hanna-kn commented 9 months ago
  1. Re bitget, what happens if the last index is smaller than the the first index?

Only the first index is returned. Perhaps returning bits from first index to last index, in reverse order (e.g. bitget(0b1110, 4, 1) = 0b0111) would be preferable, if last index is non-zero.

The example 'bitset(0b11111111, 2...8, 0) = 1' gives this error

The syntax for 2...8 (creates a vector with integers 2 to 8) is new, and has not been committed to Git yet (I will also implement a similar syntax using colon).

Jookia commented 9 months ago

I was thinking that it would return the bits by width. I can confirm that the code works when using an explicit vector.

howaboutuser commented 9 months ago

(I will also implement a similar syntax using colon).

Add step (start:step:end or start:end:step).

hanna-kn commented 9 months ago

Add step (start:step:end or start:end:step).

start:step:end, as in Matlab, but conflict with sexagesimal numbers forces additional syntax requirements; the colon expression will probably need to be enclosed in square brackets (or possibly parentheses).

Jookia commented 9 months ago

So I spent some time with this last week. Bitget works great! Bitset looks like you can only set or clear a bit, not insert a value. For example:

bitget(0xFAF, 5, 8) to hex = 0xA

But if I want to set the 0xA in 0xFAF to 0xF I have to do this:

bitset(0xFAF, 5...8, 0) | 0xF << 4 to hex

So now there's two ways to index bits in a single line, 0 based and 1 based. I guess I could use a macro to do this for me but to my knowledge you can't define your own macros/functions that get loaded every time the program starts.

hanna-kn commented 9 months ago

I guess I could use a macro to do this for me but to my knowledge you can't define your own macros/functions that get loaded every time the program starts.

Use the function command (function name expression), or the save() function with category (3rd argument) other than "Temporary". The GUIs provide more complete options for function creation.

howaboutuser commented 9 months ago

[Feature request] support struct bit field in c language https://en.cppreference.com/w/c/language/bit_field

You don't have to calulate which bit to get/set, just read/write the variable(bits), it should be easier than "bitset(0xFAF, 5...8, 0) | 0xF << 4 to hex".

hanna-kn commented 9 months ago

But if I want to set the 0xA in 0xFAF to 0xF I have to do this: bitset(0xFAF, 5...8, 0) | 0xF << 4 to hex

Now you can use bitset(0xFAF, 5, 0xF)

Edit: I did not think it through enough (bitset(0xFAF, 5...8, 1) or bitset(0xFAF, 5, [1 1 1 1]) can still be used, but I assume that it's not as useful)

howaboutuser commented 9 months ago

What will bitset(0xFFF,5,0x5) return? 0b1111 0101 1111 0b1111 1010 1111 0b1111 1101 1111 0b1111 1111 1111

Jookia commented 8 months ago

On Tue, Feb 06, 2024 at 12:55:22AM -0800, Hanna Knutsson wrote:

But if I want to set the 0xA in 0xFAF to 0xF I have to do this: bitset(0xFAF, 5...8, 0) | 0xF << 4 to hex

Now you can use bitset(0xFAF, 5, 0xF)

Is this in the latest Git commit?

hanna-kn commented 8 months ago

Is this in the latest Git commit?

It was reverted in the latest commit (bitset(0xFAF, 5, [1 1 1 1]) syntax was not removed, but I assume that it's not as useful).

Jookia commented 8 months ago

So official advice is to use a macro for this?

On Tue, Feb 06, 2024 at 11:53:49AM -0800, Hanna Knutsson wrote:

Is this in the latest Git commit?

It was reverted in the latest commit (bitset(0xFAF, 5, [1 1 1 1]) syntax was not removed, but I assume that it's not as useful).

-- Reply to this email directly or view it on GitHub: https://github.com/Qalculate/libqalculate/issues/615#issuecomment-1930650107 You are receiving this because you authored the thread.

Message ID: @.***>

hanna-kn commented 8 months ago

So official advice is to use a macro for this?

Yes (at least for now).

hanna-kn commented 8 months ago

I've now added integerDigits(), digitGet(), and digitSet() (e.g. digitSet(0xFAF, 1, 0xF, 16) returns 0xFFF) functions.

howaboutuser commented 8 months ago

I think bitset() is different from digitSet(). Here is bitset() version.

  1. 1 to 1 assign bitset(0xFFF,[index1,index2,index3],[value1,value2,value3]) index1=value1 index2=value2 index3=value3

  2. Check whether value can be express using n bits. 1bit: 0-1 2bit: 0-3 3bit: 0-7 4bit: 0-15 5bit: 0-31 Assigning 32 to 5 bits is invalid.

  3. LSB is last element when convert value to vector. bitset(0xFFF,[9,8,7,6,5,4,3],0x13) =bitset(0xFFF,[9,8,7,6,5,4,3],[0,0,1,0,0,1,1]) =0b1110 0100 1111

If bitget() can accept vector, it should return same value if index is same as bitset(). bitget(0b111001001111,[9,8,7,6,5,4,3]) =0x13

hanna-kn commented 8 months ago

bitset(0xFFF,[9,8,7,6,5,4,3],0x13) =bitset(0xFFF,[9,8,7,6,5,4,3],[0,0,1,0,0,1,1])

This would change bitset(0, 1:3) from bitset(0, [1 2 3], [1 1 1]) = 7 to bitset(0, [1 2 3], [0 0 1]) = 4.

howaboutuser commented 8 months ago

I have no better solution, if making third argument optional is more useful than assigning value, stay unchanged.

howaboutuser commented 8 months ago

If third argument is negative (default -1), don't convert it, just set all the bits to 1.

hanna-kn commented 8 months ago

If third argument is negative (default -1), don't convert it, just set all the bits to 1.

Instead of making bitset usage more complicated, it might be better to recommend use of bitset(0xFFF, 3, bitget(0x13, 1:7)) as alternative to bitset(0xFFF,[9,8,7,6,5,4,3],[0,0,1,0,0,1,1]).

I actually consider removing the option to set multiple bits (primarily using argument 2), for consistency and to match the implementation in other software (e.g. Matlab).

howaboutuser commented 8 months ago

bitset(0xFFF,[9,8,7,6,5,4,3],[0,0,1,0,0,1,1]) can reduce to bitset(0xFFF,[9:-1:3],0x13). If third argument default to -1, bitset(0, [1 2 3], [1 1 1]) can reduce to bitset(0, [1 2 3]).

The problem of bitset(0xFFF, 3, bitget(0x13, 1:7) is that bitget(0x13, 1:7) return a number, you don't know how many bits to set. 0x13 =19 =0b10011 =0b010011 =0b0010011 =0b0000000000010011

If you can specify how many bits to set in bitset(), this should be possible. And that is why I propose "1 to 1 assign".

Anyway, I respect your decision.

hanna-kn commented 8 months ago

Another solution would be to add another function, e.g. setbits(0xFFF, 3, 9, 0x13) (would change bits 3 to 9). This way I can completely remove the option to set multiple bits in bitset (and instead return a vector if one or more arguments contains vectors).

The problem of bitset(0xFFF, 3, bitget(0x13, 1:7) is that bitget(0x13, 1:7) return a number, you don't know how many bits to set.

As bitget(0x13, 1:7) does not (while bitget(0x13, 1, 7) does) return a number, it is not a problem.

howaboutuser commented 8 months ago

setbits(0xFFF, 3, 9, 0x13) seems the best solution. Waiting for your release.

howaboutuser commented 7 months ago

version 5.0.0

  1. help bitset will print bitset(Number, Position[, Value][, Bit width][, Signed integer]) and bitset(Number, First position, Last position, Value[, Bit width][, Signed integer])

  2. setbits is missing

hanna-kn commented 7 months ago

version 5.0.0

1. `help bitset` will print
   `bitset(Number, Position[, Value][, Bit width][, Signed integer])` and
   `bitset(Number, First position, Last position, Value[, Bit width][, Signed integer])`

2. setbits is missing

Fixed (bitset was set as name for setbits in data file).

Considering the many changes in version 5.0, at least some non-critical issues such as this are expected.

howaboutuser commented 7 months ago

setbits function is totally missing. (not only in help message)

hanna-kn commented 7 months ago

setbits function is totally missing. (not only in help message)

True, but still rectified by the tiny fix. You can try it yourself by editing functions.xml (replace second r:bitset with r:setbits).

howaboutuser commented 7 months ago

I found functions.xml, but it only have two lines inside.

<?xml version="1.0"?>
<QALCULATE version="5.0.0"/>

Maybe this solution is not for self-contained binaries version.

howaboutuser commented 4 months ago
> help setbits

Function: Set Multiple Bits

setbits(Number, First position, Last position, Value[, Bit width][, Signed integer])

Set binary bits at specified range with binary bits from an integer (index 1 to length of range). The index
of the least significant bit is 1. Specify bit width and signedness (use 1 for signed and 0 for unsigned) to
allow sign changes when the most significant bit is set.

Example: setbits(0xFFFF, 9, 12, 0xA) = 0xFAFF

Arguments
Number: an integer
First position: an integer >= 1 and <= 18446744073709551615
Last position: an integer >= 1 and <= 18446744073709551615
Value: an integer
Bit width: a boolean (0 or 1) (optional, default: 0)
Signed integer: a free value (optional, default: 0)

I want to know what "Bit width" and "Signed integer" do, is there an example? And why "Bit width" is boolean?

hanna-kn commented 4 months ago

I want to know what "Bit width" and "Signed integer" do, is there an example?

setbits(25;8;8;1;8;1) = −103 (1111 1111 1001 1001)
setbits(25;8;8;1;8;0) = 153  (0000 0000 1001 1001)

And why "Bit width" is boolean?

Another unfortunate mistake in setbits (bit width should be integer and signed integer boolean).