If `[u8; 16]` works for you, then you can trick Anchor into treating it as a `u128` by creating a custom type with the same name, `"u128"`. To not pollute the outer scope, you declare it in a module. Like so:

```
mod custom_scope {

    use anchor_lang::prelude::*;

    #[derive(Copy, Clone, bytemuck::Zeroable, bytemuck::Pod, Debug)]
    #[repr(C)]
    pub struct u128(pub [u8; 16]);

    impl u128 {
        pub fn as_u128(&self) -> core::primitive::u128 {
            core::primitive::u128::from_le_bytes(self.0)
        }
    }

    #[account(zero_copy)]
    #[repr(C)]
    pub struct SomeStruct {
        pub authority: Pubkey,
        pub some_value: u128, // anchor will think this `u128` is a primitive and not a custom type
        pub data: u64,
    }
}

```

It is key to also declare `SomeStruct` in the same scope so that we can do `pub some_value: u128` because this is what tricks Anchor into treating it as a regular `u128` and not a custom type.

And now, when your client sends data as `u128` you can simply cast it into the type the account expects, with `custom_scope::u128(some_value.to_le_bytes())`.

Or if you want your `my_account.some_value` to act as a `u128`, you can simply do `my_account.some_value.as_u128()`.

Here's the demo: https://beta.solpg.io/65184223fb53fa325bfd0bc9

Check the test file and notice it treats the `someValue` field in the account as `BN` without additional conversion.

And in the IDL that the above link generates during build, it will show 

```
{
    "name": "someValue",
    "type": "u128"
}
```