This page is a placeholder. All examples on this page are currently AI-generated and are not correct. This documentation will be completed in the future with accurate, tested examples.
Overview
Opcode: 0x05
Introduced: Frontier (EVM genesis)
SDIV performs signed integer division on two 256-bit values interpreted as two’s complement signed integers. The result is truncated toward zero (not toward negative infinity like some languages).
Like DIV, division by zero returns 0. Additionally, SDIV has special handling for the edge case of dividing the minimum signed integer by -1.
Specification
Stack Input:
a (top - signed dividend)
b (signed divisor)
Stack Output:
a / b (if b ≠ 0 and not MIN_INT/-1)
MIN_INT (if a = MIN_INT and b = -1)
0 (if b = 0)
Gas Cost: 5 (GasFastStep)
Operation:
Two's complement interpretation:
- Range: -2^255 to 2^255 - 1
- MIN_INT: -2^255 = 0x8000...0000
- -1: 2^256 - 1 = 0xFFFF...FFFF
Behavior
SDIV interprets 256-bit values as signed integers using two’s complement:
- Bit 255 (MSB) determines sign: 0 = positive, 1 = negative
- If
b = 0: Returns 0 (no exception)
- If
a = MIN_INT and b = -1: Returns MIN_INT (overflow case)
- Otherwise: Returns
a / b truncated toward zero
Truncation toward zero:
- Positive quotient: rounds down (e.g., 7/2 = 3)
- Negative quotient: rounds up (e.g., -7/2 = -3, not -4)
Examples
Basic Signed Division
import { sdiv } from '@tevm/voltaire/evm/arithmetic';
import { createFrame } from '@tevm/voltaire/evm/Frame';
// 10 / 2 = 5
const frame = createFrame({ stack: [10n, 2n] });
const err = sdiv(frame);
console.log(frame.stack); // [5n]
Negative Dividend
// -10 / 2 = -5
// -10 in two's complement: 2^256 - 10
const neg10 = (1n << 256n) - 10n;
const frame = createFrame({ stack: [neg10, 2n] });
sdiv(frame);
// Result: -5 in two's complement
const neg5 = (1n << 256n) - 5n;
console.log(frame.stack); // [neg5]
Negative Divisor
// 10 / -2 = -5
const neg2 = (1n << 256n) - 2n;
const frame = createFrame({ stack: [10n, neg2] });
sdiv(frame);
const neg5 = (1n << 256n) - 5n;
console.log(frame.stack); // [neg5]
Both Negative
// -10 / -2 = 5 (negative / negative = positive)
const neg10 = (1n << 256n) - 10n;
const neg2 = (1n << 256n) - 2n;
const frame = createFrame({ stack: [neg10, neg2] });
sdiv(frame);
console.log(frame.stack); // [5n]
Truncation Toward Zero
// 7 / 2 = 3 (not 4)
const frame1 = createFrame({ stack: [7n, 2n] });
sdiv(frame1);
console.log(frame1.stack); // [3n]
// -7 / 2 = -3 (not -4)
// Rounds toward zero, not negative infinity
const neg7 = (1n << 256n) - 7n;
const frame2 = createFrame({ stack: [neg7, 2n] });
sdiv(frame2);
const neg3 = (1n << 256n) - 3n;
console.log(frame2.stack); // [neg3]
MIN_INT / -1 Edge Case
// MIN_INT / -1 would overflow to 2^255 (not representable)
// SDIV returns MIN_INT instead
const MIN_INT = 1n << 255n;
const negOne = (1n << 256n) - 1n;
const frame = createFrame({ stack: [MIN_INT, negOne] });
sdiv(frame);
console.log(frame.stack); // [MIN_INT]
Gas Cost
Cost: 5 gas (GasFastStep)
SDIV has the same gas cost as DIV despite additional sign handling:
Comparison:
- ADD/SUB: 3 gas
- MUL/DIV/MOD/SDIV/SMOD/SIGNEXTEND: 5 gas
- ADDMOD/MULMOD: 8 gas
The sign interpretation adds no gas overhead.
Edge Cases
Division by Zero
// Signed division by zero returns 0
const neg10 = (1n << 256n) - 10n;
const frame = createFrame({ stack: [neg10, 0n] });
sdiv(frame);
console.log(frame.stack); // [0n]
MIN_INT Special Cases
// MIN_INT / -1 = MIN_INT (overflow case)
const MIN_INT = 1n << 255n;
const negOne = (1n << 256n) - 1n;
const frame1 = createFrame({ stack: [MIN_INT, negOne] });
sdiv(frame1);
console.log(frame1.stack); // [MIN_INT]
// MIN_INT / 1 = MIN_INT (no overflow)
const frame2 = createFrame({ stack: [MIN_INT, 1n] });
sdiv(frame2);
console.log(frame2.stack); // [MIN_INT]
// MIN_INT / MIN_INT = 1
const frame3 = createFrame({ stack: [MIN_INT, MIN_INT] });
sdiv(frame3);
console.log(frame3.stack); // [1n]
Zero Division Results
// 0 / -5 = 0
const neg5 = (1n << 256n) - 5n;
const frame = createFrame({ stack: [0n, neg5] });
sdiv(frame);
console.log(frame.stack); // [0n]
Common Usage
Signed Arithmetic
// Calculate price change (can be negative)
function priceChange(int256 oldPrice, int256 newPrice)
pure returns (int256) {
return newPrice - oldPrice; // Can be negative
}
// Average of signed values
function signedAverage(int256 a, int256 b)
pure returns (int256) {
// Must handle negative results correctly
assembly {
let sum := add(a, b)
let result := sdiv(sum, 2)
mstore(0, result)
return(0, 32)
}
}
Directional Calculations
// Calculate slope (can be negative)
function slope(int256 y2, int256 y1, int256 x2, int256 x1)
pure returns (int256) {
require(x2 != x1, "vertical line");
int256 dy = y2 - y1;
int256 dx = x2 - x1;
assembly {
let result := sdiv(dy, dx)
mstore(0, result)
return(0, 32)
}
}
Fixed-Point Signed Math
// Signed fixed-point division
int256 constant FIXED_POINT = 1e18;
function signedWdiv(int256 x, int256 y) pure returns (int256) {
require(y != 0, "division by zero");
assembly {
let result := sdiv(mul(x, FIXED_POINT), y)
mstore(0, result)
return(0, 32)
}
}
Implementation
/**
* SDIV opcode (0x05) - Signed integer division
*/
export function sdiv(frame: FrameType): EvmError | null {
// Consume gas (GasFastStep = 5)
frame.gasRemaining -= 5n;
if (frame.gasRemaining < 0n) {
frame.gasRemaining = 0n;
return { type: "OutOfGas" };
}
// Pop operands
if (frame.stack.length < 2) return { type: "StackUnderflow" };
const a = frame.stack.pop();
const b = frame.stack.pop();
let result: bigint;
if (b === 0n) {
result = 0n;
} else {
const MIN_INT = 1n << 255n;
const MAX_UINT = (1n << 256n) - 1n;
// Special case: MIN_INT / -1 would overflow
if (a === MIN_INT && b === MAX_UINT) {
result = MIN_INT;
} else {
// Convert to signed, divide, convert back
const aSigned = a < MIN_INT ? a : a - (1n << 256n);
const bSigned = b < MIN_INT ? b : b - (1n << 256n);
const quotient = aSigned / bSigned; // BigInt division truncates toward zero
result = quotient < 0n ? (1n << 256n) + quotient : quotient;
}
}
// Push result
if (frame.stack.length >= 1024) return { type: "StackOverflow" };
frame.stack.push(result);
// Increment PC
frame.pc += 1;
return null;
}
Testing
Test Coverage
import { describe, it, expect } from 'vitest';
import { sdiv } from './0x05_SDIV.js';
describe('SDIV (0x05)', () => {
const MIN_INT = 1n << 255n;
const MAX_UINT = (1n << 256n) - 1n;
const toSigned = (n: bigint) => n < 0n ? (1n << 256n) + n : n;
it('divides positive numbers', () => {
const frame = createFrame([10n, 2n]);
expect(sdiv(frame)).toBeNull();
expect(frame.stack).toEqual([5n]);
});
it('handles negative dividend', () => {
const frame = createFrame([toSigned(-10n), 2n]);
expect(sdiv(frame)).toBeNull();
expect(frame.stack).toEqual([toSigned(-5n)]);
});
it('handles negative divisor', () => {
const frame = createFrame([10n, toSigned(-2n)]);
expect(sdiv(frame)).toBeNull();
expect(frame.stack).toEqual([toSigned(-5n)]);
});
it('handles both negative', () => {
const frame = createFrame([toSigned(-10n), toSigned(-2n)]);
expect(sdiv(frame)).toBeNull();
expect(frame.stack).toEqual([5n]);
});
it('truncates toward zero (positive)', () => {
const frame = createFrame([7n, 2n]);
expect(sdiv(frame)).toBeNull();
expect(frame.stack).toEqual([3n]);
});
it('truncates toward zero (negative)', () => {
const frame = createFrame([toSigned(-7n), 2n]);
expect(sdiv(frame)).toBeNull();
expect(frame.stack).toEqual([toSigned(-3n)]);
});
it('handles MIN_INT / -1 overflow case', () => {
const frame = createFrame([MIN_INT, MAX_UINT]);
expect(sdiv(frame)).toBeNull();
expect(frame.stack).toEqual([MIN_INT]);
});
it('handles division by zero', () => {
const frame = createFrame([toSigned(-10n), 0n]);
expect(sdiv(frame)).toBeNull();
expect(frame.stack).toEqual([0n]);
});
});
Security
Sign Interpretation
// Unsigned vs signed division give different results
uint256 a = type(uint256).max; // Max uint = -1 signed
uint256 b = 2;
// Unsigned: MAX / 2 = 2^255 - 1
uint256 unsignedResult;
assembly { unsignedResult := div(a, b) }
// Signed: -1 / 2 = 0 (truncate toward zero)
uint256 signedResult;
assembly { signedResult := sdiv(a, b) }
// Results are different!
MIN_INT Overflow
// MIN_INT / -1 special case
int256 MIN = type(int256).min; // -2^255
int256 result = MIN / -1; // In Solidity, this reverts!
// But in assembly (raw SDIV):
assembly {
// Returns MIN_INT, does not revert
result := sdiv(MIN, sub(0, 1))
}
Truncation Behavior
// Different languages handle negative division differently
// EVM SDIV: Truncates toward zero
// -7 / 2 = -3
// Python, Ruby: Floor division (toward negative infinity)
// -7 // 2 = -4
// Always verify truncation direction matches expectations
Safe Signed Division
// Solidity 0.8.0+ automatically checks
function safeSdiv(int256 a, int256 b) pure returns (int256) {
return a / b; // Reverts on MIN_INT / -1 or b = 0
}
// Explicit checks for assembly usage
function assemblySdiv(int256 a, int256 b) pure returns (int256) {
require(b != 0, "division by zero");
require(!(a == type(int256).min && b == -1), "overflow");
int256 result;
assembly {
result := sdiv(a, b)
}
return result;
}
Benchmarks
SDIV performance identical to DIV:
Execution time:
- ADD: 1.0x
- MUL: 1.2x
- SDIV: 2.5x (same as DIV)
Gas cost:
- 5 gas per signed division
- No overhead for sign handling
- ~200,000 signed divisions per million gas
References